Programowanie obiektowe - koncepcyjnie

0

Mam pytanie odnośnie koncepcji programowania obiektowego. Wiem, jak to zrobić programistycznie dobrze, chodzi mi o same założenia obiektowości.

Jak z punktu widzenia programowania obiektowego (abstrahując od wydajności) powinno wyglądać rozwiązanie:
klasa "Obiekt" zawiera listę innych obiektów, które są jej składowymi typu "Składowa".
Czy w klasie "Obiekt" powinna być zawarta lista utworzonych obiektów "Składowa"?
Czy z punktu widzenia programowania obiektowego dopuszczalne jest trzymanie w klasie "Obiekt" tylko spisu obiektów "Składowa" i tworzenie ich (pojedynczych) gdy będą potrzebne?

Dzięki za pomoc.
Pozdrowienia

0

Jest to dopuszczalne. Np. tworzenie instancji dopiero przy pierwszej próbie użycia (http://en.wikipedia.org/wiki/Lazy_initialization) czy fabryka.

0

Obie te rzeczy są OK z punktu widzenia programowania obiektowego -- czemu miałyby nie być? Przechowywanie listy obiektów w innym obiekcie to normalka. Leniwą inicjalizację stosuje się rzadziej, ale jeśli jest potrzebna ze względów wydajnościowych, to jej łatwe zastosowanie umożliwia jedna z podstawowych cech programowania obiektowego: enkapsulacja. Ponieważ klasa zarządzająca składowymi udostępnia na zewnątrz tylko publiczne metody dostępu do tych składowych (enkapsulacja!), z punktu widzenia klas-klientów nie ma różnicy, czy składowe są tworzone na samym początku, czy dopiero wtedy, gdy będą potrzebne, tj. przy pierwszym odwołaniu do nich.

Dzięki enkapsulacji możesz dodać leniwą inicjalizację bez jakichkolwiek zmian w kodzie klienckim.

A wszelkie fabryki należą do kategorii wzorców projektowych zwanych wzorcami kreacyjnymi. Tworzenie obiektów na pewno jest... obiektowe!

0

Rzecz w tym, że nie chodzi mi o leniwą inicjalizację, to jest w porządku bo koniec końców mam obiekt w obiekcie.
Rozwiązanie, które moim zdaniem nie jest poprawne, to takie gdy te obiekty nie są tworzone, a jest np. wyświetlana ich lista (np. ich tytułów) bez tworzenia każdego z tych obiektów, a tylko za pomocą odpytania bazy danych.
Obiekt miałby być tworzony dopiero przy wybraniu go z listy, gdy będą potrzebne wszystkie jego dane.

Ze względów wydajnościowych takie rozwiązanie będzie wykorzystywane w naszym systemie, ale ja uważam, że nie jest to obiektowe, a inni uważają inaczej...

Dzięki za pomoc

0

@bbr:
Załóżmy, że masz tam obiekt 'lista' (klasy Lista), który może przechowywać obiekty klasy Element.

Czy chodzi o to, że masz takiego jak poniższy pseudokod?

// nie są tworzone elementy
lista.wyświetl_zestawienie();

// ale tu już tak:
lista.daj_element(numer_wybranego).daj_tytuł();

Od strony klienta ten kod jest moim zdaniem OK. Problem wystąpić może jednak w kodzie klas Lista/Element. Bo chodzi o to, że wewnątrz np. konstruktora klasy Element masz coś takiego:

class Element {
  ...
  Element(id) {
    wynik = baza_danych.wykonaj_zapytanie('SELECT * from Elementy WHERE id = ' + id);
    wiersz = wynik.daj_wiersz(0);
    this.tytuł = wiersz.daj_pole('tytuł');
  }

  String daj_tytuł() {
    return this.tytuł;
  }
}

Natomiast w funkcji Lista::wyświetl_zestawienie masz coś takiego:

class Lista {
...

  void wyświetl_zestawienie() {
    html_zestawienia = '';
    wynik = baza_danych.wykonaj_zapytanie('SELECT * from Elementy');
    for (wiersz in wynik) {
      html_zestawienia += '<h2>' + wiersz.daj_pole('tytuł') + '</h2>';
    }

    // a być może powinno być tak:
      // for (element in this.elementy) {
      //   html_zestawienia += '<h2>' + element.daj_tytuł() + '</h2>';      
      // }


    komponent.ustaw_html(html_zestawienia);
  }
}

Problemem jest to, że zapytanie wybierające Element jest do pewnego stopnia zdublowane. Klasa Lista ma swój kod zapytania, klasa Element swój. Jest to jakieś tam naruszenie zasady DRY i poniekąd hermetyzacji obiektów klasy Element. Zmiana w tabeli bazy danych Elementy może wpłynąć nie tylko na klasę Element, ale również Lista.

Czy to takie złe? Moim zdaniem, niekoniecznie. Jest dopuszczalne, by Lista -- szczególnie gdy w praktyce pełni funkcję zarządcy -- była stosunkowo ściśle związana z jej Elementami. W razie gdyby okazywało się to upierdliwe, zawsze można refaktoryzować. Tak całkiem "dla idei" to w tym momencie może nie mieć sensu, szczególnie jeśli tak jest faktycznie wygodniej.

Ale ostrzegam: w ten sposób rozumować można wtedy, gdy rzeczywiście projekt jest prowadzony tak, że często robicie refaktoryzacje i nie musicie się z tym kryć. Bo w wielu projektach na refaktoryzacje "nie ma czasu", a potem ciągną się one latami, bo komuś wygodniej było na początku olać jakąś zasadę, a po roku kod się zmienił i zaczęło to wszystkich wkurzać, ale refaktoryzacji już nie zrobili.

A jak chcesz lub faktycznie potrzebujesz zrobić to porządnie, to ZASTOSUJ leniwą inicjalizację. Co więcej, możesz tu zastosować wzorzec projektowy Pełnomocnik (ang. Proxy). Po zastosowaniu tego wzorca, klasa Lista tworzyłaby sobie na samym początku listę obiektów klasy PełnomocnikElementu. Ten pełnomocnik mógłby zainicjalizować niektóre ze swoich pól -- te, których pobranie z bazy nie jest kosztowne -- np. właśnie tytuł. W metodzie Lista::wyświetl_zestawienie() odnoszono by się właśnie do tych pól. A gdyby ktoś -- np. klient klasy Lista -- chciał się odnieść do pozostałych pól, to wtedy PełnomocnikElementu tworzyłby instancję prawdziwego Elementu.

Czy to ma tutaj bardzo duży sens? Niekoniecznie. Bo teraz z kolei klasa PełnomocnikElementu byłaby ściśle związana z Elementem. Fakt, byłoby to oczywiste, że te dwie klasy z definicji są powiązane, ale czy byłoby to o tyle jaśniejsze od faktu powiązania Elementu i Listy/Zarządcy, że gra byłaby warta zachodu?

PS. Nie jestem pewien, czy dobrze zrozumiałem o co w ogóle chodzi, więc może odpisałem nie na temat ;).

0

Od kiedy to szablon listy wyswietla i o zgrozo formatuje dane nt. elementu? Czy to aby na pewno jest oop?

0

@EgonOlsen:
Podaj proszę właściwe rozwiązanie, jeśli je znasz -- to będzie bardziej pomocne niż samo lakoniczne marudzenie ;). Co wg Ciebie powinno wyświetlać i formatować dane dotyczące elementu? I jak rozwiązać sytuację, którą ma @bbr? I dlaczego trzeba to zrobić?

0
EgonOlsen napisał(a)

Od kiedy to szablon listy wyswietla i o zgrozo formatuje dane nt. elementu? Czy to aby na pewno jest oop?

Wszystko zależy od tego jak bardzo dużej elastyczności potrzebujemy, obiektowe nie znaczy zawsze zgodne z MVC, a to chyba o to poszło.

Jak już czepiać się się void wyswietl_zestawienie(), to zawsze może to być metoda abstrakcyjna w klasie Lista, która powinna być abstrakcyjna zresztą.
Wtedy mamy: ListaHTML, ListaXML, co naturalnie daje nam fabrykę, a to już jest na pewno OOP.

Inny sposób to dodanie klasy ElelementWidok, która pobiera listę elementów i tam sobie już ją formatuje, to już rozwiązane bardziej zbliżone do rozbicia tego na wartswy i to dalej jak najbardziej OOP

0
GhostDog napisał(a)

Wszystko zależy od tego jak bardzo dużej elastyczności potrzebujemy, obiektowe nie znaczy zawsze zgodne z MVC, a to chyba o to poszło.

Podobno każa klasa powinna tylko jedną, konkretną funkcjonalność posiadać, jedno przeznaczenie.

0

Oj DRY tu już zostało wyciągnięte przez @bswierczynskiego. Zgadza się, programowanie to jednak nie sztuka tylko rzemiosło, najpierw niech jakiś komponent spełnia swoją funkcjonalność, jak tak to myśli się o refaktoringu, Ty tak od razu tworzysz maksymalne zgodne z SOLID klasy?

Eh pomyliłem pojęcia, jasne z SRP to nie jest zgodne.

Zresztą moja druga propozycja byłaby już zgodna z SRP.

0

Wszystko zależy od przeznaczenia, w niektórych sytuacjach faktycznie opłaca się od podstaw wszystko robić 'zgodnie ze sztuką'. Zwykle jest to kompromis.

0

Wrócę do tego, co napisałem wcześniej, bo tak akurat wyszło, że pisałem to po dosłownie 3 godzinach snu (dawno takiego maratonu nie miałem) i pewne rzeczy mi się... nie objawiły ;).

Nazwałem tam sobie tę klasę po prostu Lista, ale nie uznawałem tego za koniecznie czysty szablon listy, tj. odpowiednio zmodyfikowany kontener przechowujący tylko elementy. @bbr nie podał żadnych szczegółów i jak dla mnie to ta Lista mogłaby się tak naprawdę nazywać np. WidokListyFilmów (skoro @bbr wspomniał o tytułach). Wtedy jak najbardziej mogłaby tworzyć zestawienie. To, że zestawienie przygotowywane było na podstawie HTML-a też wziąłem sobie z powietrza. Tutaj jednak kwestią przynajmniej bardzo sporną byłoby to, czy WidokListyFilmów powinien formatować wpisy poszczególnych filmów, czy lepiej zrzucić to na barki samej klasy Film lub WidokFilmu (tego, co u mnie nazwałem sobie Element). Szczególnie opłacałoby się to, gdyby filmów było kilka rodzajów i każdy wyświetlany był inaczej.

W międzyczasie wtrącę, że MVC nie zawsze jest potrzebne i czasem tylko komplikuje sprawę. Niedawno uszczęśliwiłem się na siłę właśnie stosując MVC i teraz poważnie rozważam refaktoryzację likwidującą kontroler. Zwykle jednak faktycznie oddzielenie widoku i danych się bardzo opłaca.

IMO jednak dużo zależy od kontekstu i tego, co jest akurat wygodne. Powtórzę: to podejście uważam za OK tylko jeśli ktoś nie boi się refaktoryzować.

Wracając jednak do naszego przykładu... Tak, być może Lista to jest WidokListyFilmów i może zajmować się pewnym formatowaniem. Tyle że cała rzecz rozbija się o to, że wtedy nie powinna się zajmować kreacją Filmów. Albo to, albo to. Widok albo bebechy. (naturalnie, widok może przechowywać referencję do listy zajmującej się bebechami!)

Ponieważ sporo robię w branży webowej, to dość często natrafiam na przypadki, gdy trzeba w dość skomplikowany sposób sformatować jakieś dane, choćby do postaci HTML-a. Powstają wtedy takie wielkie, skomplikowane metody poprzetykane zmiennymi lokalnymi. Nieraz trudno wtedy nawet rozsądnie wydzielić metody. Rozwiązaniem może być kilka mniejszych refaktoryzacji, np. wyrażenia warunkowe można zastąpić polimorfizmem; można też użyć tzw. obiektów pustych. Ale w ostateczności można stworzyć obiekt reprezentujący metodę. Ponieważ zmienne lokalne z oryginalnej funkcji są wtedy składowymi tego obiektu, można o wiele łatwiej wydzielić odpowiednie metody.

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