E N D
S.O.L.I.D5 podstawowych wzorców dotyczących programowania zorientowanego obiektowozdefiniowane 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 ResposibilityPrinciple • 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
Aplikacja do geometri analitycznej Rectangle + draw() + area() : double Aplikacja graficzna GUI
Aplikacja do geometri analitycznej Aplikacja graficzna Geometric Rectangle + area() : double Rectangle + draw() GUI
interface Modem { public void dial(String pno); public void hangup(); public void send(char c); public char recv(); }
<<interface>> Data Channel + send(:char) + recv() : char <<interface>> Connection + dial(pno : String) + hangup() Modem implementation
OCP – Open-ClosedPrinciple • 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ą
Client Server Zamknięty klient
Client Abstract Server Server 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 { ShapeType itsType; double itsRadius; Point itsCenter; }; struct Square { ShapeType itsType; double itsSide; Point itsTopLeft; }; void DrawSquare(struct Square*) void DrawCircle(struct Circle*);
typedef struct Shape *ShapePointer; voidDrawAllShapes(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); break; } } }
class Shape { public: virtualvoid Draw() const = 0; }; class Square : public Shape { public: virtualvoid Draw() const; }; class Circle : public Shape { public: virtualvoid Draw() const; }; void DrawAllShapes(Set<Shape*>& list) { for (Iterator<Shape*> it(list); it; it++) (*it)->Draw(); }
class Shape { public: virtualvoid Draw() const = 0; virtualbool 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)) returntrue; else returnfalse; }
class Shape { public: virtual void Draw() const = 0; virtual boolPrecedes(const Shape&) const; booloperator<(const Shape& s) const {return Precedes(s);} private: static char* typeOrderTable[]; }; char* Shape::typeOrderTable[] = { “Circle”, “Square”, 0 };
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 done = true; } return thisOrd < argOrd; }
OCP wiąże się z kilkoma innymi utartymi zasadami • Wszystkie zmienne klasy powinny być prywatne
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 };
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&); };
Ż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
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
Klasyczny problem kwadratu i prostokąta classRectangle { protectedintm_Width; protectedintm_Height; public voidSetWidth(intwidth) { m_Width= width; } public voidSetHeight(intheight) { m_Height= height; } public intGetWidth() { returnm_Width; } public intGetHeight() { returnm_Height; } public intGetArea() { returnm_Width * m_Height; } }
classSquare: Rectangle { public voidSetWidth(intwidth) { m_Width = width; m_Height = width; } public voidSetHeight(intheight) { m_Width = height; m_Height = height; } } Rectangle r = new Square(); r.SetWidth(5); r.SetHeight(10); int area = r.GetArea();
public classUser{ protectedString login; protectedStringpassword; protected List modules; publicvoidaddAccessToModule(Module module) { modules.add(module); } public booleancanAccess(Module module) { returnmodules.contains(module); }
public classAdminextendsUser { private List administeredModules; public booleancanAccess(Module module) { returnadministeredModules.contains(module); } } public voidsubscribeToEvents(Useruser, Module module) { if (! user.canAccess(module)) { user.addAccessToModule(module); } module.sendEventsTo(user); }
LSP • Design by Contract. Klasy definiują warunki wejściowe i wyjściowe metod • Odpowiednia dokumentacja.
ISP – InterfaceSegregationPrinciple • Klienci nie powinni być zmuszani do bycia zależnymi od metod, których nie używają. • Likwiduje „grube” interfejsy
interfaceIWorker { voidWork(); voidEat(); } classWorker: IWorker { public voidWork() { } public voidEat() { } } class Robot: IWorker { public voidWork() { } public voidEat() { thrownewNotImplementedException(); } }
interfaceIWorkable { public voidWork(); } interfaceIFeedable { public voidEat(); } classWorker: IWorkable, IFeedable { public voidWork() { } public voidEat() { } } class Robot: IWorkable { public voidWork() { } }