Dobry kod OO Jeżeli zapytamy statystycznego programistę z czym kojarzy mu się dobry kod OO to najprawdopodobniej będzie mówił o wzorcach projektowych.

Slides:



Advertisements
Podobne prezentacje
C++ wykład 9 ( ) Szablony.
Advertisements

C++ wykład 2 ( ) Klasy i obiekty.
C++ wykład 4 ( ) Przeciążanie operatorów.
Klasy abstrakcyjne i interfejsy
Deklaracje i definicje klas w C++ Składowe, pola, metody Konstruktory
Klasa listy jednokierunkowej Przekazywanie parametrów do funkcji
Programowanie obiektowe
Klasy i obiekty.
Zaawansowane metody programowania – Wykład V
Static, const, volatile.
Dziedziczenie. Po co nam dziedziczenie? class osoba { char * imie, char * imie, * nazwisko; * nazwisko;public: void wypisz_imie(); void wypisz_imie();
Obiektowe metody projektowania systemów Design Patterns STRATEGY.
Bezpieczeństwo wyjątków w C++: OpenGL
Tworzenie i obsługa programów – przykład 3 uwagi cd. Wykorzystując różne klasy biblioteki języka Java należy pamiętać w jakim pakiecie się znajdują. Wszystkie.
Programowanie obiektowe w Javie
Wielodziedziczenie od środka Konrad Lipiński
OOPC++ - wstêp, klasy1 Klasy Do struktury można dołączyć operacje działające na jej polach. struct date { int day, month, year; void set (int d, int m,
Serwery Aplikacji ASP .NET Web Objects Arkadiusz Popa.
DZIEDZICZENIE · klasy bazowe i klasy pochodne WyświetlAutora( ) Autor
Obiektowe metody projektowania systemów
Obiektowe metody projektowania systemów Command Pattern.
Struktury.
C++ wykład 2 ( ) Klasy i obiekty.
Podstawy C# Grupa .NET PO.
Klasy w C++. Deklaracja klasy class NazwaTwojejKlasy { //w tym miejscu piszemy definicje typów, //zmienne i funkcje jakie mają należeć do klasy. }; //tutaj.
Komponentowe i rozproszone Jak pisać dobry kod. ZŁY KOD WYGLĄDA TAK...
Programowanie obiektowe w C++
Programowanie obiektowe III rok EiT
Programowanie obiektowe III rok EiT dr inż. Jerzy Kotowski Wykład IX.
Programowanie obiektowe III rok EiT
Java – coś na temat Klas Piotr Rosik
Programowanie obiektowe Wykład 3 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/21 Dariusz Wardowski.
Programowanie obiektowe Wykład 6 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/14 Dariusz Wardowski.
C# Platforma .NET CZ.3 Kuba Ostrowski.
Tworzenie Aplikacji Internetowych dr Wojciech M. Gańcza 8.
Programowanie obiektowe 2013/2014
  ELEMENTY JĘZYKA JAVA komentarze w Javie, słowa kluczowe i operatory, proste typy danych, tablice, podstawowy zestaw instrukcji.
Prasek Aneta, Skiba Katarzyna. Funkcje stałe const to takie funkcje, które nie mogą modyfikować stanu obiektu. Oznacza to, że funkcja stała nie może zmieniać.
Kurs języka C++ – wykład 9 ( )
Programowanie w języku C++
Treści multimedialne - kodowanie, przetwarzanie, prezentacja Odtwarzanie treści multimedialnych Andrzej Majkowski informatyka +
K URS JĘZYKA C++ – WYKŁAD 10 ( ) Szablony.
Kurs języka C++ – wykład 4 ( )
K URS JĘZYKA C++ – WYKŁAD 2 ( ) Klasy i obiekty.
Technologie internetowe Wykład 5 Wprowadzenie do skrytpów serwerowych.
Programowanie obiektowe Wykład 9 dr Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ 1/15 Dariusz Wardowski.
OOP, Desing Patterns … and more Michał Dubel
Obiektowe metody projektowania systemów Adapter. Wstęp: „Dostosowanie interfejsu klasy do interfejsu, którego oczekuje użytkownik. Adapter umożliwia współprace.
Obiektowe metody projektowania systemów Abstract Factory design pattern (aka. Kit)
Paweł Starzyk Obiektowe metody projektowania systemów
Dziedziczenie Wykład 7 Dziedziczenie sekwencyjne
Wykład 11 Aplikacje SDI PO11-1 / 22 Single Document Interface 1.Klasy aplikacji SDI 2.Menu systemowe aplikacji SDI 3.Serializacja 4.Tworzenie widoku 5.Tworzenie.
PO13-1 / 19 Wykład 13 Wyjątki i ich zgłaszanie Wyłapywanie wyjątków Obsługa wyjątków Wykorzystanie polimorfizmu Filtrowanie wyjątków Błędy w konstruktorach.
Partnerstwo dla Przyszłości 1 Lekcja 27 Klasy i obiekty.
Wykład 8 Polimorfizm 1.Funkcje polimorficzne 2.Czyste funkcje wirtualne i klasy abstrakcyjne PO8-1 / 38.
Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego Matuszyka Podstawy.
K URS JĘZYKA C++ – WYKŁAD 3 ( ) Przenoszenie Składowe statyczne Funkcje wbudowane Argumenty domyślne.
InMoST, Java – przykładowa aplikacja Bartosz.Michalik
Inżynieria oprogramowania OOP i zasady SOLID WWW: Jacek Matulewski Instytut Fizyki, UMK.
Programowanie Obiektowe – Wykład 6
ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod
Programowanie Obiektowe – Wykład 9
Kurs języka C++ – wykład 3 ( )
Klasy, pola, obiekty, metody. Modyfikatory dostępu, hermetyzacja
(według:
Akademia C# - Lab2 Zmienne, instrukcje warunkowe, pętle, debugger,
Delegaty Delegat to obiekt „wiedzący”, jak wywołać metodę.
Programowanie Obiektowe – Wykład 2
Język C++ Typy Łukasz Sztangret Katedra Informatyki Stosowanej i Modelowania Prezentacja przygotowana w oparciu o materiały Danuty Szeligi i Pawła Jerzego.
Zapis prezentacji:

Dobry kod OO Jeżeli zapytamy statystycznego programistę z czym kojarzy mu się dobry kod OO to najprawdopodobniej będzie mówił o wzorcach projektowych. Co poniektórzy wspomną o zasadach projektowania, enkapsulacji i hermetyzacji, dziedziczeniu, interfejsach. Niewielu wspomni o zasadach S.O.L.I.D. Nic w tym dziwnego, bo zasady te nie są sformalizowane tak jak wzorce. Dobrze określił je Krzysiek na spotkaniu mówiąc, że są raczej przysłowiami. Ja bym raczej nazwał je powiedzeniami informatyków. Każda z tych zasad odnosi się do jakiegoś bardzo ogólnego zagadnienia czy to projektowego, czy to architektonicznego czy też funkcjonalnego. Zazwyczaj do wszystkiego po trochu. Jako, że nie ma tu formalizacji więc nie można mówić o miejscach i sposobach stosowania czy problemach, które można rozwiązać. Jeżeli jednak przyjrzymy się wzorcom projektowym, to dojdziemy do wniosku, że wszystkie one zawierają w sobie te nieformalne zasady.

S.O.L.I.D 5 podstawowych wzorców dotyczących programowania zorientowanego obiektowo zdefiniowane przez Roberta C.Martina (szefu Object mentor Inc., międzynarodowy konsultant do spraw rozwoju oprogramowania, przewodniczył grupie odpowiedzialnej za stworzenie „Agile software development”, twórca książek takich jak: „Designing Object-Oriented C++ Applications using the Booch Method”, „Agile Software Development: Principles, Patterns and Practices”, „Clean Code”)

SRP – Single Resposibility Principle Jeden powód do zmian! Pozwala na separację poszczególnych modułów Jest jedną z najprostszych zasad ale jedną z najtrudniejszych do poprawnego zastosowania Szukanie i separacja odpowiedzialności jest tak naprawdę wszystkim o co chodzi w tworzeniu oprogramowania Reszta zasad nawiązuje w taki lub inny sposób do tej zasady

Aplikacja do geometri analitycznej Rectangle + draw() + area() : double Aplikacja graficzna GUI Klasa Rectangle ma dwie odpowiedzialności. Jedna aplikacja używa klasy Rectangle do obliczeń matematycznych i nigdy nie użyje metody do rysowania. Druga aplikacja rysuje na ekranie i sporadycznie wykonuje obliczenia matematyczne. W tym układzie klasa Rectangle narusza zasadę SRP, ponieważ ma dwie odpowiedzialności. Pierwsza odpowiedzialność to dostarczenie matematycznego modelu prostokąta, druga to narysowanie prostokąta na ekranie. Zależności: trzeba dołaczyć moduł GUI do aplikacji od geometi analitycznej (zwiekszenie czasu kompilacji i zużycia pamięci), jakakolwiek zmiana zmiana aplikacji graficznej wymagająca zmiany klasy Rectangle spowoduje konieczność przebudowy, ponowynych testów i dostarczenia klientowi aplikacji od geometri analitycznej. Zaniechanie tego może spowodować trudne do znalezienia błędy

Aplikacja do geometri analitycznej graficzna Geometric Rectangle + area() : double Rectangle + draw() GUI

public void dial(String pno); public void hangup(); interface Modem { public void dial(String pno); public void hangup(); public void send(char c); public char recv(); } Czasami trudno dostrzec pojedynczą odpowiedzialność. Z pozoru definicja Modemu wydaje się poprawna, ale są tu dwie odpowiedzialności: Zarządzanie połączeniem (dial, hangup) Przesyłanie danych (send, recv) Te dwie odpowiedzialności nie mają ze sobą nic wspólnego, powinny zostać rozdzielone (rożne części aplikacji będą wołać inne odpowiedzialności)

<<interface>> Data Channel + send(:char) + recv() : char Connection + dial(pno : String) + hangup() Modem implementation Rozdzielono odpowiedzialności, ale ponownie je złączono w klasie implementującej. Nie jest to pożądane, ale może być czasami konieczne (np. z powodów sprzętowych lub systemowych). Wtedy jesteśmy zmuszeni do złączania odpowiedzialności, ale rozdzielenie koncepcji jest wciąż prawidłowe w kontekście reszty aplikacji. Poza tym nikt nie musi używać tej klasy. Inny przykład: MVC

OCP – Open-Closed Principle Elementy oprogramowania (klasy, moduły, funkcje itd.) powinny być otwarte na rozszerzanie, ale zamknięte na modyfikację. Kiedy pojedyncza zmiana w kodzie wywołuję łańcuch innych zmian w modułach zależnych oznacza to „zły” projekt Powinno się projektować moduły, które nigdy się nie zmieniają Konflikt! Jak zmienić zachowanie modułu, kiedy zmieniają się wymagania aplikacji? Abstrakcja! Korzystać z interfejsów, klas abstrakcyjnych, kompozycji i dziedziczenia. Kiedy zmieniają się wymagania, powinno się rozszerzać zachowanie poszczególnych modułów nie przez zmianę kodu który już działa ale przez dodawanie nowego kodu.

Zamknięty klient Client Server Klient i Server to klasy konkretne. Klient używa serwer (agregacja) Zamknięty klient

Otwarty klient Abstract Client Server Server Klasa AbstractServer reprezentuje stałą, niezmienną abstrakcję, pod którą jednak kryje się niograniczona liczba zachowań wyrażona przez konkretne klasy implementujące (dziedziczące) Otwarty klient

Aplikacja rysująca kółka i kwadraty Aplikacja posiada listę kółek i kwadratów Kółka i kwadraty muszą być rysowanę zgodnie z kolejnością na liście

enum ShapeType {circle, square}; struct Shape { ShapeType itsType; }; struct Circle double itsRadius; Point itsCenter; struct Square double itsSide; Point itsTopLeft; void DrawSquare(struct Square*) void DrawCircle(struct Circle*);

typedef struct Shape *ShapePointer; void DrawAllShapes(ShapePointer list[], int n) { int i; for (i = 0; i < n; i++) struct Shape* s = list[i]; switch (s->itsType) case square: DrawSquare((struct Square*)s); break; case circle: DrawCircle((struct Circle*)s); } Funkcja DrawAllShapes łamie zasadę open-closed, ponieważ musi być modyfikowana, ilekroć chcemy dodać nową figurę.W realnej aplikacji przy takim podejściu podobne switche byłyby w wielu miejscach i korekta wszystkich tych miejsc byłaby kłopotliwa i błędogenna.

virtual void Draw() const = 0; }; class Square : public Shape class Shape { public: virtual void Draw() const = 0; }; class Square : public Shape virtual void Draw() const; class Circle : public Shape void DrawAllShapes(Set<Shape*>& list) for (Iterator<Shape*> it(list); it; it++) (*it)->Draw(); } Domknięcie metody DrawAllShapes ze względu na kształt figury. Metoda DrawAllShapes nigdy nie ulegnie zmianie, gdy pojawi się nowy typ kształtu. Prawie żadna aplikacja/moduł może być zamknięta na 100%. Co by było gdyby program wymagał rysowania kółek przed prostokątami? Metoda DrawAllShapes nie jest odporna na tą zmianę. Musiałaby zostać zmodyfikowana. Generalnie nieważne jak odpornym nasz moduł byłby na zmiany, zawsze może pojawić się wymaganie na którą nasza abstrakcja/struktura aplikacji nie jest przygotowana.

virtual void Draw() const = 0; class Shape { public: virtual void Draw() const = 0; virtual bool Precedes(const Shape&) const = 0; bool operator<(const Shape& s) {return Precedes(s);} }; void DrawAllShapes(Set<Shape*>& list) OrderedSet<Shape*> orderedList = list; orderedList.Sort(); for (Iterator<Shape*> it(orderedList); it; it++) (*it)->Draw(); } bool Circle::Precedes(const Shape& s) const if (dynamic_cast<Square*>(s)) return true; else return false; Funkcja DrawAllShapes kopiuje listę do uporządkowanego zboiru, sortuje go wykorzystując metodę Precedes i rysuje na ekranie. Metoda Precedes łamie zasadę OCP. Co będzie jak porządek rysowania będzie musiał uwzględniać inne kształty?

virtual void Draw() const = 0; class Shape { public: virtual void Draw() const = 0; virtual bool Precedes(const Shape&) const; bool operator<(const Shape& s) const {return Precedes(s);} private: static char* typeOrderTable[]; }; char* Shape::typeOrderTable[] = “Circle”, “Square”, Podejście Data Driven. Kolejność jest ustalana przez wpis w tabeli. Teraz tylko klasa bazowa implementuje Precedes. Aczkolwiek aplikacja nie jest domknięta na 100%. Trzeba dodawać kolejne kształty do tablicy.

bool Shape::Precedes(const Shape& s) const { const char* thisType = typeid(*this).name(); const char* argType = typeid(s).name(); bool done = false; int thisOrd = -1; int argOrd = -1; for (int i = 0; !done; i++) const char* tableEntry = typeOrderTable[i]; if (tableEntry != 0) if (strcmp(tableEntry, thisType) == 0) thisOrd = i; if (strcmp(tableEntry, argType) == 0) argOrd = i; if (argOrd > 0 && thisOrd > 0) done = true; } else return thisOrd < argOrd;

OCP wiąże się z kilkoma innymi utartymi zasadami Wszystkie zmienne klasy powinny być prywatne W świetle zasady OCP powód do tego jest jasny. Jeśli zmienna klasy zmienia się, każda metoda która od nie zależy też musi zostać zmieniona i taka funkcja nie może zostać zamknięta

Co gdy dane pole nigdy się nie zmieni Co gdy dane pole nigdy się nie zmieni? Czy istnieje powód, żeby czynić je polem prywatnym? class Device { public: bool status; //status ostatniej operacji na //urządzeniu }; W OOD spodziewamy się, że metody klasy nie są zamknięte na zmiany względem pól klasy. Spodziewamy się za to, że każda inna klasa jest zamknięta na zmiany tych pól. To jest enkapsulacja. Znaczenie tego pola nigdy się nie zmieni. Więc jeśli nigdy się nie zmienia i wszystkie moduły przestrzegają zasady, że jest to pole tylko do odczytu to dlaczego nie uczynić tego pola publicznym? A co jeśli jeden moduł wykorzysta to, że to pole jest także do zapisu i zmieni jego wartosć? Wtedy inne moduły mogą odczytać nieprawdziwy status operacji. To za duże ryzyko, dlatego lepiej uczynić pole prywatnym.

int hours, minutes, seconds; Time& operator-=(int seconds); class Time { public: int hours, minutes, seconds; Time& operator-=(int seconds); Time& operator+=(int seconds); bool operator< (const Time&); bool operator> (const Time&); bool operator==(const Time&); bool operator!=(const Time&); }; Istnieje małe prawdopodobieństwo, że pola klasy Time będą zmieniane przez moduły aplikacji. Raczej będą odczytywane. Jest też mało prawdopodobne, że klasa pochodna chciałaby ustawiać poszczególne pola pojedynczo. Za to klient mógłby zmienić poszczególne pola i naruszyć spójność czasu. Ten przykład nie narusza OCP ale jest postrzegany jako „zły styl”

Żadnych zmiennych globalnych – Nigdy Żaden moduł, który zależy od zmiennej globalnej nie może zostać zamknięty względem innego modułu, jeśli ten inny moduł korzysta z tej zmiennej.

LSP – Liskov Substitution Principle Definicja Barbary Liskov: Jeśli dla każdego obiektu o1 typu S istnieje obiekt o2 typu T, taki że dla każdego programu P zdefiniowanego w kontekście typu T, zachowanie programu P pozostaje niezmienione kiedy o1 jest podmieniane obiektem o2, to prawdziwe jest, że typ S jest podtypem typu T. Funkcje, które używają wskaźników lub referencji do klas bazowych muszą być w stanie używać obiektów klas dziedziczących po klasach bazowych bez dokładnej znajomości tych obiektów 1 dla webskich gości 2 dla mniej webskich gości

LSP Jeśli twój kod oczekuje jakiejś klasy, to zamiast niej powinieneś móc podstawić dowolną klasę z niej dziedziczącą bez zmieniania żadnych oczekiwanych zachowań. Jeśli przeciążasz metodę, napisz ją tak, by użyta polimorficznie działała poprawnie 3. Nie dla orłów 4. Dla ludu

Klasyczny problem kwadratu i prostokąta class Rectangle { protected int m_Width; protected int m_Height; public void SetWidth(int width) { m_Width = width; } public void SetHeight(int height) { m_Height = height; } public int GetWidth() { return m_Width; } public int GetHeight() { return m_Height; } public int GetArea() { return m_Width * m_Height; } }

class Square: Rectangle { public void SetWidth(int width) m_Width = width; m_Height = width; } public void SetHeight(int height) m_Width = height; m_Height = height; Rectangle r = new Square(); r.SetWidth(5); r.SetHeight(10); int area = r.GetArea(); Wynik spodziewany: 50, otrzymany: 100. Złamanie LSP: sekwencja kodu przynosi różne efekty w zależności od tego czy posiadamy referencję na klasę bazową czy potomną. Klasa potomna nie powinna zmieniać zachowania klasy. Ważne: Klasy square i rectangle widziane w izolacji wydają się być poprawne. Jednakże poprawność/spójność modelu nie może być sprawdzona w izolacji. Spójność modelu może być tylko poprawnie zbadana w kontekście wzajemnych zależności i sposobu użycia.

public class User { protected String login; protected String password; protected List modules; public void addAccessToModule(Module module) { modules.add(module); } public boolean canAccess(Module module) { return modules.contains(module);

public class Admin extends User { private List administeredModules; public boolean canAccess(Module module) { return administeredModules.contains(module); } public void subscribeToEvents(User user, Module module) { if (! user.canAccess(module)) { user.addAccessToModule(module); module.sendEventsTo(user); Na pierwszy rzut oka nie widać żadnego błędu - jeśli ktoś jeszcze nie ma dostępu do jakiegoś modułu, to jest on mu przyznawany i dopisywany jest on do listy użytkowników notyfikowanych o zdarzeniach. Ale niestety przez to, że programista nie zastosował się do zasady podstawiania, kod ten jest poprawny tylko dla zwykłych użytkowników. Wielokrotne wywoływanie tej metody dla obiektów klasy Admin spowoduje ciągłe dodawanie tych samych modułów do do kolekcji modules klasy User, choć metoda canAccess w klasie Admin w ogóle jej nie sprawdza. Błąd jest zawsze w klasie, a nie w kodzie wołającym

LSP Design by Contract. Klasy definiują warunki wejściowe i wyjściowe metod Odpowiednia dokumentacja.

ISP – Interface Segregation Principle Klienci nie powinni być zmuszani do bycia zależnymi od metod, których nie używają. Likwiduje „grube” interfejsy

interface IWorker { void Work(); void Eat(); } class Worker: IWorker public void Work() public void Eat() class Robot: IWorker throw new NotImplementedException();

class Worker: IWorkable, IFeedable public void Work() interface IWorkable { public void Work(); } interface IFeedable public void Eat(); class Worker: IWorkable, IFeedable public void Work() public void Eat() class Robot: IWorkable http://www.pzielinski.com/?p=424