SlideShare uma empresa Scribd logo
1 de 50
Pułapki programowania obiektowego
                               Wersja 2, 29 marca 2011


       Adam Sawicki – www.asawicki.info
Agenda
 Kim jestem
 „Fanatyzm obiektowy”
  • Projektowanie obiektowe
  • Data-Oriented Design
 „Mania wrapowania”
  • Wrapper – na 3 przykładach
 Generalizacja
 Dziedziczenie
 Enkapsulacja
 Wzorce projektowe
  • Singleton
 Podsumowanie
  • DOD raz jeszcze
 Pytania
 2
Adam Sawicki
 adam@asawicki.info
 www.asawicki.info
 @Reg__
 Programista
  • Politechnika Częstochowska – Informatyka
  • Praktyki w siedzibie Microsoft w Redmond, USA
  • AquaFish 2 (gra wydana przez PLAY Publishing)
  • Metropolis Software (They – shooter na PC i X360)
  • Cyfrowy Polsat S.A.


 3
Programowanie obiektowe
 Teoria: program składa się z klas
  • Łączą dane (pola) i kod (metody)
  • Modelują byty z dziedziny problemu
  • Są od siebie niezależne
  • Nadają się do ponownego użycia
 Założenia
  • Enkapsulacja (hermetyzacja) – udostępnienie interfejsu,
    ukrycie implementacji
  • Polimorfizm i dziedziczenie – używanie obiektu
    abstrakcyjnego bez rozróżniania konkretnych typów




 4
Fanatyzm obiektowy




5
Projektowanie obiektowe

 OOP można rozważad na różnych płaszczyznach:
  • Ideologiczna – zasady programowania obiektowego i
    abstrakcyjne znaczenie jego założeo,
  • Techniczna – jak się używa programowania obiektowego w
    danym języku i jak ono działa,
  • Praktyczna – jak używad go w programach dla swojego
    pożytku, a nie tylko dla zasady.


 Tak jak ze wszystkim, nadmierna radykalnośd i ślepe
     poparcie bez myślenia rodzi fanatyzm, a to jest złe.

 6
Przykład

Gra w kółko i krzyżyk – podejście obiektowe

1. Znajdujemy klasy identyfikując rzeczowniki




 7
Przykład
2. Projektujemy powiązania między klasami




 8
Przykład
3. Implementujemy klasy

 Co umieścid w tych klasach, żeby nie były puste???
 W której klasie umieścid potrzebne dane i kod???

Analysis Paralysis – poszukiwanie idealnego rozwiązania




 9
Przykład
3. Implementujemy klasy

 Co umieścid w tych klasach, żeby nie były puste???
 W której klasie umieścid potrzebne dane i kod???

Analysis Paralysis – poszukiwanie idealnego rozwiązania




 10
Data-Oriented Design
 Alternatywne podejście:
  Data-Oriented Design (DOD)
                                            enum POLE {
  • Myśl o strukturze danych w pamięci        POLE_PUSTE,
                                              POLE_KOLKO,
                                              POLE_KRZYZYK,
 Pytanie: Jakie dane powinna               };
                                            POLE Plansza[3][3];
  przechowywad gra?                         int CzyjRuch;
  • Tablica 3 x 3 pól
  • Każde pole jest w jednym ze stanów: puste,
    kółko, krzyżyk
  • Czyj jest teraz ruch: gracza 1 lub 2


 11
DOD – przykład

 Następnie pomyśl, co po kolei kod powinien robid z tymi
  danymi
  • Wykonanie ruchu przez gracza
  • Sprawdzenie, czy ruch jest prawidłowy
  • Wstawienie symbolu do tablicy
  • Sprawdzenie, czy gracz wygrał
  • Rozpoczęcie tury przeciwnego gracza




 12
DOD – przykład

 Następnie pomyśl, co po kolei kod powinien robid z tymi
  danymi
  • Wykonanie ruchu przez gracza
  • Sprawdzenie, czy ruch jest prawidłowy
  • Wstawienie symbolu do tablicy
  • Sprawdzenie, czy gracz wygrał
  • Rozpoczęcie tury przeciwnego gracza




 13
Projektowanie obiektowe – wnioski
 Podejście obiektowe nie jest idealne
 Można nawet stwierdzid, że nie spełniło swoich
  założeo
  • Modelowanie rzeczywistych obiektów? Co modeluje
    manager, helper, listener, obeserver, locker i inne -ery?
  • Źle użyte działa przeciwko wydajności, jak też prostocie i
    czytelności kodu
 Warto je stosowad, ale z rozwagą
  • Znaj i doceniaj inne możliwości
  • Nie szukaj gotowych „klocków” uciekając od myślenia

 14
Mania wrapowania




15
Mania wrapowania


 Wielu programistów czuje potrzebę, żeby naukę czy
  wykorzystanie w swoim programie jakiejś biblioteki
  zacząd od napisania własnej otoczki (wrappera)
  zamykającego jej interfejs.
  • Robią to niemal odruchowo i bez zastanowienia, czy to
      potrzebne.


 Jakie argumenty za tym stoją?


 16
Przykład 1: FMOD
Biblioteka dźwiękowa FMOD – wczytanie dźwięku WAV

FMOD::Sound *Sound;
FmodSystem->createSound(
  WavFileName,
  FMOD_LOOP_OFF | FMOD_2D | MOD_SOFTWARE,
  0,
  &Sound);


 Jimmy jest przerażony, chce uprościd interfejs
  • Te parametry są niepotrzebne
  • Chcę mied mniej parametrów
  • Nie chcę sięgad do dokumentacji FMOD – wolę własny
    interfejs
 17
Przykład 1: FMOD
Jimmy pisze własną klasę dźwięku, która zamyka FMOD::Sound

class CSound {
private:
  FMOD::Sound *Sound;
public:
  void Load(const char *WavFileName);
};

void CSound::Load(const char *WavFileName) {
  FmodSystem->createSound(
    WavFileName,
    FMOD_LOOP_OFF | FMOD_2D | FMOD_SOFTWARE,
    0,
    &Sound);
}

 18
Przykład 1: FMOD
 Jimmy potrzebuje możliwości zapętlenia dźwięku
  • Musi dodad parametr Loop
  • Musi zamieniad parametr ze swojej postaci do postaci
    FMOD

void CSound::Load(const char *WavFileName, bool Loop) {
    FmodSystem->createSound(
         WavFileName,
         (Loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) |
         FMOD_2D | FMOD_SOFTWARE,
         0,
         &Sound);
}

    19
Przykład 1: FMOD
 Jimmy odkrywa odtwarzanie w dwie strony i też chce to
  umożliwid
  • Musi zdefiniowad własny enum LOOP_MODE
  • Musi zamieniad swój enum na ten z FMOD
enum LOOP_MODE {
  LOOP_MODE_NONE, LOOP_MODE_NORMAL, LOOP_MODE_BIDI };

void CSound::Load(const char *WavFileName, LOOP_MODE LoopMode) {
  unsigned LoopFlag = 0;
  switch (LoopMode) {
  case LOOP_MODE_NONE:   LoopFlag = FMOD_LOOP_OFF;    break;
  case LOOP_MODE_NORMAL: LoopFlag = FMOD_LOOP_NORMAL; break;
  case LOOP_MODE_BIDI:   LoopFlag = FMOD_LOOP_BIDI;   break;
  }
  FmodSystem->createSound(WavFileName,
    LoopFlag | FMOD_2D | FMOD_SOFTWARE, 0, &Sound);
}
 20
Przykład 1: FMOD


 Wreszcie duża częśd możliwości FMOD jest
  przepisana do własnego interfejsu
 Trzeba zrobid do niego własną dokumentację
 Zamiast flag FMOD trzeba pamiętad własne


 Jaka to różnica?
 Warto było??



 21
Przykład 2: DirectX

 Zrobię wrapper na DirectX, to potem
  będę mógł wymienić renderer na
  OpenGL bez modyfikowania kodu gry.




 22
Przykład 2: DirectX

 Zrobię wrapper na DirectX, to potem
  będę mógł wymienić renderer na
  OpenGL bez modyfikowania kodu gry.




 23
Przykład 2: DirectX

 Jakie jest prawdopodobieostwo, że
  • P1 – skooczysz kod swojej gry/silnika
  • P2 – będziesz potrzebował mied drugą implementację renderera
  • P3 – uda się zaimplementowad renderer w OpenGL bez większych zmian
      w jego interfejsie


 P1 ∙ P2 ∙ P3 ≈ 0


 Znasz już dobrze zarówno DirectX, jak i OpenGL?
  • Jeśli nie → P3 = 0


 24
Przykład 3: WinAPI i MFC
 Ktoś w Microsoft:
  WinAPI jest niefajne, bo jest strukturalne. Napiszmy
  obiektową otoczkę.

// WinAPI                             // MFC
HDC hdc = GetWindowDC(hwnd);          CDC *dc = wnd->GetDC();
MoveToEx(hdc, 0, 0, NULL);            dc->MoveTo(0, 0);
LineTo(hdc, 100, 100);                dc->LineTo(100, 100);
ReleaseDC(hdc);                       wnd->ReleaseDC(dc);


 Czy to zrobiło aż tak dużą różnicę? Warto było???
 Efekt?
  • MFC to tylko cienka otoczka na WinAPI. Nikt go nie lubi.

 25
