background image

Zasady programowania 

obiektowego

Prostota przede wszystkim

background image

Zasady dla klas

background image

Law of Demeter

• Demeter była boginią przyrody (Gleba-Matka), urodzajów, patronką 

rolnictwa. Główny mit związany z Demeter opisuje jej poszukiwania zaginionej 

córki (swojej i Zeusa) - Persefony (Kora - Dziewczyna). Bogini zostawiła ją na 

łące, bawiącą się z nimfami (okeanidami). Odchodząc zabroniła jej zrywać 

narcyzów - kwiatów związanych z bóstwami podziemnymi. Persefona nie 

posłuchała matki. Z woli Zeusa wyrósł na łące wspaniały kwiat, w którym 

z korzenia wyrastało sto łodyg o cudownym wyglądzie i zapachu. Gdy 

dziewczyna odkryła kwiat zapomniała o przestrogach matki i zerwała go. 

Otwarła się wówczas ziemia i Hades, bóg podziemi, porwał Persefonę 

i zaczarowanym rydwanem wywiózł ją w dal. Lecieli ponad ziemią i morzem - do 

Tartaru. Nikt nie słuchał krzyków Persefony. Dopiero po długim czasie dotarły 

one do matki - Demeter. 

• Zrozpaczona Demeter rozpoczęła poszukiwania. Przekazała swoją żałobę po 

Persefonie polom. Zasiewy zmarniały, spiekota wysuszyła źródła i rzeki, padały 

zwierzęta, a ludzi nawiedził głód. Nie było nawet z czego składać ofiar bogom. 

Bogowie olimpijscy wystraszyli się, że jeżeli ludzie wyginą, to kto będzie składał 

im ofiary. Zeus wysłał do Demeter muzycharyty i kolejno innych olimpijczyków. 

Ale nikomu nie udało się ubłagać bogini. W końcu Zeus wysłał Heraklesa do 

Hadesa z żądaniem uwolnienia Persefony. Hades posłuchał rozkazu Zeusa, ale 

rozstając się z Persefoną podał jej słodką pestkę granatu. Persefona zjadła ją, 

nie wiedząc, że związała się tym na zawsze z podziemnym królestwem. 

• Gdy Persefona wyszła z podziemi uradowana Demeter sprawiła, że wszystko na 

ziemi rozkwitło. Z powodu zjedzonej pestki granatu Persefona odtąd trzecią 

cześć roku musiała spędzać w królestwie Hadesa. Na ziemi panowała wtedy 

zima - przerwa w wegetacji. Pozostałą część roku matka z córką spędzały 

razem, obdarzając ziemię plonami.

background image

Prawo Demeter

background image

Law of Demeter

• Law of Demeter (LoD) (Don't Talk to 

Strangers; or Principle of Least Knowledge)

• Treść: Niech obiekt rozmawia tylko z bliskimi 

współpracownikami.

• Opis: Chodzi w niej o to, żeby obiekt nie 

komunikował się (wywoływał metod, pobierał 

wartości pól) z obiektów w dalekiej odległości w 

grafie powiązań między obiektami. Oznacza to, że 

obiekt powinien mieć minimalną wiedzę o innych 

obiektach. Unika się dzięki temu „kruchych” 

połączeń, co ogranicza wpływ miejscowych zmian 

w projekcie na resztę projektu. 

background image

Law of Demeter – intuicja 

Możesz się bawić:
• sam ze sobą,
• swoimi zabawkami (ale nie                  

wolno Ci rozbierać ich na części!),

• zabawkami, które dostaniesz od innych,
• zabawkami, które sam stworzysz, gdzie 

zabawkami są po prostu obiekty, a 
zabawa polega na wywoływaniu metod.

background image

Law of Demeter - 

formalizacja

public class Demeter {         // Prawo Demeter dla klas
  private A a;
  private int func() { ... }
  public void example (B b) {
                              // obiekt może wywołać tylko takie metody: 
     int k = func(); // <--------- tego samego obiektu

     b.invert(); // <------------- parametru przesłanego do funkcji

     a = new A();
     a.setActive(); // <---------- obiektu stworzonego przez 

Demeter

  }
}

background image

Law of Demeter - 

naruszenie

class A {public: void m(); P p(); B b; };
class B {public: C c; };
class C {public: void foo(); };
class P {public: Q q(); };
class Q {public: void bar(); };
void A::m() {this.b.c.foo(); 

this.p().q().bar();}

background image

