Návrhové principy

Proč?

  • pomáhá předcházet špatnému design

Co působí špatný desing?

  1. ztuhlost - jakákoli změna je obtížná nebo je potřeba na mnoha místech
     
  2. křehkost - provedená úprava působí problémy , často na nesouvisejících místech
     
  3. znovu(ne)použitelnost - znovupoužití určité části je složitější než ji napsat znovu

S - Single responsibility principle

S.O.L.I.D

O - Open/Close principle

L - Liskov substitution principle

I - Interface segregation principle

D - Dependency inversion principle

Single responsibility principle

  • Každá třída/fce by měla mít právě jednu odpovědnost/důvod ke změně. 
  • HINT: Pokud zodpovědnost třídy/fce nelze popsat jednoduchou větou bez "a", pravděpodobně porušuje SRP.
  • Snižuje složitost systému, zvyšuje jeho pochopitelnost a čitelnost

Porušení SRP

class Book {
    private Name name;
    private Author author;
    private Content content;
 
    // ... getters
    // ... settters
 
    public void print() {
        // book printing code
    }
 
    public void read() {
        // book reading code
    }
}

Oprava podle SRP

class Book {
    private Name name;
    private Author author;
    private Content content;
 
    // ... getters
    // ... settters
}
class Printer {
    public void print(Book book) {
        // book printing code
    }
}
class Reader {
    public void read(Book book) {
        // book reading code
    }
}

Open/Close principle

  • Každá třída/fce by měla být otevřená pro rozšíření a uzavřená pro změny 
  • Zajistíme především pomocí abstrakce a polymorfismu

  • !!Vždy budou existovat změny vyžadující zásah do existujícího kódu, ale čím méně tím lépe

Porušení OCP

class Payment {
    public void pay(Method method, Money amount) {
        if (method.isCash()) {
            //do something
        } else if (method.isTransfer()) {
            //do something else
        } else {
            throw new IllegalArgumentException("Unknown payment option.");
        }
    }
}

Oprava podle OCP

interface Method {
    void confirmPaymentReceived(Money amount);
}
 
class TransferMethod implements Method { /* ...confirmPaymentReceived... */ }
class CashMethod implements Method { /* ...confirmPaymentReceived... */ }
class Payment {
    public void pay(Method method, Money amount) {
        method.confirmPaymentReceived(amount);
        printReceipt(amount);
        dispatchGoods();
    }
}

Liskov substitution principle

  • Všude kde je vyžadována bázová třída, musí být možné použít její podtřídy bez jejich znalostí
  • Definuje za jakých podmínek je možné vytvářet potomky bázových tříd abychom se nedostali do problémů.

Podmínky LSP

  1. Preconditions - musí platit před provedením kódu
  2. Postconditions - musí platit po provedení kódu
  3. Invariants - musí platit po celou dobu existence objektu
  1. Preconditions - nesmí být zesilovány
  2. Postconditions - nesmí být oslabovány
  3. Invariants - musí být zachovány

V podtřídách:

Porušení LSP

(nutná znalost)

class Shape {}

class Rectangle extends shape {}
 
class Circle extends shape {}

class Drawer {
    void draw(Shape shape) {
        if (shape instanceof Rectangle) {
            //draw
        } else if (shape instanceof Circle) {
            //draw
        }
    }
}

Oprava LSP

(nutná znalost)

class Shape {
    abstract void draw();
}

class Rectangle extends shape {
    @Override
    void draw() {
        //draw
    }
}
 
class Circle extends shape {
    @Override
    void draw() {
        //draw
    }
}

class Drawer {
    void draw(Shape shape) {
        shape.draw();
    }
}

Porušení LSP

(nezaměnitelnost)

class Rectangle {
    int width;
    int height;

    int getArea() {
        return width*height;
    }

    //set & get
}
 
class Square extends Rectangle {
    void setWidth(int width) {
        this.width = width;
        this.height = height;
    }

    void setHeight(int height) {
        setWidth(height);
    }
}
class Test {
    void test() {
        Rectangle rectangle =
            SomeFactory.getRectangle(); 

        rectangle.setWidth(5);
        rectangle.setHeight(10);

        rectangle.getArea();
        //4 Rectangle get 50
        //4 Squere get 100
    }
}

Oprava LSP

(nezaměnitelnost)

  • V setterech nastavuje vždy jen hodnotu které se setter týká. Přidáme kontrolu že čtverec má width==height 
  • Dědíme z obecnější třídy 'Shape' a fce getArea je abstraktní. Každý potomek si implemetuje fci sám bez přetěžování bázové třídy

Interface segregation principle

  • Každé rozhraní by mělo být co nejmenší
     
  • Žádná třída by neměl být nucena používat rozhraní, která nepoužívá

Porušení ISP

interface Lifecycle {
    void start();
    void stop();
}

Oprava ISP

interface Startable {
    void start();
}

interface Stoppable{
    void stop();
}

Dependency inversion principle

  • Všechny závisloti by měli vést jedním směrem. Od konkrétní k abstraktní.
     
  • Všechny závisloti by měly být na abstraktní třídy a rozhraní, nikdy na konkrétní implementaci
     
  • Přispívá k redukci závislostí

Porušení DIP

class Worker {
    void work(){
        //working
    }
}

class Manager {
    Worker worker;

    void manage(){
        worker.work();
    }

    //get & set
}
class SuperWorker {
    void work(){
        //working much more
    }
}

Oprava DIP

interface Worker {
    void work();
}

class LazyWorker implements Worker {
    void work(){
        //working
    }
}

class Manager {
    Worker worker;

    void manage(){
        worker.work();
    }

    //get & set
}
class SuperWorker implements Worker{
    void work(){
        //working much more
    }
}

Law of Demeter

(rozmlouvej s přáteli, ne s cizinci)

  • Snaha o co největší ukrytí vnitřní struktury objektů a tím snížení provázanosti tříd

Metoda f třídy C smí volat jen:

  • metody třídy C
  • metody objektů vytvořených metodou f
  • metody objektů předaných jako argumenty f
  • metody objektů, které jsou instanční proměnnou C

Příznaky porušení LoD

  • Zřetězené gettery/mnoho pomocných proměnných

value = object.getX().getY().getValue();

x = object.getX();
y = x.getY();
value = y.getValue();

Porušení LoD

public class Customer {
  private String name;
  private Wallet myWallet;
  //get & set
}

public class Wallet {
  private float value;

  public void addMoney(float deposit) {
   value += deposit;
  }

  public void subtractMoney(float debit) {
   value -= debit;
  }
  //get & set
}
payment = 2.00;
Wallet wallet = myCustomer.getWallet();
if (wallet.getTotalMoney() > payment) {
  wallet.subtractMoney(payment);
  //OK
} else {
  //NOK
}

Oprava podle LoD

public class Customer {
  private String name;
  private Wallet myWallet;

  public String getName(){
    return name;
  }

  public bool getPayment(float bill) {
    if (myWallet != null && myWallet.getTotalMoney() > bill) {
      myWallet.subtractMoney(payment);
      return true;
    } else {
     return false;
  }

}
payment = 2.00;
if (myCustomer.getPayment(payment)) {
  // OK
} else {
  // NOK
}

(Ne)Výhody LoD

+ snižování provázanosti tříd

+ třídá má kontrolu svého vnitřního stavu

+ snažší orientace ve třídách

- třída musí poskytovat metody pro manipulaci se svou vnitřní strukturou