Polimorfizm - zastosowanie praktyczne

0

Cześć,

ostatnio bawię się w projektach dziedziczeniem i polimorfizmem i tak sobie pomyślałem... Jak często zdarza się Wam korzystać z w pełni zorientowanego obiektowo programowania? Nie mówię tutaj o gotowych klasach polimorficznych dostarczonych w bibliotekach takich jak Qt czy .NET.

Chodzi mi bardziej o własne hierarchie dziedziczenia i ich wykorzystanie w projektach w pracy itp. Często się z tego korzysta? Jeżeli o mnie chodzi sądzę, że raczej częściej korzysta się z normalnego dziedziczenia po to żeby mocniej wyspecjalizować klasę podstawową, aniżeli z inteligentnych wskaźników polimorficznych.

Pytam z czystej ciekawości i jestem ciekaw jako to tak naprawdę jest w życiu hehe :)

1

Jak robiłem grę dla znajomego na urodziny to sporo klas funkcjonalnych z tego korzystało ;)
Przykładowo zrobiłem sobie klasę czysto abstrakcyjną CGerbil (Gerbil to myszoskoczek), która robiła za interfejs dla potworków na mapce. Potworków było kilka typów. Jeden podczas swojej tury coś robił, inny stał w miejscu, a jeszcze inny umierał z głodu po 20 turach. W interfejsie miałem metodę virtual void TakeMove() = 0;. Każdy z typów definiował ją na swój sposób. A do wykonania akcji przez wszystkie potworki wystarczyła pojedyncza pętla.

0

@grzesiek51114 właściwie prawie że non stop się z tego korzysta poprzez interfejsy. Szczególnie jak gdzieś masz Template Method na przykład. A może to po prostu mnie się takie projekty trafiają? ;]

0

Czemu pytałem właściwie? Bo skoro polimofrizm jest często wykorzystywany to dobrze wyrobić sobie nawyk prawidłowego korzystania z niego. W swoim ostatnim projekcie miałem kilka okien o bardzo podobnej funkcjonalności w Qt. Zrobiłem sobie klasę, od której dziedziczyłem metody, żeby tyle nie kodować. Wszystkie podklasy miały np. generowanie komunikatu o błędzie w swojej klasie podstawowej. Wcześniej miałem to zrobione bez dziedziczenia ale wkurzyłem się już przy klepaniu trzeciej klasy podobnego okienka, która była w 80% taka sama jak poprzednie. W ogóle to bardzo fajny mechanizm :) Pytam też, bo w książkach piszą, że ogólnie nie jest to często wykorzystywany mechanizm ale widzę, że okazuje się, że jednak odwrotnie.

Przykład z tymi potworkami dobitnie to pokazuje.
W bardziej rozwiniętych projektach pewnie przyda się UML żeby zobaczyć gdzie te hierarchie się przydają.

1

Polimorfizm to jeden z podstawowych elementów programowania, więc to chyba oczywiste, że jest często używany?

Zarówno w mojej pracy korzysta się praktycznie non stop z polimorfizmu jak i w moim projekcie który jest robiony od roku - praktycznie wszystko co kluczowe jest oparte na polimorfizmie.

Krótka odpowiedź: Tak, korzysta się nader często.

1

@grzesiek51114 zmień książki ;) We wszystkich systemach do wieloetapowego przetwarzania danych stosuje się takie podejście. Wyobraź sobie że masz 10 możliwych algorytmów ekstrakcji danych, 10 algorytmów manipulacji danych i 10 możliwych formatów wyjściowych i piszesz system który umożliwia użytkownikowi wybrać sposób ekstrakcji, listę manipulacji i format wyjściowy. Z delegacyjnym template method opartym o polimorfizm klepniesz to szybko i będzie bardzo przejrzyste - każda z tych 3 grup ma swój interfejs i nasza "główna metoda" ma raptem kilka linijek gdzie woła metody na delegatach.
Jak zrobiłbyś to bez polimorfizmu? Rozpatrując wszystkie przypadki -> pisząc 30 ifów? ;] A jak tych algorytmów będzie po 100? ;)

0

Witam,

@Shalom - no być może ;) Pytanie teraz jak najbardziej efektywnie korzystać z polimorfizmu?

Czy robić tak?

KlasaPochodna1 obj1;
KlasaPochodna2 obj2;

KlasaPodst *wsk = &obj1;
wsk->metoda();

Czy np. tak?

KlasaPodst *wsk = new KlasaPochodna1;
wsk->metoda();

