CO jak Ćwiczenia Open/closed principle, czyli Zasada otwarte-zamknięte

przez | 10 lutego, 2020

Open/closed principle – Zasada otwarte-zamknięte

Wszystkie klasy powinny być otwarte na rozszerzenia, ale zamknięte na modyfikacje.

Jak wykorzystywać w praktyce OCP?

Warto jest stosować interfejsy oraz operować na abstrakcji, a nie uzależniać się od szczegółów implementacyjnych.

Trudności związane z OCP

Ciężko jest czasami zdecydować, co jest szczegółem implementacyjnym oraz w którą stronę będzie rozszerzała się nasza aplikacja.

Przykład

Ćwiczenia tej metody wykonałem na bardzo prostym przykładzie. Mamy kalkulator, który ma za zadanie obliczyć powierzchnię figur. W pierwszej wersji używa on switcha, do rozpoznania, jaka jest to klasa.

class AreaCalculator
 {
     private readonly List<object> figures;
     public AreaCalculator(List<object> figures)
     {
         this.figures = figures;
     }
     public double Calculate()
     {
         double area = 0;
         foreach (var figure in figures)
         {
             switch (figure)
             {
                 case Square square:
                     area += square.GetWidth() * square.GetWidth();
                     break;
                 case Rectangle rectangle:
                     area += rectangle.GetWidth() * rectangle.Getheight();
                     break;
                 case Circle circle:
                     area += Math.PI * Math.Pow(circle.GetRadius(), 2);
                     break;
                 default:
                     area += area;
                     break;
             }
         }
         return area;
     }
 }
 
 class Square
 {
     private readonly double width;
     public Square(double width)
     {
         this.width = width;
     }
     internal double GetWidth()
     {
         return width;
     }
 }
 class Rectangle
 {
     private readonly double width;
     private readonly double height;
 
     public Rectangle(double width, double height)
     {
         this.width = width;
         this.height = height;
     }
     internal double GetWidth()
     {
         return width;
     }
     internal double Getheight()
     {
         return height;
     }
 }
 class Circle
 {
     private readonly double radius;
     public Circle(double radius)
     {
         this.radius = radius;
     }
     internal double GetRadius()
     {
         return radius;
     }
 }
 class Program
 {
     static void Main(string[] args)
     {
         List<object> figures = new List<object>()
         {
             new Square(5),
 
         };
         AreaCalculator areaCalculator = new AreaCalculator(figures);
 
         Debug.Assert(areaCalculator.Calculate() == 25);
 
         List<object> figures2 = new List<object>()
         {
             new Square(5),
             new Rectangle(5,3),
 
         };
         Debug.Assert(new AreaCalculator(figures2).Calculate() == (25 + 15));
         List<object> figures3 = new List<object>()
         {
             new Square(5),
             new Rectangle(5,3),
             new Circle(5),
 
         };
         Debug.Assert(new AreaCalculator(figures3).Calculate() 
           == (25 + 15 + Math.PI * 25));
     }
 }

W drugim przypadku klasy figur implementują interfejs, który ma metodę do liczenia pola powierzchni klasy. Każda klasa sama implementuję liczenie swojego pola powierzchni. Kalkulator jedynie sumuje pola powierzchni.

class AreaCalculator
   {
       private readonly IEnumerable<IFigureAreaCalculator> figures;
       public AreaCalculator(IEnumerable<IFigureAreaCalculator> figures)
       {
           this.figures = figures;
       }
 
       public double Calculate()
       {
           return figures.Sum(x => x.GetArea());
       }
   }
   interface IFigureAreaCalculator
   {
       double GetArea();
   }
   class Square : IFigureAreaCalculator
   {
       private readonly double width;
       public Square(double width)
       {
           this.width = width;
       }
 
       public double GetArea()
       {
           return width * width;
       }
 
       internal double GetWidth()
       {
           return width;
       }
   }
   class Rectangle : IFigureAreaCalculator
   {
       private readonly double width;
       private readonly double height;
 
       public Rectangle(double width, double height)
       {
           this.width = width;
           this.height = height;
       }
       internal double GetWidth()
       {
           return width;
       }
       internal double Getheight()
       {
           return height;
       }
 
       public double GetArea()
       {
           return width * height;
       }
   }
   class Circle : IFigureAreaCalculator
   {
       private readonly double radius;
       public Circle(double radius)
       {
           this.radius = radius;
       }
 
       public double GetArea()
       {
           return Math.PI * Math.Pow(radius, 2);
       }
 
       internal double GetRadius()
       {
           return radius;
       }
   }
   class Program
   {
       static void Main(string[] args)
       {
           var figures = new List<IFigureAreaCalculator>()
           {
               new Square(5),
 
           };
           AreaCalculator areaCalculator = new AreaCalculator(figures);
 
           Debug.Assert(areaCalculator.Calculate() == 25);
 
           var figures2 = new List<IFigureAreaCalculator>()
           {
               new Square(5),
               new Rectangle(5,3),
 
           };
           Debug.Assert(new AreaCalculator(figures2).Calculate() == (25 + 15));
 
           var figures3 = new List<IFigureAreaCalculator>()
           {
               new Square(5),
               new Rectangle(5,3),
               new Circle(5),
 
           };
           Debug.Assert(new AreaCalculator(figures3).Calculate() == (25 + 15 + Math.PI * 25));
       }
   }

Dodanie nowej figury nie będzie wymagało zmiany w klasie AreaCalculator.

Wszystkie posty związane z mini projektem: Poznaj zasady SOLID i OOP:

Źródła

Obraz główny

Materiały

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *