Programowanie obiektowe - kiedy klasa, a kiedy nie?

Nie chodzi mi o to, aby udzielona odpowiedź pokazywała różnice między np. klasą, a obiektem czy po prostu przytaczała różne teorie odnośnie klasy. Wiem co to jest klasa. Mam jednak spory i ciągnący się w czasie problem z projektowaniem obiektowego kodu i to właśnie przez klasy. Nie mogę znaleźć granicy pomiędzy stworzeniem klasy, a rozwiązaniem problemu inaczej. Teoria dotycząca klas w dalszym ciągu nie daje mi wyraźnych odpowiedzi. Klasa ma być jak najbardziej jednostkowa, wyspecjalizowana, klasa powinna być rzeczownikiem - i wiele innych teorii, które nie pomagają mi w znalezieniu tej “magicznej” granicy. Przecież nie będę opierać swoich aplikacji na jedynie bardzo podstawowych klasach typu “menu item”. Spełniają w pełni definicje klasy, ale w żaden sposób nie są powiązane z funkcjonalnością odpowiednią dla konkretnej aplikacji.

 

Chodzi mi o dyskusyjne przypadki, gdzie np. klasa może być użyta, jej zastosowanie jest zgodne z definicją. Wielu nawet by się sprzeczało że tak jest bardzo dobrze. Jednak być może powinno się użyć innej alternatywy, bo, dajmy na to - tak sądzą niektórzy weterani specjaliści. Więc poniekąd chodzi o poprawne zaprojektowanie kodu z klasami, w pewnym sensie o “najpoprawniejsze” ich zastosowanie.

 

Chcę skrajnych przypadków, gdzie większość uważa, że zastosowanie klasy jest w pełni poprawne i uzasadnione, jednak z czasem zaczyna wychodzić, że użycie klasy było błędem. Nie chodzi mi też o trzymanie się tej bezpiecznej wersji, gdzie przyjmujemy klasy typu “menu item” i nie zdobywamy się na nic bardziej związanego z funkcjonalnością danej aplikacji.

 

Być może mój problem polega częściowo na tym, że sądzę, że klasa powinna być jak najtrwalsza, ale jeżeli niekoniecznie - to wtedy pojawia się dużo więcej możliwości rozplanowania aplikacji, a kasowanie jakiejś funkcjonalności może równać się skasowaniu całej klasy, co jest chyba niepożądane. Nawet głupi zaawansowany kalkulator można rozplanować lepiej bądź gorzej w takim przypadku i kiedy zaczyna się to lepiej, a kończy gorzej przy czym to “gorzej” jest dalej w pełni poprawne obiektowo?

Nie jestem 

 

tylko amatorem - programistą, ale jeśli chodzi o:

 

 

to myślę, że jeśli potrzebujesz większej ilości podobnych obiektów (wraz z funkcjami dla nich), to warto jest stworzyć klasę, jeśli jednak użycie klasy miało by się sprowadzać do użycia np jednego obiektu, to myślę, że używanie klasy nie ma sensu. Jeśli w dalszej części projektu masz zamiar wykorzystać więcej obiektów danej klasy lub może się ona przydać w innych projektach, wtedy warto zrobić osobną klasę.

 

Pozdrawiam.

Jak kolega powyżej również nie jestem jakimś specjalistą jednak trochę już kodu napisałem.

 

Osobiście tworzę klasę za każdym razem jak jakaś zmienna musi być sparowana z jakąś inną zmienną lub funkcją. Jeśli ma być indywidualnym bytem nawet jeśli wykorzystam go tylko raz. Czemu? Bo zapewnia mi to większe bezpieczeństwo i porządek. Zmienne nie latają po kodzie samopas, ale zawsze są spięte z konkretnym obiektem.

 

Widzę, że Twój tok myślenia idzie w kierunku: “Hmm kiedy powinienem zastosować klasę”, a powinieneś raczej myśleć “Hmm kiedy jej nie stosować”. Programujesz obiektowo? Twórz klasy i obiekty, a tylko w wyjątkowych sytuacjach z konkretnych przyczyn próbuj rozwiązać problem inaczej.

 

Ale to tylko moje zdanie, mogę się mylić, dopiero zaczynam swoją karierę profesjonalną…

Klasa to pewna abstrakcja. Jeśli rzecz, której dotyczy, nie daje się uprościć do typu prostego, to warto zastosować klasę. Przykładowo:

Pojemnik, to dobry kandydat na klasę. Możemy sobie dziedziczyć, możemy dodawać metody, wszystko co trzeba - łatwo i przyjemnie.

Ale Pojemność to już coś, co można przedstawić jako float pamiętając o jednostce “litr” jako o obowiązującej. Wszystko zależy od tego co chcemy opakować w klasę. Zresztą ciężko mi znaleźć miejsca, gdzie klasy da się zastąpić w inny sposób (strukturą? w C# struktura to w gruncie rzeczy klasa, więc granica się zaciera), tablicą zmiennych? Nie cofajmy się do czasów języka C.

Jedyne co mi przychodzi na myśl, gdzie klasa mogłaby być problemem, to serializacja/deserializacja w różnych środowiskach. Czasem po prostu łatwiej przekazać dane w postaci stringa rozdzielonego pipe’ami “|” niż robić parser XMLa w PHP żeby przyjął naszą zserializowaną klasę z C#.

Jeśli programujesz obiektowo to programuj obiektowo i twórz klasy. Prosta zasada. Ogólnie polecam zapoznać się z podstawowymi według mnie wzorcami projektowymi zaproponowanymi przez “Gang Czterech” http://www.blackwasp.co.uk/GofPatterns.aspx 

No i problem polega na tym, że obydwoje (LonngerM i Konsola) podaliście wg mnie argumenty prawidłowe, mimo że macie odmienne spojrzenie na klasy. Można pokusić się o porównanie do rzeczywistości i na tym oprzeć swoje zdanie, ale to też nie jest takie łatwe. Przecież to, że w rzeczywistości jakiś przedmiot istnieje tylko (pozornie?) jeden nie znaczy, że w przyszłości - być może odległej, nie znajdą się kolejne. A jednak ludzie mają raczej tendencje do ujmowania czegoś w klasy tzn. ujmować obiekt w typ dopiero po wystąpieniu więcej niż raz tego danego obiektu i w sumie takie podejście też nie wydaje się złe. Może w rzeczywistości obydwa te podejścia są dobre? Jedno jest profilaktyczne, a drugie “w razie potrzeby”, ale obydwa są na tyle dobre, aby wybrać z nich dowolny, wygodny dla siebie?

 

@LonngerM  Co do mojego podejścia odnośnie unikania klas - chcę wykluczyć pozornie dobre i poprawne zastosowanie klas, które w przyszłości tak naprawdę generuje problemy, zamiast je rozwiązywać. Po wykluczeniu dostatecznej ilości pozornie dobrych przypadków użycia powinno udać się znacznie zawęzić możliwość stosowania klasy głównie do tych przypadków w których powinno się je wykorzystywać, choć masz racje co do tego, że raczej kieruje się w stronę ich nieużywania, niż używania. Może to dlatego, że w znacznej większości widzę teksty mówiące o wielkiej potrzebie tworzenia klas i dodatkowo maksymalnym specjalizowaniu, minimalizowaniu ich, a gdzieś obok tych tekstów znajdują się też “protesty” twierdzące, że często może to być przerost formy nad treścią. Trudno mi powiedzieć z czym tu się bardziej zgodzić, ponieważ nieodpowiednie rozplanowanie klasowe może mieć w przyszłości przykre skutki, choć faktycznie profilaktyczne tworzenie klasy wszędzie gdzie się da, wydaje się dość dobrym rozwiązaniem.

Możesz opisać problem który według Ciebie jest nie do rozwiązania obiektowo? Pozatym jeśli to możliwe podaj przykłąd jego rozwiązania nie obiektowego, które według Ciebie jest lepsze niż obiektowe.

 

Wiem o czym mówisz, bo sam maiłem takie przemyślenia jak zaczynałem bawić się obiektami. Teraz klasy tworzę w najmniejszych projektach, bo pozwalają zachować pewien porządek. Klasy statyczne dzielą funkcje w zależności od czego są (operacje na plikach, łączenie z bazą danych itp), obiekty wykorzystuje wszędzie tam gdzie dane są w jakiś sposób przesyłane w większej ilości, a zmienne używam już tylko wewnątrz poszczególnych funkcji i to najczęściej przy pętlach;)

 

@mordesku dobrze pisze - napisz o swoim przypadku to zobaczymy, bo przyznam równiez mnie on mocno zainteresował.