W drugim przypadku trzeba jeszcze ten obiekt zniszczyć co jest chyba trochę trudne mając w kodzie takie ify, które warunkują, której klasy pochodnej użyć. Naprawdę, żeby skorzystać z polimorfizmu trzeba sobie wcześniej najlepiej statycznie potworzyć wszystkie obiekty? Trochę dziwne, bo mając ich dużo będzie to problem, chociaż zawsze można sobie stworzyć tablicę takich obiektów.

Doradźcie mi proszę jak najlepiej to zrobić, ponieważ w Internecie jest gro przykładów ale właśnie zdecydowana większość dotyczy pierwszego sposobu.

Pozdrawiam
Grzesiek

0

http://www.cplusplus.com/reference/memory/unique_ptr/
http://www.cplusplus.com/reference/memory/shared_ptr/
kontener IoC

Zresztą niszczenie obiektu w ogóle nie jest problemem bo przecież wołasz delete wskaznik i już. Oczywiście o ile jesteś normalny i masz tam wirtualny destruktor, ale to zakładam ze jest oczywiste skoro używasz polimorfizmu.

0

Fakt, takie wskaźniki bardzo wiele wyjaśniają. Wirtualny destruktor jak najbardziej jest. MinGW bez niego wywala zresztą ostrzeżenie przy kompilacji. Wcześniej zastanawiałem się jak zrealizować coś takiego poprawnie:

KlasaPodst *wsk;
if(warunek){
	wsk = new KlasaPochodna1;
	wsk->metoda();
}
else{
	wsk = new KlasaPochodna2;
	wsk->metoda();
}

Po takich ifach będą miał wyciek pamięci kiedy będę chciał usunąć wsk. Właśnie o to mi chodziło w jaki sposób temu zaradzić:)
Dlatego też interesuje mnie pętla, o której pisał Ola Nordmann.

0

Nie rozumiem skąd niby wyciek pamięci. Przecież jak zrobisz na koniec delete wsk; to żadnego wycieku nie będzie o_O

0

Polimorfiz polimorfizmem ale interface'y interface'ami. Szału dostaję jak mam poprawiać kod w którym są tuziny interface'ów. Interface z interface'ami a i w środku jeszcze interface'y, gdzie kod nigdy nie miał i nie będzie miał wielu różnych klas implementujących te interface'y i się kończy na relacji jeden interface - jedna klasa. Do tego jeszcze IOC dochodzi i przy dziesiątkach plików w projekcie szukaj sobie odpowiedniej klasy, której nazwa gdzieś w stringu w pliku konfiguracyjnym jest zapisana. Rozumiem, że to przydaje się to gdy ktoś unit testy robi, tylko po co to, skoro tych unit testów nigdy nie było? A potem szlag człowieka trafia, jak zamiast nacisnąć F12 i przejść do definicji klasy musi się w jakieś (ctrl + f)'y bawić.

Tak więc taki apel do was programistów: nie używajcie interface'ów jak nie są wam faktycznie potrzebne.
Dziedziczenia są ok jeśli rozszerzacie istniejącą klasę, acz ostatnio spotkałem się też z przypadkiem, gdzie każda klasa dziedziczyła po klasie, która dziedziczyła z innej klasy, gdzie ostatecznie każda metoda z klasy środkowej, która była używana w klasie finalnej i tak musiała być napisana od nowa. Coś jak stworzenie abstrakcyjnej klasy z bazowej klasy, która przez zamieszanie z access modifierami tylko zagmatwała w kodzie, a usunąć jej nie można było, bo w wielu miejscach kodu były wywołania na klasie środkowej metod z klasy bazowej na instancji klasy w trzeciej hierarchii dziedziczenia.

Nie lubię takich rzeczy. Chcę KISS, KISS, KISS!

0

@wasiu akurat IoC często wymusza użycie interfejsów ;) Zauważ że jeśli np. w Javie używasz kontenera IoC i chcesz anotowac sobie klasy jakimiś @Transactional albo innym AOP to musisz w miejscach gdzie wstrzykujesz mieć interfejs (bo nie da się przypisać proxy w miejsce klasy).
Zresztą nigdy nie spotkałem się z problemami o których piszesz. IntelliJ bez problemów pozwala nawigować pomiędzy klasami/interfejsami w takich sytuacjach.
Zresztą łatwiej pisze się kod kiedy operujesz wąskim interfejsem i po ctrl+spacja dostajesz 3 możliwe metody a nie 300...

0