Zmniejszenie powiązań 

między obiektami. Przykład.

• Kiepskie rozwiązanie dla wyszukiwania 

wina

• W= ListaWin.dajWina();
• /* Wyszukiwanie liniowe w liście win... */
• Odwołujemy sie do pola klasy ListaWin.
• Nadopiekuńczość – chcemy wykonać 

pracę za kogoś innego, bo na przykład 
uważamy, że lepiej to zrobimy.

background image

Zmniejszenie powiązań 

między obiektami. Przykład.

• Związane są z tym dwa potencjalne problemy:
• Klasa ListaWin może zawierać struktury 

przyśpieszające wyszukiwanie win po nazwie, 
w czasie logarytmicznym, dlatego liniowe 
przeglądanie listy jest nieefektywne.

• Po pewnym czasie może się okazać, że 

będziemy chcieli przechowywać listę win w 
innej strukturze danych (np. w posortowanej 
tablicy) i trzeba będzie przerabiać wszystkie 
wyszukiwania win w programie.

background image

Korzyści ze stosowania LoD

Dwie główne korzyści, które daje nam 

stosowanie prawa Demeter to:

• Nie musimy znać struktury innych 

obiektów.

• Wszelkie zmiany w innych obiektach 

nie maja wpływu na naszą metodę.

background image

Korzyści cd.

• Wnioski płynące ze stosowania prawa Demeter, 

potwierdzone empirycznie podczas badań na 
rzeczywistych programach wskazują, że ułatwia 
ono pielęgnację, zmniejsza gęstość błędów, a 
także ogranicza liczbę i rodzaj powiązań pomiędzy 
obiektami, a co za tym idzie – wzmacnia 
hermetyzację i abstrakcję klasy. 

• Zastosowanie prawa Demeter na poziomie kodu 

programu powoduje przeniesienie 
odpowiedzialności za dostęp do metod i atrybutów 
klasy z obiekt odwołującego się do nich do ich 
właściciela.

background image

LoD króciutko

• Rumbaugh podsumował zasadę Law 

of Demeter w następujący sposób:

Metoda powinna mieć ograniczoną 
wiedzę o modelu obiektów. 

background image

Cienie

• Konsekwentne stosowanie prawa 

Demeter, może skutkować nadmierną 
liczbą metod, których jedynym zadaniem 
jest przedłużenie wywołania metody

• class Samochód{
• private Silnik silnik;
• public void uruchom() { silnik.uruchom;}
• }

background image

A co z tym zrobić?

• System.Console.Writeln(„Hello World”);
• Żeby prawo Demeter nie rodziło takich 

sprzeczności, zezwala na odwoływanie 
sie do pól i metod obiektów globalnych 
(w szczególności do różnych 
przestrzeni nazw; w Javie pakietów).

background image

Law of Demeter

Istnieją dwie postaci prawa Demeter: 

silna i słaba.

W przypadku wersji słabej prawo to 

jest rozszerzone także na podklasy 
klasy analizowanej

background image

Single-Responsibility 

Principle 

• Zasada ta jest silnie powiązana z zasadą 

skupienia (high cohesion) ze wzorów GRASP 

(General Responsibility Assignment Software 

Patterns ). Chodzi o to, żeby klasa miała 

tylko jedną odpowiedzialność (responsibility), 

ponieważ odpowiedzialność jest 

potencjalnym źródłem zmian. Jeżeli klasa ma 

więcej niż jedną odpowiedzialność to zmiana 

jednej z nich będzie wpływać na 

implementację reszty.

background image

Single-Responsibility 

Principle

• Prostym przykładem złamania tej zasady 

jest klasa, która jednocześnie zawiera logikę 
(np. obliczanie powierzchni figury) oraz kod 
związany z grafiką (np. odpowiedzialny za 
rysowanie tej figury). Trochę lepszym 
przykładem złamania zasady SRP jest klasa 
zawierająca jednocześnie kod wybierający 
dane z bazy oraz logikę odpowiedzialną za 
obliczanie pozycji faktury (typowy i częsty 
przykład pomieszania logiki biznesowej) 

background image

Single-Responsibility 

Principle

• class Suma

{

private int wynik;

public void Dodaj(int a, int b)

{

wynik = a + b;

}

public void Wypisz()

{

Console.WriteLine(wynik);

}

}

background image

Single-Responsibility 

Principle

• interface IWyświetl

{

void Wyświetl(int wynik);

}

class Suma{

     private int wynik;