Wrapper – wnioski
 Jaka wobec tego jest alternatywa?
  • W porę zastanowid się, czy nie wystarczy używad w swoim
    programie interfejsu danej biblioteki bezpośrednio.


 Warto napisad wrapper, kiedy zapewnia przynajmniej
  jedno z poniższych:
  • Znacząco upraszcza interfejs
  • Dodaje naprawdę dużo nowej funkcjonalności
  • Wprowadza dużo wyższy poziom abstrakcji
  • Faktycznie posiada kilka różnych implementacji


 26
Warstwy

The object-oriented version of
"Spaghetti code" is, of course,
"Lasagna code". (Too many
layers.) – Roberto Waltman

All problems in computer
science can be solved by
another level of indirection…
Except for the problem of too
many layers of indirection. –
David Wheeler

 27
Generalizacja




28
Generalizacja – błędne koło

              1.Chcemy, żeby każdy pisany kod był jak
                najbardziej ogólny i uniwersalny,

                aby nie trzeba było wprowadzad w
 3        1     nim zmian, kiedy zmienią się
     :(         wymagania.

     2



29
Generalizacja – błędne koło

              2. Unikamy wprowadzania zmian w
                 kodzie,

                 bo każda, nawet miała zmiana,
 3        1      wymaga bardzo dużo pracy.
     :(

     2



30
Generalizacja – błędne koło

              3. Każda zmiana w kodzie wymaga
                 bardzo dużo pracy,

                 ponieważ wszystko piszemy jak
 3        1      najbardziej ogólnie i uniwersalnie.
     :(

     2



31
Generalizacja – błędne koło


 Rozwiązanie
  • Pisz w sposób prosty, bezpośrednio to co ma byd napisane i
    nie więcej.
  • Nie wyprzedzaj przyszłości. Kiedy pojawi się taka potrzeba,
    wtedy przerobisz kod na bardziej ogólny.




 32
Dziedziczenie




33
Dziedziczenie


 Teoria
  • Modeluje relację „jest rodzajem”
  • Umożliwia abstrakcję i polimorfizm
  • Dzieli obszerny kod na fragmenty mające częśd
    wspólną




 34
Dziedziczenie

 Wady
  • Kod jednej funkcjonalności jest rozproszony między
    wiele klas i plików
  • Tworzy ścisłe powiązania między klasami
  • Zmiana w jednym miejscu powoduje nieoczekiwane
    efekty w innych miejscach
  • Często trudno zaprojektowad dobry układ klas, ich
    pól, metod i zależności między nimi


 35
Dziedziczenie

 Przykład: okrąg i elipsa
  • Matematyk mówi: okrąg jest rodzajem elipsy,
    która ma dwa promienie równe!
  • Programista obiektowy mówi: elipsa jest
    rodzajem okręgu, który dodaje drugi promieo!


                          ??




 36
Dziedziczenie

Rozwiązanie:
 Myśl o danych
 Rozważ inne możliwości
  •   enum Type
  •   Kompozycja (agregacja)
  •   Podejście sterowane danymi (data-driven)
  •   Architektura komponentowa
 Stosuj dziedziczenie jak najrzadziej, nie jak najczęściej
 Patrz na dziedziczenie jak na mechanizm językowy, nie jak na
  ideę do modelowania każdej relacji „jest rodzajem”


 37
Enkapsulacja




38
Enkapsulacja
 Teoria
  • Udostępnia interfejs, ukrywa szczegóły implementacji
  • Pozwala zmienid implementację bez zmian w
    interfejsie i w innych częściach kodu


         class Foo {
         public:
           int GetValue() { return Value; }
           void SetValue(int v) { Value = v; }
         private:
           int Value;
         };


 39
Enkapsulacja
 Problem
  • Czym się różni getter + setter od uczynienia pola publicznym?
  • Czy naprawdę wierzysz, że możesz zmienid implementację tego
    pola (zmienid jego typ, wyliczad za każdym razem, pobierad z
    innego obiektu) bez zmian w interfejsie i w innych częściach
    kodu?

class Foo {
public:
  int GetValue() { return (int)Value; }
  void SetValue(int v) { Value = (float)v; }
private:
  float Value;
};


 40
Enkapsulacja
Rozwiązanie:
 Nie bój się używad struktur z publicznymi polami tam,
  gdzie chodzi o paczkę danych
 Rozważ inne metody dostępu do danych, np. parametry
  przekazywane podczas inicjalizacji, a potem dostępne
  tylko do odczytu
  • Descriptor, immutability struct FooDesc {
                             int Value;
                           };

                           class Foo {
                           public:
                             Foo(const FooDesc &params);
                             void GetParams(FooDesc &out_params);
                           };

 41
Wzorce projektowe:
              Singleton




42
Singleton
 Teoria
  • Tylko jedna instancja klasy
  • Dostępna globalnie
  • Inicjalizowana przy pierwszym użyciu
                        SoundSystem::GetInstance().PlaySound("Boom!");
 Wady
  • Brak jawnej kontroli nad kolejnością inicjalizacji i finalizacji
  • Narzut na każde wywołanie
  • Problem z bezpieczeostwem wątkowym
 Czy na pewno system dźwiękowy powinien się
  inicjalizowad w momencie, kiedy chcemy odegrad
  pierwszy dźwięk?

 43
Singleton

Rozwiązanie:
 Jawnie tworzyd i niszczyd obiekty globalne w określonym
  miejscu programu
                        g_SoundSystem = new SoundSystem();




Every time you make a singleton, God kills a startup.
Two if you think you've made it thread-safe. – popularna
sentencja z Twittera

 44
Podsumowanie




45
OOP kontra DOD


:(




:)


     46
OOP kontra DOD

          class BaseObject {                                        ??
          private:                                 Render
           float3 position;                            Render

:(        public:
                                                       Render

                                                       Render
           virtual void Render() = 0;
          };


          struct RenderBatch {
           float3 position;             Frustum         Sort /
                                         Culling        Group    Render
           Mesh *mesh;
           Material *material;
:)        };




     47
Podsumowanie

 DOD służy raczej do optymalizacji wydajności
  • Kod bardziej przyjazny dla pamięci cache
  • Kod łatwiej się zrównolegla

 Jednak wielu odrzuca „przedwczesną optymalizację”
  nadinterpretując znany cytat Donalda Knutha

 Dlatego tutaj pokazałem, jak krytyczne spojrzenie na OOP
  może pomóc także w uproszczeniu kodu.


 48
Bibliografia



 Fanatyzm obiektowy, Adam Sawicki
  • http://www.asawicki.info/Download/Misc/Fanatyzm_obiektowy.html


 Materiały o Data-Oriented Design
  • http://www.asawicki.info/news_1422.html (lista linków)




 49
Pytania?




50

Mais conteúdo relacionado

Semelhante a Pułapki programowania obiektowego

Tworzenie wydajnego kodu c++ w podejściu zorientowanym na dane
Tworzenie wydajnego kodu c++ w podejściu zorientowanym na daneTworzenie wydajnego kodu c++ w podejściu zorientowanym na dane
Tworzenie wydajnego kodu c++ w podejściu zorientowanym na daneAdam Sawicki
 
[PL] Jak programować aby nie zwariować?
[PL] Jak programować aby nie zwariować?[PL] Jak programować aby nie zwariować?
[PL] Jak programować aby nie zwariować?Jakub Marchwicki
 
Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Jakub Marchwicki
 
Patterns for organic architecture
Patterns for organic architecturePatterns for organic architecture
Patterns for organic architectureJaroslaw Palka
 
Praktyczne code reviews - PHPConPl
Praktyczne code reviews - PHPConPlPraktyczne code reviews - PHPConPl
Praktyczne code reviews - PHPConPlSebastian Marek
 
Czym jest złożoność ?
Czym jest złożoność ?Czym jest złożoność ?
Czym jest złożoność ?GOG.com dev team
 
Złam zasady i stwórz wydajny stos IP przy użyciu DPDK
Złam zasady i stwórz wydajny stos IP przy użyciu DPDKZłam zasady i stwórz wydajny stos IP przy użyciu DPDK
Złam zasady i stwórz wydajny stos IP przy użyciu DPDKSemihalf
 
Dependency Injection w Androidzie
Dependency Injection w AndroidzieDependency Injection w Androidzie
Dependency Injection w AndroidzieThe Software House
 
Open Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatowOpen Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatow3camp
 
Python wspolbieznie final
Python wspolbieznie finalPython wspolbieznie final
Python wspolbieznie finalPyDataBydgoszcz
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JSDawid Rusnak
 
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)Waldek Kot
 
Budowanie aplikacji PHP bez użycia frameworków
Budowanie aplikacji PHP bez użycia frameworkówBudowanie aplikacji PHP bez użycia frameworków
Budowanie aplikacji PHP bez użycia frameworkówMichal Lukaszewski
 
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...CodiLime
 
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...MarcinStachniuk
 
Testowanie bezpieczenstwa aplikacji mobilnych
Testowanie bezpieczenstwa aplikacji mobilnychTestowanie bezpieczenstwa aplikacji mobilnych
Testowanie bezpieczenstwa aplikacji mobilnychSecuRing
 
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...Wojciech Sznapka
 

Semelhante a Pułapki programowania obiektowego (20)

Tworzenie wydajnego kodu c++ w podejściu zorientowanym na dane
Tworzenie wydajnego kodu c++ w podejściu zorientowanym na daneTworzenie wydajnego kodu c++ w podejściu zorientowanym na dane
Tworzenie wydajnego kodu c++ w podejściu zorientowanym na dane
 
[PL] Jak programować aby nie zwariować?
[PL] Jak programować aby nie zwariować?[PL] Jak programować aby nie zwariować?
[PL] Jak programować aby nie zwariować?
 
Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)Design principles 4 hackers - tech3camp (28142014)
Design principles 4 hackers - tech3camp (28142014)
 
Oss w software house
Oss w software houseOss w software house
Oss w software house
 
Patterns for organic architecture
Patterns for organic architecturePatterns for organic architecture
Patterns for organic architecture
 
Praktyczne code reviews - PHPConPl
Praktyczne code reviews - PHPConPlPraktyczne code reviews - PHPConPl
Praktyczne code reviews - PHPConPl
 
Czym jest złożoność ?
Czym jest złożoność ?Czym jest złożoność ?
Czym jest złożoność ?
 
Złam zasady i stwórz wydajny stos IP przy użyciu DPDK
Złam zasady i stwórz wydajny stos IP przy użyciu DPDKZłam zasady i stwórz wydajny stos IP przy użyciu DPDK
Złam zasady i stwórz wydajny stos IP przy użyciu DPDK
 
scenariusz-5-poszlaki
scenariusz-5-poszlakiscenariusz-5-poszlaki
scenariusz-5-poszlaki
 
Dependency Injection w Androidzie
Dependency Injection w AndroidzieDependency Injection w Androidzie
Dependency Injection w Androidzie
 
Open Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatowOpen Xml Translator - czyli o oswajaniu formatow
Open Xml Translator - czyli o oswajaniu formatow
 
Python wspolbieznie final
Python wspolbieznie finalPython wspolbieznie final
Python wspolbieznie final
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JS
 
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
INVOKEDYNAMIC - bardziej dynamiczna JVM (Confitura 2012)
 
Budowanie aplikacji PHP bez użycia frameworków
Budowanie aplikacji PHP bez użycia frameworkówBudowanie aplikacji PHP bez użycia frameworków
Budowanie aplikacji PHP bez użycia frameworków
 
Refaktoryzacja
RefaktoryzacjaRefaktoryzacja
Refaktoryzacja
 
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...
CodiLime Tech Talk - Michał Cłapiński, Mateusz Jabłoński: Debugging faultily ...
 
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
Poznaj lepiej swoje srodowisko programistyczne i zwieksz swoja produktywnosc ...
 
Testowanie bezpieczenstwa aplikacji mobilnych
Testowanie bezpieczenstwa aplikacji mobilnychTestowanie bezpieczenstwa aplikacji mobilnych
Testowanie bezpieczenstwa aplikacji mobilnych
 
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...
Łebski Development czyli kiedy i dlaczego tworzyć oprogramowanie pod klucz i ...
 

Pułapki programowania obiektowego

  • 1. Pułapki programowania obiektowego Wersja 2, 29 marca 2011 Adam Sawicki – www.asawicki.info
  • 2. Agenda  Kim jestem  „Fanatyzm obiektowy” • Projektowanie obiektowe • Data-Oriented Design  „Mania wrapowania” • Wrapper – na 3 przykładach  Generalizacja  Dziedziczenie  Enkapsulacja  Wzorce projektowe • Singleton  Podsumowanie • DOD raz jeszcze  Pytania 2
  • 3. Adam Sawicki  adam@asawicki.info  www.asawicki.info  @Reg__  Programista • Politechnika Częstochowska – Informatyka • Praktyki w siedzibie Microsoft w Redmond, USA • AquaFish 2 (gra wydana przez PLAY Publishing) • Metropolis Software (They – shooter na PC i X360) • Cyfrowy Polsat S.A. 3
  • 4. Programowanie obiektowe  Teoria: program składa się z klas • Łączą dane (pola) i kod (metody) • Modelują byty z dziedziny problemu • Są od siebie niezależne • Nadają się do ponownego użycia  Założenia • Enkapsulacja (hermetyzacja) – udostępnienie interfejsu, ukrycie implementacji • Polimorfizm i dziedziczenie – używanie obiektu abstrakcyjnego bez rozróżniania konkretnych typów 4
  • 6. Projektowanie obiektowe  OOP można rozważad na różnych płaszczyznach: • Ideologiczna – zasady programowania obiektowego i abstrakcyjne znaczenie jego założeo, • Techniczna – jak się używa programowania obiektowego w danym języku i jak ono działa, • Praktyczna – jak używad go w programach dla swojego pożytku, a nie tylko dla zasady.  Tak jak ze wszystkim, nadmierna radykalnośd i ślepe poparcie bez myślenia rodzi fanatyzm, a to jest złe. 6
  • 7. Przykład Gra w kółko i krzyżyk – podejście obiektowe 1. Znajdujemy klasy identyfikując rzeczowniki 7
  • 9. Przykład 3. Implementujemy klasy  Co umieścid w tych klasach, żeby nie były puste???  W której klasie umieścid potrzebne dane i kod??? Analysis Paralysis – poszukiwanie idealnego rozwiązania 9
  • 10. Przykład 3. Implementujemy klasy  Co umieścid w tych klasach, żeby nie były puste???  W której klasie umieścid potrzebne dane i kod??? Analysis Paralysis – poszukiwanie idealnego rozwiązania 10
  • 11. Data-Oriented Design  Alternatywne podejście: Data-Oriented Design (DOD) enum POLE { • Myśl o strukturze danych w pamięci POLE_PUSTE, POLE_KOLKO, POLE_KRZYZYK,  Pytanie: Jakie dane powinna }; POLE Plansza[3][3]; przechowywad gra? int CzyjRuch; • Tablica 3 x 3 pól • Każde pole jest w jednym ze stanów: puste, kółko, krzyżyk • Czyj jest teraz ruch: gracza 1 lub 2 11
  • 12. DOD – przykład  Następnie pomyśl, co po kolei kod powinien robid z tymi danymi • Wykonanie ruchu przez gracza • Sprawdzenie, czy ruch jest prawidłowy • Wstawienie symbolu do tablicy • Sprawdzenie, czy gracz wygrał • Rozpoczęcie tury przeciwnego gracza 12
  • 13. DOD – przykład  Następnie pomyśl, co po kolei kod powinien robid z tymi danymi • Wykonanie ruchu przez gracza • Sprawdzenie, czy ruch jest prawidłowy • Wstawienie symbolu do tablicy • Sprawdzenie, czy gracz wygrał • Rozpoczęcie tury przeciwnego gracza 13
  • 14. Projektowanie obiektowe – wnioski  Podejście obiektowe nie jest idealne  Można nawet stwierdzid, że nie spełniło swoich założeo • Modelowanie rzeczywistych obiektów? Co modeluje manager, helper, listener, obeserver, locker i inne -ery? • Źle użyte działa przeciwko wydajności, jak też prostocie i czytelności kodu  Warto je stosowad, ale z rozwagą • Znaj i doceniaj inne możliwości • Nie szukaj gotowych „klocków” uciekając od myślenia 14
  • 16. Mania wrapowania  Wielu programistów czuje potrzebę, żeby naukę czy wykorzystanie w swoim programie jakiejś biblioteki zacząd od napisania własnej otoczki (wrappera) zamykającego jej interfejs. • Robią to niemal odruchowo i bez zastanowienia, czy to potrzebne.  Jakie argumenty za tym stoją? 16
  • 17. Przykład 1: FMOD Biblioteka dźwiękowa FMOD – wczytanie dźwięku WAV FMOD::Sound *Sound; FmodSystem->createSound( WavFileName, FMOD_LOOP_OFF | FMOD_2D | MOD_SOFTWARE, 0, &Sound);  Jimmy jest przerażony, chce uprościd interfejs • Te parametry są niepotrzebne • Chcę mied mniej parametrów • Nie chcę sięgad do dokumentacji FMOD – wolę własny interfejs 17
  • 18. Przykład 1: FMOD Jimmy pisze własną klasę dźwięku, która zamyka FMOD::Sound class CSound { private: FMOD::Sound *Sound; public: void Load(const char *WavFileName); }; void CSound::Load(const char *WavFileName) { FmodSystem->createSound( WavFileName, FMOD_LOOP_OFF | FMOD_2D | FMOD_SOFTWARE, 0, &Sound); } 18
  • 19. Przykład 1: FMOD  Jimmy potrzebuje możliwości zapętlenia dźwięku • Musi dodad parametr Loop • Musi zamieniad parametr ze swojej postaci do postaci FMOD void CSound::Load(const char *WavFileName, bool Loop) { FmodSystem->createSound( WavFileName, (Loop ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF) | FMOD_2D | FMOD_SOFTWARE, 0, &Sound); } 19
  • 20. Przykład 1: FMOD  Jimmy odkrywa odtwarzanie w dwie strony i też chce to umożliwid • Musi zdefiniowad własny enum LOOP_MODE • Musi zamieniad swój enum na ten z FMOD enum LOOP_MODE { LOOP_MODE_NONE, LOOP_MODE_NORMAL, LOOP_MODE_BIDI }; void CSound::Load(const char *WavFileName, LOOP_MODE LoopMode) { unsigned LoopFlag = 0; switch (LoopMode) { case LOOP_MODE_NONE: LoopFlag = FMOD_LOOP_OFF; break; case LOOP_MODE_NORMAL: LoopFlag = FMOD_LOOP_NORMAL; break; case LOOP_MODE_BIDI: LoopFlag = FMOD_LOOP_BIDI; break; } FmodSystem->createSound(WavFileName, LoopFlag | FMOD_2D | FMOD_SOFTWARE, 0, &Sound); } 20
  • 21. Przykład 1: FMOD  Wreszcie duża częśd możliwości FMOD jest przepisana do własnego interfejsu  Trzeba zrobid do niego własną dokumentację  Zamiast flag FMOD trzeba pamiętad własne  Jaka to różnica?  Warto było?? 21
  • 22. Przykład 2: DirectX  Zrobię wrapper na DirectX, to potem będę mógł wymienić renderer na OpenGL bez modyfikowania kodu gry. 22
  • 23. Przykład 2: DirectX  Zrobię wrapper na DirectX, to potem będę mógł wymienić renderer na OpenGL bez modyfikowania kodu gry. 23
  • 24. Przykład 2: DirectX  Jakie jest prawdopodobieostwo, że • P1 – skooczysz kod swojej gry/silnika • P2 – będziesz potrzebował mied drugą implementację renderera • P3 – uda się zaimplementowad renderer w OpenGL bez większych zmian w jego interfejsie  P1 ∙ P2 ∙ P3 ≈ 0  Znasz już dobrze zarówno DirectX, jak i OpenGL? • Jeśli nie → P3 = 0 24
  • 25. Przykład 3: WinAPI i MFC  Ktoś w Microsoft: WinAPI jest niefajne, bo jest strukturalne. Napiszmy obiektową otoczkę. // WinAPI // MFC HDC hdc = GetWindowDC(hwnd); CDC *dc = wnd->GetDC(); MoveToEx(hdc, 0, 0, NULL); dc->MoveTo(0, 0); LineTo(hdc, 100, 100); dc->LineTo(100, 100); ReleaseDC(hdc); wnd->ReleaseDC(dc);  Czy to zrobiło aż tak dużą różnicę? Warto było???  Efekt? • MFC to tylko cienka otoczka na WinAPI. Nikt go nie lubi. 25
  • 26. Wrapper – wnioski  Jaka wobec tego jest alternatywa? • W porę zastanowid się, czy nie wystarczy używad w swoim programie interfejsu danej biblioteki bezpośrednio.  Warto napisad wrapper, kiedy zapewnia przynajmniej jedno z poniższych: • Znacząco upraszcza interfejs • Dodaje naprawdę dużo nowej funkcjonalności • Wprowadza dużo wyższy poziom abstrakcji • Faktycznie posiada kilka różnych implementacji 26
  • 27. Warstwy The object-oriented version of "Spaghetti code" is, of course, "Lasagna code". (Too many layers.) – Roberto Waltman All problems in computer science can be solved by another level of indirection… Except for the problem of too many layers of indirection. – David Wheeler 27
  • 29. Generalizacja – błędne koło 1.Chcemy, żeby każdy pisany kod był jak najbardziej ogólny i uniwersalny, aby nie trzeba było wprowadzad w 3 1 nim zmian, kiedy zmienią się :( wymagania. 2 29
  • 30. Generalizacja – błędne koło 2. Unikamy wprowadzania zmian w kodzie, bo każda, nawet miała zmiana, 3 1 wymaga bardzo dużo pracy. :( 2 30
  • 31. Generalizacja – błędne koło 3. Każda zmiana w kodzie wymaga bardzo dużo pracy, ponieważ wszystko piszemy jak 3 1 najbardziej ogólnie i uniwersalnie. :( 2 31
  • 32. Generalizacja – błędne koło  Rozwiązanie • Pisz w sposób prosty, bezpośrednio to co ma byd napisane i nie więcej. • Nie wyprzedzaj przyszłości. Kiedy pojawi się taka potrzeba, wtedy przerobisz kod na bardziej ogólny. 32
  • 34. Dziedziczenie  Teoria • Modeluje relację „jest rodzajem” • Umożliwia abstrakcję i polimorfizm • Dzieli obszerny kod na fragmenty mające częśd wspólną 34
  • 35. Dziedziczenie  Wady • Kod jednej funkcjonalności jest rozproszony między wiele klas i plików • Tworzy ścisłe powiązania między klasami • Zmiana w jednym miejscu powoduje nieoczekiwane efekty w innych miejscach • Często trudno zaprojektowad dobry układ klas, ich pól, metod i zależności między nimi 35
  • 36. Dziedziczenie  Przykład: okrąg i elipsa • Matematyk mówi: okrąg jest rodzajem elipsy, która ma dwa promienie równe! • Programista obiektowy mówi: elipsa jest rodzajem okręgu, który dodaje drugi promieo! ?? 36
  • 37. Dziedziczenie Rozwiązanie:  Myśl o danych  Rozważ inne możliwości • enum Type • Kompozycja (agregacja) • Podejście sterowane danymi (data-driven) • Architektura komponentowa  Stosuj dziedziczenie jak najrzadziej, nie jak najczęściej  Patrz na dziedziczenie jak na mechanizm językowy, nie jak na ideę do modelowania każdej relacji „jest rodzajem” 37
  • 39. Enkapsulacja  Teoria • Udostępnia interfejs, ukrywa szczegóły implementacji • Pozwala zmienid implementację bez zmian w interfejsie i w innych częściach kodu class Foo { public: int GetValue() { return Value; } void SetValue(int v) { Value = v; } private: int Value; }; 39
  • 40. Enkapsulacja  Problem • Czym się różni getter + setter od uczynienia pola publicznym? • Czy naprawdę wierzysz, że możesz zmienid implementację tego pola (zmienid jego typ, wyliczad za każdym razem, pobierad z innego obiektu) bez zmian w interfejsie i w innych częściach kodu? class Foo { public: int GetValue() { return (int)Value; } void SetValue(int v) { Value = (float)v; } private: float Value; }; 40
  • 41. Enkapsulacja Rozwiązanie:  Nie bój się używad struktur z publicznymi polami tam, gdzie chodzi o paczkę danych  Rozważ inne metody dostępu do danych, np. parametry przekazywane podczas inicjalizacji, a potem dostępne tylko do odczytu • Descriptor, immutability struct FooDesc { int Value; }; class Foo { public: Foo(const FooDesc &params); void GetParams(FooDesc &out_params); }; 41
  • 42. Wzorce projektowe: Singleton 42
  • 43. Singleton  Teoria • Tylko jedna instancja klasy • Dostępna globalnie • Inicjalizowana przy pierwszym użyciu SoundSystem::GetInstance().PlaySound("Boom!");  Wady • Brak jawnej kontroli nad kolejnością inicjalizacji i finalizacji • Narzut na każde wywołanie • Problem z bezpieczeostwem wątkowym  Czy na pewno system dźwiękowy powinien się inicjalizowad w momencie, kiedy chcemy odegrad pierwszy dźwięk? 43
  • 44. Singleton Rozwiązanie:  Jawnie tworzyd i niszczyd obiekty globalne w określonym miejscu programu g_SoundSystem = new SoundSystem(); Every time you make a singleton, God kills a startup. Two if you think you've made it thread-safe. – popularna sentencja z Twittera 44
  • 47. OOP kontra DOD class BaseObject { ?? private: Render float3 position; Render :( public: Render Render virtual void Render() = 0; }; struct RenderBatch { float3 position; Frustum Sort / Culling Group Render Mesh *mesh; Material *material; :) }; 47
  • 48. Podsumowanie  DOD służy raczej do optymalizacji wydajności • Kod bardziej przyjazny dla pamięci cache • Kod łatwiej się zrównolegla  Jednak wielu odrzuca „przedwczesną optymalizację” nadinterpretując znany cytat Donalda Knutha  Dlatego tutaj pokazałem, jak krytyczne spojrzenie na OOP może pomóc także w uproszczeniu kodu. 48
  • 49. Bibliografia  Fanatyzm obiektowy, Adam Sawicki • http://www.asawicki.info/Download/Misc/Fanatyzm_obiektowy.html  Materiały o Data-Oriented Design • http://www.asawicki.info/news_1422.html (lista linków) 49