IoC jasne, że wymusza. Bardziej chodzi o to, że ktoś wprowadza takie rzeczy do kodu bo 'to jest modne' ale w żaden praktyczny sposób tego nie wykorzystuje. W Visualu inteli widocznie nie jest takie inteligentne i z ctrl+space nie widzę dużej różnicy w liście wyników wpisując nazwę klasy, a inferface'u poza dodatkową literką na przedzie - z reguły wystarczy 3-4 litery by mieć większość elementów, które szukamy, a przy ostanio używanych wystarcza z reguły jedna.
Przy programowaniu w Visualu nie tylko ja się z tymi problemami spotkałem, acz i od kilku programistów już słyszałem, że się idzie zgubić w takim kodzie, gdzie za dużo jest interface'ów które praktycznie do niczego nie służą.

0

Zależy co w Visualu się pisze. InteliSense rzeczywiście jest niedoskonałe jeżeli chodzi o Visual C++, ale już np w C# działa świetnie. W tym drugim to pamiętam jak wiele razy było CTRL+spacja, CTRL+spacja, CTRL+spacja i gotowa linijka. Nie wiem jak w C++ CLI, ale gołe C++ w Visualu powinno mieć lepiej zrobione Inteli, bo trochę wstyd np przy Qt Creatorze.

Ot taki offtop od offtopa :)

0
wasiu napisał(a):

Polimorfiz polimorfizmem ale interface'y interface'ami. Szału dostaję jak mam poprawiać kod w którym są tuziny interface'ów. Interface z interface'ami a i w środku jeszcze interface'y, gdzie kod nigdy nie miał i nie będzie miał wielu różnych klas implementujących te interface'y i się kończy na relacji jeden interface - jedna klasa.

Moim zdaniem nie ma w tym nic złego. Może jest to jakieś złamanie YAGNI, ale też nie do końca, bo błąd tak naprawdę popełnia ten, kto z tych interfejsów nie korzysta.

Do tego jeszcze IOC dochodzi i przy dziesiątkach plików w projekcie szukaj sobie odpowiedniej klasy, której nazwa gdzieś w stringu w pliku konfiguracyjnym jest zapisana.

Konfiguracja IoC w plikach? Ale po co? :|

Rozumiem, że to przydaje się to gdy ktoś unit testy robi, tylko po co to, skoro tych unit testów nigdy nie było?

Skoro nie było testów, to co to za programista?

A potem szlag człowieka trafia, jak zamiast nacisnąć F12 i przejść do definicji klasy musi się w jakieś (ctrl + f)'y bawić.

No, trzeba przytrzymać dodatkowy klawisz... Od braku Vima ludziom się w głowach przewraca. ;P

Dziedziczenia są ok jeśli rozszerzacie istniejącą klasę, acz ostatnio spotkałem się też z przypadkiem, gdzie każda klasa dziedziczyła po klasie, która dziedziczyła z innej klasy, gdzie ostatecznie każda metoda z klasy środkowej, która była używana w klasie finalnej i tak musiała być napisana od nowa. Coś jak stworzenie abstrakcyjnej klasy z bazowej klasy, która przez zamieszanie z access modifierami tylko zagmatwała w kodzie, a usunąć jej nie można było, bo w wielu miejscach kodu były wywołania na klasie środkowej metod z klasy bazowej na instancji klasy w trzeciej hierarchii dziedziczenia.

O ile prościej byłoby, gdyby to sensownie zaprojektować na małych interfejsach. :)

wasiu napisał(a):

W Visualu inteli widocznie nie jest takie inteligentne i z ctrl+space nie widzę dużej różnicy w liście wyników wpisując nazwę klasy, a inferface'u poza dodatkową literką na przedzie - z reguły wystarczy 3-4 litery by mieć większość elementów, które szukamy, a przy ostanio używanych wystarcza z reguły jedna.

Wydaje mi się, że @Shalom pisał o tym, że operując na zmiennej typu interfejsowego masz dostępnych mniej metod niż dla zmiennej typu klasy.

IInterfejs x = new Klasa();
Klasa y = new Klasa();
x. // mniej metod niż dla y.

Przy programowaniu w Visualu nie tylko ja się z tymi problemami spotkałem, acz i od kilku programistów już słyszałem, że się idzie zgubić w takim kodzie, gdzie za dużo jest interface'ów które praktycznie do niczego nie służą.

W każdym dużym kodzie idzie się zgubić. W klasach statycznych na 20k linii kodu i metodach tworzonych metodą copy-paste również. I moim zdaniem bardziej niż w kodzie, w którym odpowiedzialności są podzielone na wiele małych interfejsów.

Ogólnie masz rację - niepotrzebne interfejsy są niepotrzebne. Ale świadomy programista, który używa IoC i testów jednostkowych, tych interfejsów potrzebuje.

1 użytkowników online, w tym zalogowanych: 0, gości: 1