Dobry kod OO
E N D
Presentation Transcript
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() { } }