     public int Wynik

     {

        get

            {

       return wynik;

            }

•       }

     public void Dodaj(int a, int 

b)

     {

       wynik = a + b;

     }

}

• class SumaUI : IWyświetl

{

     public void Wyświetl(int 

wynik)

     {

          Console.WriteLine(wynik);

      }

}

//UI – User Interface

• class Program

{

     static void Main(string[] args)

     {

          Suma s = new Suma();

          s.Dodaj(2,2);

          IWyświetl w = new             

    

SumaUI();

          w.Wyświetl(m.Wynik);

      }

 }

background image

Open/close principle

• Treść: Elementy oprogramowania powinny być otwarte na 

rozszerzenia, ale zamknięte na modyfikacje.

Opis: Podstawą dla tej zasady jest spostrzeżenie, że każdy kod (klasa, 

moduł, itp.) zmienia się z czasem i ma więcej niż jedną wersję. Dobre 

stosowanie tej zasady powoduje, że wprowadzenie rozszerzeń w jednej 

klasie nie spowoduje całej kaskady zmian w innych klasach.

Trzeba zatem tworząc kod od razu mieć na uwadze możliwość 

przyszłych rozszerzeń. Trzeba przewidzieć możliwość tworzenia 

rozszerzeń i zmiany zachowania kodu, ale nie powinno się to odbywać 

poprzez zmianę już istniejącego kodu.

Można powiedzieć, że OCP to główna zasada projektowania 

obiektowego, która pozwala spełniać jego slogany reklamowe 

(flexibility, reusability, maintainability).

Przykład: stosowanie się do tej zasady zazwyczaj oznacza bazowanie 

na abstrakcji (klasy abstrakcyjne) i stosowanie polimorfizmu 

(oczywiście nie tylko do tego). Dobrym przykładem złamania tej zasady 

jest tworzenie logiki zawierającej instrukcje „switch..case” 

rozróżniającej typy obiektów.

background image

Open/close principle

class

 Shape

    {
         

private

 Punkt _center;

        

#region

 Center Get / Set

    }

class

 Circle : Shape 

    {
        

private

 

int

 _radius;

        

#region

 Radius Get / Set

    }

class

 Square : Shape 

    {
        

private

 

int

 _side;

        

#region

 Side Get / Set

    }

background image

Open/close principle

 

class

 DrawManager

 {
   

private

 List

<

Shape

shapeList;       

   

public

 

DrawManager(){…}

   

public

 

void

 add(Shape s){…}

   public

 

void

 drawShapes(){->}

 

 

   private

 

void

 drawCirle(Circle c){..}

   

private

 

void

 drawSquare(Square sq){..}

}

public

 

void 

drawShapes()

  { 
    foreach(Shape s 

in

 shapeList)

     {
         

if

 (s is Circle)

         {                          

drawCirle((s as Circle));

         }
          

else

 

if

 (s is Square)

         {                      

drawSquare((s as Square));

         }
      }
   }

background image

Open/close principle

 

abstract

 

class

 Shape

    {
        

private

 Punkt _center;

   public

 

abstract

 

void

 Draw();

    };

    

class

 Circle : Shape 

    {
         

private

 

int

 _radius;

    public

 override 

void

 Draw()

    }

    

class

 Square : Shape 

    {
        

private

 

int

 _side;

   public

 override 

void

 Draw()

    }

    

class

 DrawManager

    {
        

//......

   public

 

void

 drawShapes()

        {
            foreach (Shape s 

in

 _shapeList)

                s.Draw();
        }

        //......

    }

background image

OCP - metody

• Podstawowymi mechanizmami 

umożliwiającymi stosowanie zasady 
OCP są  abstrakcja i polimorfizm.

• W językach programowania ze 

statyczną obsługą typów (C#) ważny 
jest mechanizm dziedziczenia 
(hierarchia).

background image

OCP na koniec

• Stosowanie programowania 

obiektowego, bezmyślne wykorzystanie 
abstrakcji we wszelkich możliwych 
miejscach to nie jest zasada OCP. 

• Najlepszym wyjściem jest definiowanie 

abstrakcji tylko w tych obszarach 
programowania, co do których istnieje 
przypuszczenie szczególnie częstych 
zmian. 

background image

Problem testowy

• CA14
• Lsp+Ocp.doc

background image

Liskov Substitution 

Principle 

• Treść: [W hierarchii dziedziczenia] podtypy muszą 

być „podstawialne” za typy bazowe.

Opis: Zasada ta umożliwia wykrycie źle stworzonej 

hierarchii dziedziczenia, w szczególności źle 

zaimplementowaną klasę pochodną. Chodzi o to, 

żeby wszystkie podtypy zachowywały się tak jak 

zakłada się, że zachowuje się typ bazowy. 

Zazwyczaj domyślnie się zakłada, że za typ bazowy 

można podstawić klasę pochodną i zachowanie 

będzie zmodyfikowane, ale będzie poprawne z 

punktu widzenia „klientów” klasy bazowej.

Zasada ta jest nawet silniejsza niż podstawowa 

zasada dziedziczenia „is-a” (jest).

background image

Liskov Substitution 

Principle

•  Zasada Liskov Substitution Principle (LSP) 

wprowadza wskazówki dotyczące projektowania 

używając dziedziczenia. Została sformułaowana 

przez Barbarę Liskov i w wolnym tłumaczeniu 

brzmi następująco:

Jeżeli dla każdego obiektu o1 typu S istnieje 

obiekt o2 typu T, taki że dla wszystkich 

programów P zdefiniowanych używając T, 

zachowanie P pozostaje niezmienne po zamianie 

o1 na o2 to typ S jest podtypem typu T.

background image

Przecież kwadrat jest (IS-A) 

prostokątem

public

 

class

 Rectangle

{

    

private

 

double

 width;

    

private

 

double

 height;

    

    

public virtual double

 Width

    {

        

get

 { 

return

 width; }

        

set

 { width = value;}

    }

    

    

public

 virtual 

double

 Height

    {

        

get

 { 

return

 height; }

        

set

 { height = value;}

     }

    

}

 public class Square: Rectangle
    {
        public override double Width
        {
            set
            {
                base.Width = value;
                base.Height = value;
            }
        }
         public override double Height
        {
            set
            {
                base.Height = value;
                base.Width = value;
            }
        }
    }
   public class RectangleTests
    {
        public void AreaOfRectangle()
        {
            Rectangle r = new Rectangle();//wstawmy Square
            
            r.Width = 5;
            r.Height = 2;
            if (r.Area() != 10) throw new Exception("ZŁe pole");
        }
    }

background image

Relacja IS-A

• LSP mówi, że ta relacja musi 

zachowywać zachowania 

• Zachowanie prostokąta: w czasie 

zmiany jednego z boków, drugi nie 
może być zmieniony

• Zachowanie kwadratu: gdy 

zmieniamy jeden bok to drugi też

background image

DBC

• Sposób projektowania 

oprogramowania zaproponowany 
przez Bertranda Meyera w latach 80

• Wspierany przez język Eiffel
• Idea: 
• Kontrakt między klientem (miejsce 

wywołania procedury) i dostawcą 
(procedurą)

background image

Projektowanie przez 

kontrakt

• Warunki wstępne
• Warunki końcowe
• Przestrzeganie kontraktu - asercje
• Dla prostokąta i kwadratu
• WW - puste
• Width=w;
• WK : width==w && 

height==old.height

background image

Proste i odcinki

public class Prosta
{
  private Punkt p1;
  private Punkt p2;

  public Prosta(Punkt p1, Punkt p2)

{this.p1=p1; this.p2=p2;}

  public Punkt P1 { get { return p1; } }
  public Punkt P2 { get { return p2; } }
  public double Slope { get {/* kod */} }
  public double YIntercept { get {/* kod 

*/} }

  public virtual bool JestNa(Punkt p) {/* 

kod */}

}

public class Odcinek : Prosta
{
  public Odcinek(Punkt p1, Punkt 

p2) : base(p1, p2) {}

  public double Length() {

 get {/* kod */} }

  public override bool 

JestNa(Punkt p) {/* kod */}

}

background image

I co teraz – naruszenie LSP

• Przymknąć oko. W końcu czy musimy 

używać klasy bazowej w miejsce 
pochodnej?

• Ale to świadczy o niezbyt wysokiej 

jakości naszego kodu. Trzeba 
przemyśleć.

• Wyodrębnić (factor) !

background image

To jest to

public abstract class ObiektLiniowy
{
  private Punkt p1;
  private Punkt p2;

  public ObiektLiniowy(Punkt p1, Punkt p2)
  {this.p1=p1; this.p2=p2;}

  public Punkt P1 { get { return p1; } }
  public Punkt P2 { get { return p2; } }

  public double Slope { get {/* kod */} }
  public double YIntercept { get {/* kod 

*/} }

  public virtual bool IsOn(Punkt p) {/* kod 

*/}

}

public class Prosta : ObiektLiniowy
{
  public Prosta(Punkt p1, Punkt p2) : 

base(p1, p2) {}

  public override bool IsOn(Punkt p) {/* kod 

*/}

}
public class Odcinek : ObiektLiniowy
{
  public Odcinek(Punkt p1, Punkt p2) : 

base(p1, p2) {}

  public double GetLength(){/* kod */}
  public override bool IsOn(Punkt p) {/* kod 

*/}

}
//Promień

background image

Klasyczne naruszenie LSP

• class Bazowa
• {

public virtual void f() {/*jakiś kod*/}

• }
• class Pochodna
• {
•  public override void f(){}
• }

background image

OCP a LSP

• OCP jest jedną z podstawowych reguł 

programowania obiektowego

• LSP jest jednym z warunków OCP
• Czynnikiem który naprawdę definiuje 

podtyp, jest możliwość zastępowania 
wynikająca z jawnego bądź 
niejawnego kontraktu.

background image

Dependency-Inversion 

Principle 

• Treść: Moduły wysokopoziomowe nie powinny zależeć 

od modułów niskopoziomowych. Oba powinny zależeć 

od abstrakcji. Abstrakcje nie powinny zależeć od 

detali. Detale powinny zależeć od abstrakcji.

Opis: Główną ideą tej zasady jest wymuszenie, aby 

moduły czy klasy nie zależały od bardziej 

niskopoziomowych elementów, a raczej, aby 

niskopoziomowe klasy czy moduły bazowały na 

klasach abstrakcyjnych, czy wyższych modułach. 

Dzięki temu niskopoziomowe zmiany nie będą miały 

wpływu na wyższy poziom aplikacji. Zastosowanie tej 

zasady najważniejsze jest przy podziale aplikacji na 

warstwy, ale także powinno się ją stosować przy 

projektowaniu klas.

background image

Dependency-Inversion 

Principle 

• Tam gdzie to tylko możliwe bądź zależny 

od abstrakcji a nie konkretów. Czyli jeśli 
chcesz mieć w klasie pole typu lista, to nie 
deklaruj LinkedList, ArrayList ani nic 
takiego, tylko po prostu List. To ułatwi ci 
refaktoryzację jeśli okaże się, że trzeba 
skorzystać z innej implementacji. 

• Typowym naruszeniem DIP jest pobieranie 

z formy danych (grid) i obliczanie np. sumy 
w tej samej funkcji 

background image

Dependency-Inversion 

Principle

class B

{

   public void ZrobCos() { }

}

class A

{

  public void Metoda()

  {

    B b = new B();

    b.ZrobCos();

  }

}

Jakakolwiek zmiana w klasie B może mieć wpływ na zachowanie klasy A. Poza tym, klasa A może tylko sterować 

zachowaniem B. Jak to zmienić?

interface IB

{

  void ZrobCos() { }

}

class B : IB

{

  public void ZrobCos() { }

}

class A

{

  public void Metoda()

  {

    IB b = new B();

   b.ZrobCos();

  }

}

Co osiągnęliśmy?

-klasa A może sterować dowolnymi klasami implementującymi interface IB

background image

Dependency-Inversion 

Principle

background image

Interface Segregation 

Principle 

• Treść: klient nie powinien być zmuszony do 

zależności od metod, których nie używa.

Opis: jest to chyba najrzadziej łamana zasada 

ze wszystkich dotychczas przedstawionych. 

Postuluje ona, aby interfejsy były jak 

najbardziej skupione na jednym celu, a tym 

samym nie były „przeładowane” metodami. 

Jeżeli klasa implementuje interfejs, który ma 

bardzo wiele metod to możliwe jest, że nie 

wszystkie będzie wykorzystywać, a co gorsze 

będzie musiała, co spowoduje złamanie SRP.

background image

Interface Segregation 

Principle -naruszenie 

• Najprostszym złamaniem ISP byłoby 

umieszczenie w jednym interfejsie metod 

służących do operacji graficznych (np.: 

Draw(), Rotate()) oraz odpowiedzialnych za 

trwałość (np.: Save(), Load()).

• Każdorazowo, gdy implementujemy 

metodę interfejsu (bo musimy) i 

zostawiamy ją pustą to oznacza 

nadmiernie rozbudowany interfejs.

• Interface pollution

background image

Document Outline