SOLID

SOLID cz. II – Zasada otwarte/zamknięte

By on 15 lutego 2017

Drugą zasadą, czyli literą O w słowie SOLID jest zasada otwarte/zamknięte (Open/Closed Principe). Najłatwiej opisuje ją zdanie:

„otwarty na rozbudowę, zamknięty na modyfikacje”

Oznacza to że powinno dać się zmienić zachowanie obiektu bez zmiany jego kodu, czyli dodać nową funkcjonalność bez konieczności modyfikacji już istniejących.

Najlepiej jest uczyć się na przykładach, więc najpierw prosty książkowy przykład łamiący zasadę otwarte/zamknięte.

class Square{
  int a;

  public Square(int a){
    this.a = a;
  }
}

class Trapeze{
  int a;
  int b;
  int h;
  
  public Trapeze(int a, int b, int h){
    this.a = a;
    this.b = b;
    this.h = h;
  }
}

class PolygonHelper{
  public static double area(Object polygon){
    if(polygon instanceof Square)
    {
      Square square = (Square) polygon;
      return square.a * square.a;
    }
    else if(polygon instanceof Trapeze)
    {
      Trapeze trapeze = (Trapeze) polygon;
      return (trapeze.a + trapeze.b) * trapeze.h / 2.0;
    }
    else
    {
      throw new MyOwnException("No such polygon");
    }
}

public class MainActivity extends Activity{
  @Override
  public void onCreate(Bundle savedInstanceState) 
  {
    super.onCreate(savedInstanceState);

    List<Object> polygons = new ArrayList<Object>();
    polygons.add(new Square(2));
    polygons.add(new Trapeze(2, 3, 1));

    for(Object polygon : polygons){
      double area = PolygonHelper.area(polygon);
    }
  }
}

Teraz zatrzymaj się na chwilę, na spokojnie przeanalizuj kod i pomyśl co będziesz musiał zrobić, aby dodać nową figurę. Nie wystarczy dodanie nowej klasy np. Rectangle, ale również będziesz musiał edytować klasę PolygonHelper, aby uwzględnić w warunku nową funkcjonalność.

Można temu zapobiec stosując pomocniczy interfejs.

public interface IArea{
  double area();
}

public class Square implements IArea{
  int a;

  public Square(int a){
    this.a = a;
  }

  double area(){
    return a*a;
  }
}

public class Trapeze implements IArea{
  int a;
  int b;
  int h;

  public Trapeze(int a, int b, int h){
    this.a = a;
    this.b = b;
    this.h = h;
  }

  double area(){
    return (a + b) * h /2 .0;
  }
}

public class MainActivity extends Activity{
  @Override
  public void onCreate(Bundle savedInstanceState) 
  {
    super.onCreate(savedInstanceState);

    List<IArea> polygons = new ArrayList<IArea>();
    polygons.add(new Square(2));
    polygons.add(new Trapeze(2, 3, 1));

    for(IArea polygon : polygons){
      double area = polygon.area();
    }
  }
}

W ten sposób uzyskaliśmy bardziej uniwersalny kod. Wszystkie figury łączy to że mają powierzchnię. Każda z figur implementuje interfejs IArea i sama decyduje jak wylicza swoją powierzchnię. W tej chwili dodanie nowej figury sprowadza się wyłącznie do dodania nowej klasy, a więc kod jest gotowy na rozbudowę:

public class Rectangle implements IArea{
  int a;
  int b;

  public Rectangle(int a, int b){
    this.a = a;
    this.b = b;
  }

  double area(){
    return a*b;
  }
}

Nie musieliśmy edytować żadnego kodu, więc kod jest zamknięty na modyfikacje.
Aby użyć policzyć pole prostokąta wystarczy dopisać:

polygons.add(new Rectangle(2, 4));

Jako pracę domową dopisz również obsługę liczenia obwodu figur.

TAGS
RELATED POSTS

LEAVE A COMMENT