@mordesku  Dlaczego wnioskujesz po tym co napisałem, że sugeruję, że istnieją problemy, których rozwiązać obiektowo się nie da? Chodzi jedynie o wykluczenie pozornie dobrych sposobów użycia klas i jednocześnie dojście do odpowiedzi, kiedy najlepiej te klasy stosować. Typy prymitywne są tutaj dość dobrym przykładem. Jedne języki mają je rozwiązane jako obiekty, a drugie jako… no, typy prymitywne. Nie chcę też wchodzić w sferę wydajności bo tutaj w niektórych przypadkach jasne okaże się, że lepiej napisać coś “na piechotę” niż obiektowo - ale takim tokiem myślenia cofnąć można się do Assemblera. Trzymać się będę najlepszego odwzorowania rzeczywistości, więc np. wg mnie  @hindus  podał dość dobry przykład, kiedy w dwóch przypadkach można klasę zastosować, ale niekoniecznie byłoby to najwłaściwsze w jednym z tych przypadków. Może na wstępie odnieś się do tego przypadku. Z drugiej strony może być tak, że “szukam igły w stogu siana” - taka trochę moja wada i tworząc ten temat spodziewałem się takiej możliwości, ale to zostawmy “na koniec”. Co więcej, wcale nie chcę mieć racji, bo to mnie utwierdzi że wgłębianie się w takie szczegóły jest w jakiś sposób racjonalne, choć także już przy tworzeniu tego tematu niemalże wiedziałem, że niepotrzebnie aż tak się w to wszystko “wgłębiam”. Stworzyłem go jednak dlatego, że niewiele można znaleźć informacji w sieci na temat dobrego projektowania kodu obiektowego, a przykłady z klasami “Samochodem” czy “Fakturą” to zdecydowanie za mało.

 

Sugeruje tak ponieważ w pierwszym poście napisałeś:

@mordesku  racja. W tym przypadku “problem” użyty został bardziej potocznie. Chodziło bardziej o “dylemat”.

 

@LonngerM  a przypadkiem nie tworzy Ci się niekiedy taki bałagan klasowy? Pytam, bo faktycznie jestem nieco sceptycznie nastawiony do stosowania klas wszędzie gdzie to możliwe, ale całkiem możliwe, że niepotrzebnie. Co sądzisz o przykładzie podanym przez hindusa?

Rozumiem szkoda że nie masz przykładów, nie lubie wdawać się ideologiczne dyskusje tego typu. Jeśli chodzi o wartości primitywne i ich nie prymitywne opakownie to dość fajnie mozna zobaczyć jakie są wady użycia prymitywów i jakie zalety użycia obiektów. Chociaż oczywiście uzywanie prymitywów też ma swoje zalety jednak na zupełnie innej płaszczyźnie (np tam gdzie każdy bajt pamięci się liczy). Dzięki prymitywom opakowanym w klasy tak jak np w javie Long i long oraz Integer i int możemy w łatwy sposób je sortować czy porównywać.  Każdy z nich implementuje wspólny interfejs Comparable. Dzięki czemu może zostać posortowany przez metodę która operuje na liście, tablicy czy innej kolekcji takich właśnie obiektów implementujących ten interfejs. Realizacja tego dla zwykłych prymitywów wiązała by się z potrzebą implementacji masy kodu dla każdego typu a tak wykorzystujemy to co dostarczy nam w odpowiedzi wywołana metoda compeareTo(). W tym przypadku widać że nie musimy w ogóle wiedzieć jaki jest typ danych trzymany przez obiekt, interesuje nas tylko co zwróci metoda.

Całkiem fajna, rzeczowa odpowiedź odnośnie argumentacji za obiektowymi typami prymitywnymi. Co do przykładów…  hindus  podał dość dobry, ja spróbuję podać kolejne, jednak łatwiejsze do rozwiązania, ustalenia odpowiedzi, już niezwiązane z typami prymitywnymi. Np. klasa “Nauka” w programie do nauki liter dla dziecka i jej metody typu pokazanie litery, skasowanie jej. Niby wszystko ok, ale przecież na 99,9% (100?) nie będzie nigdy potrzeby stworzenia drugiej instancji tej klasy, pomijając że mimo wyspecjalizowania klasy (robi coś konkretnego) jest ona dość ogólna. Zresztą w niektórych książkach piszą, że wiele razy będziemy tworzyć klasy, mimo że nigdy nie użyjemy więcej niż jednej instancji obiektu.

 

Schodząc do nieco bardziej codziennych sytuacji: Co jednak w językach pozwalających tworzyć obiekty bezklasowe, jeżeli mamy w sumie pewność, że instancja będzie tylko jedna? W którym momencie tworzymy klasę zbyt ogólną i dodajemy do kodu w ten sposób niepotrzebny balast? Mamy “rzeczownik”, więc teoretycznie można stworzyć klasę, no ale jak rozbić klasę “Nauka”, jeżeli robi coś tak prostego jak pokazywanie liter po kolei? Może to kwestia nazewnictwa i “Nauka” jest bardzo odpowiednią nazwą, a tylko wydaje się zbyt ogólną?

 

Nie lepiej stworzyć w tym przypadku klasy “Litera”, która będzie przechowywać jej wartość i funkcje Pokaż/ukryj? Następnie klasa nauka będzie wykorzystywać obiekty typu “Litera” do przeprowadzenia nauki - generowanie ich wartości i operacje typu pokaż ukryj. Nie widzę tu problemu by nie tworzyć klas.

 

Powołujesz się na przykład hindusa, chodzi o te beczki tak? Również nie widzę problemu. Pojemnik będzie zawierać właściwość - pojemność, która będzie dynamicznie generowana za pomocą zmiennych przetrzymujących wymiary i jednostki.

 

Jakoś nie mogę się dopatrzeć tutaj problemu. Tak jak mówiłem, tworzę takie klasy by najlepiej zachować porządek w kodzie. No chyba, że mówimy o C i pisanie pod Arduino;P

 

Mówi Ci coś wzorzec/antywzorzec Singleton http://pl.wikipedia.org/wiki/Singleton_%28wzorzec_projektowy%29. Według mnie trochę doszukujesz się dziury w całym. Osobiście podpinam się pod wypowiedź LonngerM dotyczącą porządku. Nie ma nic lepszego niż jasna spója architektura.

 

Dodam od siebie że piszę zawodowo i tylko obiektowo. W projektach mam po kilkanaście tysięcy klas i nie mam problemu z odnalezieniem się. Są widoki mają jasne nazwy dzięczi czemu wiem co wyświetlają. Są serwsiy również z jasnymy nazwami, są komendy itd. Odpowiednia architektura która również jest obiektowa (EventBus, CommandMap itd.) pozwalają mi po 3 latach rozwoju aplikacji dalej bez problemu i obaw dodawać nowe i modyfikować stare funkcje.

Akurat musiałem ukończyć edycję posta moment przed udzieleniem przez Was odpowiedzi tzn. odświeżeniem strony w celu ich zobaczenia :stuck_out_tongue:

 

@mordesku  jak najbardziej mówi, ale właśnie zapisał się w mojej głowie jako negatywne zjawisko w programowaniu obiektowym.

 

@LonngerM  czyli kwestia doszukania się odpowiedniej nazwy… coś czułem, że można wybrać coś lepszego niż “Nauka” :wink:

 

Dzięki Wam za odpowiedzi, temat uznaje za wyczerpany. Chyba jednak to moje sceptyczne podejście skutecznie mnie blokowało w dojściu do rozwiązania “problemu”. Okazuje się, że w dalszym ciągu średnio wychodzi mi chłodne, analityczne podejście. Wdarło się nieco owego sceptyzmu, ale temat właśnie miał być próbą udowodnienia bądź zaprzeczenia temu.

 

Jeszcze raz dzięki :slight_smile:

 

EDIT: No i edit… nie doczytałem do końca dobrze Ciebie  LonngerM , więc dopisuję: jednak nie kwestia nazewnictwa, a rozbicia na jeszcze coś mniejszego. Czy jednak warto robić klasę “Nauka”, skoro jest ona bardzo ogólnym rzeczownikiem? Może skoro aplikacja opiera się właśnie na nauce, to taka klasa jest zbędna? i w ramach editu jeszcze: mordesku , fajnie że udzielasz się jako ktoś doświadczony - Twoje rady można uznać za pewne. Spytam na koniec: podałeś Singletona. Co o nim sądzisz? Można go zastosować w kodzie, czy jak jego przeciwnicy - absolutnie nie, bo przeczy programowaniu obiektowemu? 

