Pułapki programowania obiektowego

Slides:



Advertisements
Podobne prezentacje
Znaki informacyjne.
Advertisements

Joanna Sawicka Wydział Nauk Ekonomicznych, Uniwersytet Warszawski
C++ wykład 2 ( ) Klasy i obiekty.
Wprowadzenie do informatyki Wykład 6
Deklaracje i definicje klas w C++ Składowe, pola, metody Konstruktory
Programowanie obiektowe
Modelowanie przypadków użycia
Klasy i obiekty.
Wzorce.
Zaawansowane metody programowania – Wykład V
Ludwik Antal - Numeryczna analiza pól elektromagnetycznych –W10
Obiektowe metody projektowania systemów Design Patterns STRATEGY.
Bezpieczeństwo wyjątków w C++: OpenGL
Liczby pierwsze.
Kamil Smitkiewicz Bezpieczeństwo w PHP.
1 mgr inż. Sylwester Laskowski Opiekun Naukowy: prof. dr hab. inż. Andrzej P. Wierzbicki.
PROGRAM OPERACYJNY KAPITAŁ LUDZKI Priorytet III, Działanie 3.2
Ksantypa2: Architektura
Systemy operacyjne Copyright, 2000 © Jerzy R. Nawrocki Wprowadzenie do informatyki.
Obiektowe metody projektowania systemów
Obiektowe metody projektowania systemów
Struktury.
Tablice.
C++ wykład 2 ( ) Klasy i obiekty.
Zasady zaliczenia Warunki uzyskania zaliczenia:
Języki programowania obiektowego
Wstęp do programowania obiektowego
Techniki programowania gier
Wykonawcy:Magdalena Bęczkowska Łukasz Maliszewski Piotr Kwiatek Piotr Litwiniuk Paweł Głębocki.
Pułapki programowania obiektowego Adam Sawicki - – 2 stycznia 2011www.asawicki.info.
T: Różnice pomiędzy programowaniem strukturalnym a obiektowym
Źródła: podręcznikopracował: A. Jędryczkowski.
Projektowanie obiektowe
Programowanie obiektowe III rok EiT
KOLEKTOR ZASOBNIK 2 ZASOBNIK 1 POMPA P2 POMPA P1 30°C Zasada działanie instalacji solarnej.
ŻYWE JĘZYKI PROGRAMOWANIA LIVING IT UP WITH A LIVE PROGRAMMING LANGUAGE Sean McDirmid Ecole Polytechnique Fédérale de Lausanne (EPFL)
Jerzy F. Kotowski1 Informatyka I Wykład 14 DEKLARATORY.
JAVA c.d.. Instrukcji wyboru SWITCH używamy, jeśli chcemy w zależności od wartości pewnego wyrażenia wykonać jeden z kilku fragmentów kodu. Jest to w.
WPROWADZENIE W ŚWIAT OBIEKTÓW
Dziedziczenie Maciek Mięczakowski
Inicjalizacja i sprzątanie
Programowanie obiektowe Wykład 3 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21 Dariusz Wardowski.
Programowanie obiektowe Wykład 7 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/20 Dariusz Wardowski.
Programowanie obiektowe Wykład 6 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14 Dariusz Wardowski.
Projektowanie obiektowe
Programowanie obiektowe – język C++
Programowanie obiektowe 2013/2014
ZWIĄZKI MIĘDZY KLASAMI KLASY ABSTRAKCYJNE OGRANICZENIA INTERFEJSY SZABLONY safa Michał Telus.
EcoCondens Kompakt BBK 7-22 E.
Programowanie w języku C++
Programowanie w języku C++
W2 Modelowanie fenomenologiczne I
User experience studio Użyteczna biblioteka Teraźniejszość i przyszłość informacji naukowej.
Obliczalność czyli co da się policzyć i jak Model obliczeń sieci liczące dr Kamila Barylska.
Treści multimedialne - kodowanie, przetwarzanie, prezentacja Odtwarzanie treści multimedialnych Andrzej Majkowski informatyka +
Architektura współczesnych gier video Adam Sawicki asawicki.infoasawicki.info
Jak Jaś parował skarpetki Andrzej Majkowski 1 informatyka +
ZOSTAŃ SPORTOWYM KOKSEM SP 11 TYM KTÓRY OCZARUJE KOMISJĘ SĘDZIOWSKĄ.
Elementy geometryczne i relacje
Obiektowe metody projektowania systemów Abstract Factory design pattern (aka. Kit)
Paweł Starzyk Obiektowe metody projektowania systemów
Wzorce Projektowe w JAVA
Programowanie Zaawansowane
K URS JĘZYKA C++ – WYKŁAD 3 ( ) Przenoszenie Składowe statyczne Funkcje wbudowane Argumenty domyślne.
Programowanie Obiektowe – Wykład 6
Klasy, pola, obiekty, metody. Modyfikatory dostępu, hermetyzacja
Programowanie Obiektowe – Wykład 2
PGO Dziedziczenie Michail Mokkas.
Zapis prezentacji:

Pułapki programowania obiektowego Wersja 2, 29 marca 2011 Adam Sawicki – www.asawicki.info

Agenda Kim jestem „Fanatyzm obiektowy” „Mania wrapowania” Projektowanie obiektowe Data-Oriented Design „Mania wrapowania” Wrapper – na 3 przykładach Generalizacja Dziedziczenie Enkapsulacja Wzorce projektowe Singleton Podsumowanie DOD raz jeszcze Pytania

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. Nie jest to nazbytni narcyzm albo autoreklama? – poradzić się kogoś.

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

Fanatyzm obiektowy

Projektowanie obiektowe OOP można rozważać na różnych płaszczyznach: Ideologiczna – zasady programowania obiektowego i abstrakcyjne znaczenie jego założeń, Techniczna – jak się używa programowania obiektowego w danym języku i jak ono działa, Praktyczna – jak używać go w programach dla swojego pożytku, a nie tylko dla zasady. Tak jak ze wszystkim, nadmierna radykalność i ślepe poparcie bez myślenia rodzi fanatyzm, a to jest złe.

Przykład Gra w kółko i krzyżyk – podejście obiektowe 1. Znajdujemy klasy identyfikując rzeczowniki

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

Przykład 3. Implementujemy klasy Co umieścić w tych klasach, żeby nie były puste??? W której klasie umieścić potrzebne dane i kod??? Analysis Paralysis – poszukiwanie idealnego rozwiązania

Przykład 3. Implementujemy klasy Co umieścić w tych klasach, żeby nie były puste??? W której klasie umieścić potrzebne dane i kod??? Analysis Paralysis – poszukiwanie idealnego rozwiązania

Data-Oriented Design Alternatywne podejście: Data-Oriented Design (DOD) Myśl o strukturze danych w pamięci Pytanie: Jakie dane powinna przechowywać gra? 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 enum POLE { POLE_PUSTE, POLE_KOLKO, POLE_KRZYZYK, }; POLE Plansza[3][3]; int CzyjRuch;

DOD – przykład Następnie pomyśl, co po kolei kod powinien robić 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

DOD – przykład Następnie pomyśl, co po kolei kod powinien robić 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

Projektowanie obiektowe – wnioski Podejście obiektowe nie jest idealne Można nawet stwierdzić, że nie spełniło swoich założeń 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 stosować, ale z rozwagą Znaj i doceniaj inne możliwości Nie szukaj gotowych „klocków” uciekając od myślenia

Mania wrapowania

Mania wrapowania Wielu programistów czuje potrzebę, żeby naukę czy wykorzystanie w swoim programie jakiejś biblioteki zacząć 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ą?

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ścić interfejs Te parametry są niepotrzebne Chcę mieć mniej parametrów Nie chcę sięgać do dokumentacji FMOD – wolę własny interfejs

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); }

Przykład 1: FMOD Jimmy potrzebuje możliwości zapętlenia dźwięku Musi dodać parametr Loop Musi zamieniać 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); }

Przykład 1: FMOD Jimmy odkrywa odtwarzanie w dwie strony i też chce to umożliwić Musi zdefiniować własny enum LOOP_MODE Musi zamieniać 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);

Przykład 1: FMOD Wreszcie duża część możliwości FMOD jest przepisana do własnego interfejsu Trzeba zrobić do niego własną dokumentację Zamiast flag FMOD trzeba pamiętać własne Jaka to różnica? Warto było??

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

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

Przykład 2: DirectX Jakie jest prawdopodobieństwo, że P1 ∙ P2 ∙ P3 ≈ 0 P1 – skończysz kod swojej gry/silnika P2 – będziesz potrzebował mieć drugą implementację renderera P3 – uda się zaimplementować 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

Przykład 3: WinAPI i MFC Ktoś w Microsoft: WinAPI jest niefajne, bo jest strukturalne. Napiszmy obiektową otoczkę. Czy to zrobiło aż tak dużą różnicę? Warto było??? Efekt? MFC to tylko cienka otoczka na WinAPI. Nikt go nie lubi. // WinAPI HDC hdc = GetWindowDC(hwnd); MoveToEx(hdc, 0, 0, NULL); LineTo(hdc, 100, 100); ReleaseDC(hdc); // MFC CDC *dc = wnd->GetDC(); dc->MoveTo(0, 0); dc->LineTo(100, 100); wnd->ReleaseDC(dc);

Wrapper – wnioski Jaka wobec tego jest alternatywa? W porę zastanowić się, czy nie wystarczy używać w swoim programie interfejsu danej biblioteki bezpośrednio. Warto napisać 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

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

Generalizacja

Generalizacja – błędne koło 1 2 3 Chcemy, żeby każdy pisany kod był jak najbardziej ogólny i uniwersalny, aby nie trzeba było wprowadzać w nim zmian, kiedy zmienią się wymagania. :(

Generalizacja – błędne koło 1 2 3 Unikamy wprowadzania zmian w kodzie, bo każda, nawet miała zmiana, wymaga bardzo dużo pracy. :(

Generalizacja – błędne koło 1 2 3 Każda zmiana w kodzie wymaga bardzo dużo pracy, ponieważ wszystko piszemy jak najbardziej ogólnie i uniwersalnie. :(

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

Dziedziczenie

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

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 zaprojektować dobry układ klas, ich pól, metod i zależności między nimi

Przykład: okrąg i elipsa 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 promień! ??

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”

Enkapsulacja

Enkapsulacja Teoria Udostępnia interfejs, ukrywa szczegóły implementacji Pozwala zmienić 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; };

Enkapsulacja Problem Czym się różni getter + setter od uczynienia pola publicznym? Czy naprawdę wierzysz, że możesz zmienić implementację tego pola (zmienić jego typ, wyliczać za każdym razem, pobierać 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; };

Enkapsulacja Rozwiązanie: Nie bój się używać 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);

Wzorce projektowe: Singleton

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

Singleton Rozwiązanie: Jawnie tworzyć i niszczyć obiekty globalne w określonym miejscu programu Every time you make a singleton, God kills a startup. Two if you think you've made it thread-safe. – popularna sentencja z Twittera g_SoundSystem = new SoundSystem();

Podsumowanie

OOP kontra DOD :( :)

OOP kontra DOD class BaseObject { private: float3 position; public: virtual void Render() = 0; }; struct RenderBatch { Mesh *mesh; Material *material; ?? Render Render :( Render Render Frustum Culling Sort / Group Render :)

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.

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)

Pytania?