• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

klasy, dziedziczenie - zmienne wirtualne ( ich brak )

Object Storage Arubacloud
0 głosów
259 wizyt
pytanie zadane 29 maja 2018 w C i C++ przez Jakub 0 Pasjonat (23,120 p.)

Witam, uczę się z książki C++ Szkoła programowania i jestem na rozdziale dotyczącym dziedziczenia. Mam takie testowe klasy:

class Rectangle {
private:
	int x, y;
	std::string name;
public:

	virtual void show() {
		std::cout << name << " -> " << x <<
			", " << y << std::endl;
	}

	int& retx() {
		return x;
	}

	int& rety() {
		return y;
	}

	std::string& retname() {
		return name;
	}

	Rectangle(std::string n, int v1 = 1, int v2 = 1) : name(n) {
		x = v1;
		y = v2;
	}

	int vvv = 1;

protected:
	std::string& baseClassName() {
		return name;
	}

	int& baseClassX() {
		return x;
	}

	int& baseClassY() {
		return y;
	}
};

class Cube : public Rectangle { //klasa powinna nazywać się raczej Cuboid ale już  nie zmieniałem 

private:
	int z;
public:

	virtual void show() {
		std::cout << retname() << " -> " << baseClassX() << ", "
			<< baseClassY() << ", " << z << std::endl;
	}

	Cube(std::string n, int v1 = 1, int v2 = 1, int v3 = 1)
		: z(v3), Rectangle(n, v1, v2) {
	}

	int& retz() {
		return z;
	}

	int vvv = 2;
};

Oczywiście nie ma to być piękny kod ale kod w którym sprawdzam i testuje wiele sytuacji ( bo to chyba konieczny proces żeby przyswoić sobie wiedzę ). Wiem już że metody wirtualne są po to że kiedy użyjemy np. referencji typu Rectangle, na jakiś obiekt typu Rectangle lub Cube to żeby wywołanie funkcji ( np show ) nie zależało od typu referencji tylko od tego na co ona wskazuje. Jednak natknąłem się na jeszcze inny problem, w deklaracji klasy Bazowej i pochodnej mamy pole ( zmienną ) vvv. Co zrobić żeby zachowywała się podobnie jak metody wirtualne? No bo np. tu:

Cube cub1{ "cub1",10,10,10 };
Rectangle rec1{ "rec1",6,2 };

Rectangle& ref1 = cub1;
std::cout<<ref1.vvv; //1 a chciałbym by było 2 

Rectangle& ref2 = rec1;
std::cout<<ref2.vvv; //1

Czyli nie zależała od typu referencji ale od typu obiektu na jaki referencja wskazuje. Bo nie mogę zapisać:

virtual int vvv = 1 -> w bazowej

virtual int vvv = 2 -> w pochodnej

 

Będę bardzo wdzięczny za radę  jak sobie z tym poradzić ;)

1 odpowiedź

0 głosów
odpowiedź 29 maja 2018 przez RafalS VIP (122,820 p.)
edycja 29 maja 2018 przez RafalS
 
Najlepsza

Nie, takie cos nie jest możliwe i biorac pod uwage to ze nie powinno sie uzywac skladowych publicznych w programowaniu obiektowym - taki mechanizm nie ma racji bytu. Jesli chcesz osiagnac sam efekt to zrob virtualna metode get ktora zwroci referencje do odpowiedniej skladowej.

A tak na marginesie wybrales bardzo zly przyklad dziedziczenia: w peogrprogramo obiektowym kwadrat nie jest prostokatem i i rekruterzy lubia zadawac to pytanie na rozmowach o prace :p

Najłatwiej będzie pokazać kod:

class Rectangle {
protected:
	double width;
	double height;
public:
	double getWidth() { return width; }
	double getHeigth() { return height; }
	double setHeight(double newHeight) { height = newHeight; }
	double setWidth(double newWidth) { width = newWidth; }
	double area() { return width * height; }
	double length() { return 2 * width + 2 * height; }
};

class Cube : public Rectangle {
public:
	//ale moment przeciez cube ma tylko jedno pole
	//co tu zrobic?????
	double setHeight(double newHeight) { height = newHeight; width = newHeight; }
	double setWidth(double newWidth) { width = newWidth; height = newWidth; }
	//brzydkie jak cholera, widac na pierwszy rzut oka
	//interfejs publiczny tej klasy jest nieczytelny
	//i nadmiarowy
};

I pozostaje problem z tym, że tak na prawdę to dla kwadratu nie ma sensu rozdzielenie wysokości i szerokości. Ktoś powie, rozwiązaliśmy problem, nie wygląda to ładnie, ale wszystko jest ok.

Ale czemu dziedziczenie tu tak bardzo nie pasuje skoro kwadrat przecież jest prostokątem. Po pierwsze co się może rzucić w oczy - generalnie dziedziczenie ma rozszerzać funkcjonalności. Tutaj mamy ich zawężenie, bo pasowałoby usunąć jedną pare seterów i geterów, gdyż są one zbędne. To samo z polami, jedno z nich jest zbędne. Widzimy więc, że to co chcielibyśmy zrobić to okroić klase prostokąt a nie dodać do niej dodatkowe funkcjonalności poza tymi, które już ma.

Czemu to jest złe? Na razie bez przytaczania jakichkolwiek zasad. Przysłonięte metody robią coś więcej niż twierdzą, że robią w nazwie. setHeigth ustawia równocześnie width. Bez sensu. Jakiś programista może patrzeć na same deklaracie w pliku .h i nie będzie wiedział, że metoda, której używa ma efekty uboczne, które mogą zostać wykryte o wiele później w postaci ciężkiego w znalezieniu buga.

Prawdziwy problem polega na nieprecyzyjnym nazwaniu naszych obiektów. Rectangle powinien nazywać się ResizeableRectangle, bo można edytować jego boki po stwtorzeniu obiektu. Teraz widzimy, że dziedziczenie jest zaprojektowane bez sensu, bo kwadrat - w większości przypadków - wcale nie jest prostokątem o edytowalnych bokach.

Rozwiązanie problemu to wywalenie seterów i zrobienie z naszych kształtów tzw "immutable objects", czyli takich obiektów których po stworzeniu nie można już edytować. Tzn boki prostokąta można ustawiać tylko w konstruktorze. Jeśli chcemy prostokąt o innych bokach trzeba stworzyć nowy. Poniekąd jest to uczieczka od oryginalnego problemu, ale jest to jedyne sensowne rozwiązanie. Teraz dziedziczenie niczego już nie zepsuje.

Teraz czemu cała ta debata jest ważna i czemu pyta się o to na rozmowach o pracę. Podobny mechanizm jest częstym źródłem błędów w dużych aplikacjach, ale można go wyeliminować stosując się do jednej z zasady SOLID, która mówi, że obiekty klas pochodnych powinne "pasować" tam gdzie oczekujemy obiektów bazowych. Chodzi o to, że rozszerzona funkcja musi działać dla większego lub równego zbioru danych wejściowych i zwracać mniejszy lub równy zbiór danych wyjściowych. Nie może osłabić post-conditions i wzmocnić pre-conditions. Tzn jeśli metoda z klasy bazowej o takiej sygnaturze:

int fun(int x);

Działała zarówno dla dodatnich jak i ujemnych iksów a zwracała liczby tylko dodatnie to rozszerzenie tej metody w klasie pochodnej nie może zawęzić warunków wstępnych (np rzucać wyjątku w przypadku jesli x jest < 0) i poszerzać warunków wyjściowych (tzn zwracać liczb dodatnich i ujemnych jeśli bazowa zwracała tylko dodatnie, to samo z rzucaniem wyjątku; jeśli bazowa wyjątków nie rzucała, to pochodna też może, bo te obiekty mają być wymienialne, tzn, że gdyby w miejscach gdzie zazwyczaj pracujemy na obiektach klasy bazowej pojawił się obiekt klasy pochodnej to, żeby nic nas nie zaskoczyło - a zaskoczeniem mogłoby być np rzucenie wyjątku; bazowa nie rzucała więc nie baliśmy się że rzuci i nie łapaliśmy, przez co gdy pojawi się tam pochodna to wyjątek nas zaskoczy.

Rozpisałem się, prawdopodobnie niepotrzebnie, bo jak troszke googlowałem na ten temat to wszędzie opisywany jest własnie ten problem relacji prostokąta z kwadratem :D. Nawet po polsku jest:

https://pl.wikipedia.org/wiki/Zasada_podstawienia_Liskov

A tutaj bardzo ciekawa wypowiedz na stacku:

https://softwareengineering.stackexchange.com/questions/238176/why-would-square-inheriting-from-rectangle-be-problematic-if-we-override-the-set?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa

I generalnie dziedziczenie jest bardzo kłopotliwe więc często rezygnuje się z niego na rzecz kompozycji.

komentarz 29 maja 2018 przez Jakub 0 Pasjonat (23,120 p.)

Dzięki, teraz wiem przynajmniej że to nie jest możliwe. Ogólnie też wiem że do czegoś takiego używa się metod ale to był tylko taki przykład ;)

* A to nie jest tak że każdy kwadrat jest też prostokątem a nie każdy prostokąt kwadratem? Co do nazwy klasy pochodnej to zdaje sobie sprawę że powinno być Cuboid ( prostopadłościan ) a nie Cube czyli sześcian.

Wieczorem rozpisze czemu.

To nie zamykam jeszcze pytania 

komentarz 29 maja 2018 przez RafalS VIP (122,820 p.)
Jest tak w matematyce :P. W dziedziczeniu nie. I wiele osób narzeka, że we wstępach do programowania obiektowego podaje się błędne przykłady, które wyglądają ok, bo coś jest bardziej specyficzną wersją czegoś innego, ale dziedziczenie nie jest aż tak 1 do 1 z przykładami z życia wziętymi.

Generalnie dziedziczenie to pewna abstrakcja, którą jest łatwiej na początku zrozumieć jak się poda kilka takich przykładów z życia wziętych. Ok na początek, ale na dłuższą metę okaże się, że część tych przykładów z życia ni jak nie ma się do dziedziczenia. Nie da się dobrze tego ogarnąć inaczej niż przed doświadczenie i poznanie zasad projektowania powiązań między klasami.
komentarz 29 maja 2018 przez RafalS VIP (122,820 p.)
Edytowałem post, rzuć okiem
komentarz 29 maja 2018 przez Jakub 0 Pasjonat (23,120 p.)
edycja 29 maja 2018 przez Jakub 0
Dziękuje mnóstwo pracy włożonej w wytłumaczenie dlaczego kwadrat nie powinien dziedziczyć z prostokąta. Nie miałem o tym pojęcia więc z pewnością ta wiedza okaże się dla mnie praktyczna. To jednak nie jest tak że w moim kodzie klasa kwadrat dziedziczy z klasy prostokąta, wygląda to raczej tak że trójwymiarowy prostopadłościan dziedziczy z dwuwymiarowego prostokąta ( czyli do klasy pochodnej dodana jest składowa 'z' ). Dałem błędną nazwę klasy: 'Cube' czyli sześcian ( bo kwadrat to square ). Ponieważ sześcian ma wymiary x,y i z takie same to faktycznie to dziedziczenie sześcianu z prostokąta nie miało by sensu. Tyle że klasa pochodna powinna się nazywać Cuboid, czyli już prostopadłościan. Powinienem zmienić nazwę klasy żeby nie była myląca...

 

* chodź dziedziczenie prostopadłościanu z prostokąta też nie jest rzecz jasna do końca poprawne

Podobne pytania

0 głosów
0 odpowiedzi 186 wizyt
pytanie zadane 8 grudnia 2017 w C i C++ przez neefiq Nowicjusz (120 p.)
0 głosów
1 odpowiedź 230 wizyt
pytanie zadane 24 maja 2020 w C# przez Penguin77 Nowicjusz (160 p.)
0 głosów
1 odpowiedź 550 wizyt
pytanie zadane 10 listopada 2017 w C i C++ przez Poczatkujacy Nowicjusz (150 p.)

92,576 zapytań

141,426 odpowiedzi

319,651 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...