пятница, 28 декабря 2018 г.

Принцип Открытости/Закрытости


Введение
Принцип открытости закрытости (OCP) говорит о том, что элементы программного обеспечения (такие как классы, модули, функции и т.д.) должны быть открыты для расширения, но закрыты для модификации. Это значит, что подобные элементы позволяют расширять свое поведение без модификации исходного кода.
Класс закрыт, так как он может быть скомпилирован, хранится в библиотеке, и использоваться клиентскими классами. Но также он открыт, так как любой новый класс может использовать его в качестве родителя, и добавлять новые возможности. Когда наследуемый класс объявлен, то нет нужды изменять оригинал или беспокоить его клиентов.


Практические примеры
Нарушение OCP
Давайте скажем, что у нас есть класс Rectangle. Как большинство прямоугольников которые нам встречались, у него его высота и ширина.


     
class Rectangle

{
    public $width;
    public $height;
}

Теперь наш заказчик хочет сделать приложение в котором можно посчитать площадь коллекции прямоугольников.
Это не проблема для нас. Мы еще в школе учили, что прощадь прямоугольника – это умножение ширины на высоту.





Class AreaCalculator

{
    /**
     * Calculate the total area of the rectangles.
     *
     * @param  Illuminate\Support\Collection  $rectangles
     * @return float
     */
    public function Area($rectangles)
    {
        $area = 0;
        $rectangles->each(function ($rectangle) use (&$area) {
            $area += ($rectangle->width * $rectangle->height);
        });
        return $area;
    }
}

Мы представим наше решение в виде класса AreaCalculator и получим похвалу от заказчика. Но кроме похвалы ему бы хотелось добавить возможность вычисления площади не только для прямоугольников, но так же и для круга.
Это усложняет нашу задачу, но после непродолжительных размышений, мы приходим к решению, где меняем наш метод Area таким образом, что теперь он принимает коллекцию классов вместо более специфичного типа Rectangle.


class
AreaCalculator

{
    /**
     * Calculate the total area of the shapes.
     *
     * @param  Illuminate\Support\Collection  $shapes
     * @return float
     */
    public function Area($shapes)
    {
        $area = 0;
        $shapes->each(function ($shape) use (&$area) {
            switch (gettype($shape)) {
                case "Rectangle":
                    $area += ($shape->width * $shape->height);
                    break;
                case "Circle":
                    $area += (PI * pow($circle->radius, 2));
                    break;
            }
        });
        return $area;
    }
}

 Код представленный выше удовлетворяет спецификации и позволяет вычислить общую площадь для коллекции прямоугольников и кругов. Есть только проблема, если заказчик захочет впоследствии добавить треугольники, пятиугольники и полигоны. Нам придется написать код для каждого отдельного случая, который заказчик хочет поддержать.

Соблюдение OCP
Вот самый простой способ для этого. Мы знаем что для каждой фигуры мы хотим вычислить площадь. Для того что бы алгоритм работал хорошо, мы можем создать интерфейс, который назовем Shape, таким образом все фигуры должны поддерживать метод getArea, и мы можем сказать с уверенностью, что наш код не словается и его можно расширять.


interface
Shape

{
    /**
     * Calculate the area of the shape.
     *
     * @return mixed
     */
    public function getArea();
}

class Rectangle implements Shape
{
    public $width;
    public $height;

    public function getArea()
    {
        return $this->width * $this->height;
    }
}

class Circle implements Shape
{
    public $radius;

    public function getArea()
    {
        return pow($this->radius, 2) * PI;
    }
}

/**
     * Calculate the total area of the shapes.
     *
     * @param  Illuminate\Support\Collection  $shapes
     * @return float
     */
    public function Area($shapes)
    {
        $area = 0;
        $shapes->each(function ($shape) use (&$area) {
           $area += $shape->getArea();
        });
        return $area;
    }
}
Теперь наш код представленный выше будет всегда работать с любым элементом который реализует интерфейс Shape.
Это демонстрирует принцип открытости/закрытости – код закрыт для модификации, посколько основная функциональность уже написана, но открыт дя расширения, поскольку теперь мы можем передать любую фигуру, которая реализует интерфейс Shape.
Выводы
Принцип открытости/закрытости разработан для того что бы вы писали ваш код в такой манере, что основная функциональность была бы однозначной и локаничной, насколько это возможно.
Пример показанный выше просто берет коллекцию фигур и вычисляет их площадь. Нам не нужно исследовать эти медоты и менять их логику вычисления площади, но мы можем добавить новые фигуры, и они будут обработаны все без исключения.