Nie wiem z jakiego softu korzystasz do kodowania ale każde współczesne IDE ma bardzo fajne możliwości do nawigacji po klasach. Więc wyobraś sobie że masz 10000 plików i nie wszędzie są klasy. Jak chcesz znaleźć odpowiedni plik? Jak przyjmujesz że masz plik na klase to nie ma tego problemu.

 

EDIT:

A co jeśli Ci powiem że twoja aplikacja też może być klasą tylko dla opisania co robi nazywa się Nauka?

W tym pytaniu bardziej chodziło o to, czy w ogóle byłaby potrzebna taka klasa jak “Nauka”, skoro cała aplikacja opiera się na nauce i niczym innym. Nie wystarczy tylko “Litera” w tym przypadku? No bo rozrasta nam się struktura i być może niepotrzebnie… po co operować na “nauka.f.pokaż()” skoro można na “f.pokaż()”, albo konkretniej - po co doprowadzać do takiej konstrukcji, skoro jedynym zadaniem aplikacji i tak jest nauka?

 

EDIT: O, dobre pytanie zadałeś. Właśnie to miałem w głowie - czy np. oryginalną nazwę aplikacji (np. nie wiem, DziecixSuper) można traktować jako zamiennik nazwy dla klasy, zamiast tą nazwę traktować jako opisującą np. Nauka.

 

Na przykład po to, żeby zrobić testy jednostkowe na Mockach :slight_smile: Ogólnie mam wrażenie, że zadajesz pytanie w trochę złym kierunku. Szukasz konkretnych przykładów kurczowo trzymając się mojego jednego, ale nie tędy droga. W momencie projektowania systemu, jak już tworzymy klasy, należy zdecydować, czy dana rzecz kwalifikuje się do opakowania w klasę, czy nie. Ta umiejętność przychodzi z praktyką, po prostu w pewnym momencie programistyczna intuicja zaczyna podpowiadać Ci, że coś co opisujesz kwalifikuje się już do stworzenia z niej klasy. Potrzeba mocno abstrakcyjnego myślenia i sporo napisanego kodu za sobą, żeby dobrze to rozwiązać - a i tak znajdzie się pewnie jakiś architekt, który zrobi to lepiej albo trochę inaczej. Trzeba też wiedzieć w jaki sposób przekazuje się typy referencyjne, a w jaki sposób typy proste i co to nam implikuje. Dlatego nie zakładaj, że Pojemność zawsze należy przekazać przez typ prosty, a Pojemnik przez klasę. Jak będziesz miał jeden typ pojemników (którym jest np. baryłka ropy) to możesz go przedstawiać jako jednostka. Jest ustandaryzowana i przechowywanie w środku kolejnej wartości “Pojemność” mija się z celem. Lepiej trzymać ilość ropy w postaci ilości baryłek (a może zmienić koncepcję i przechowywać ilość litrów?). Z drugiej strony jak robisz oprogramowanie dla hurtowni czy fabryki paszy, to Pojemnik będący klasą (najlepiej abstrakcyjną) po której dziedziczą docelowe pojemniki jak butelka PET czy wiaderko 10-litrowe będzie bardzo dobrym rozwiązaniem. 

Podsumowując - nie licz na konkretne przykłady, bo dostaniesz je do konkretnych zastosowań. Spróbuj podejść do tematu na zasadzie “czy przechowywanie wartości X jako typu prostego wystarczy”. Ale tu też można się nadziać, bo zwykła “ilość” która mogłaby się wydawać oczywistą wartością zmiennoprzecinkową też może być podana w litrach albo w kilogramach, a może w sztukach? I nagle dopisując do kodu ilosc_kg, ilosc_litr robisz bajzel którego dałoby się uniknąć wprowadzając klasę Ilosc po której dziedziczyłyby klasy Waga, Pojemnosc i Sztuka. Każda pozwalająca na trochę inne działanie.

 

Klasy nazywaj jak chcesz ale ważne żeby z nazwy wynikało czym dana klasa się zajmuje. Np jeśli klasa jest widokiem listy liter to nazwa np. LetterListView.  Dzięki temu wiesz że to widok i tyle i nawet jeśli za 2 lata ktoś inny usiądzie to tego kodu to wyszuka sobie klasy ze słowem View i będzie miał wszystkie widoki jak na dłoni.

 

EDIT: jeśli klasa główna aplikacji nazywa się tak jak aplikacja (o ile to możliwe) też nic się nie stanie bo każdy się domysli. No chyba że appka się nazywa List albo Array :wink: