Главная страница
Навигация по странице:

  • 7. Принцип инверсии зависимостей (DIP). Формулировка и мотивация. Пример нарушения и устранение.

  • MySQLConnection

  • PasswordReminder зависит от класса MySQLConnection .Если позже изменить ядро базы данных, то придется менять и класс PasswordReminder

  • connect

  • OCP не нарушается

  • 19. Шаблон проектирования Фасад (Facade). Назначение. Пример использования.

  • Даб работа по методам программирования. обновлено пронько. 1. Классы в C#. Модификаторы доступа. Поля, свойства, индексаторы


    Скачать 0.78 Mb.
    Название1. Классы в C#. Модификаторы доступа. Поля, свойства, индексаторы
    АнкорДаб работа по методам программирования
    Дата11.03.2023
    Размер0.78 Mb.
    Формат файлаdocx
    Имя файлаобновлено пронько.docx
    ТипДокументы
    #980160
    страница4 из 6
    1   2   3   4   5   6

    14. Принцип открытости/закрытости (OCP). Формулировка и мотивация. Пример нарушения и устранение.

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

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

    Следование принципу OCP заключается в том, что программное обеспечение изменяется не через изменение существующего кода, а через добавление нового кода. То есть созданный изначально код остаётся «нетронутым» и стабильным, а новая функциональность внедряется либо через наследование реализации, либо через использование абстрактных интерфейсов и полиморфизм.

    Принцип открытости/закрытости Мейера основывается на идее, что разработанная изначально реализация класса в дальнейшем не модифицируется (разве что исправляются ошибки), а любые изменения производятся через создание нового класса, который обычно наследуется от изначального. Согласно определению Мейера реализация интерфейса может быть унаследована и переиспользована, но интерфейс может и измениться в новой реализации.

    Позже был сформулирован полиморфный принцип открытости/закрытости. Он основывается на строгой реализации интерфейсов и на наследовании от абстрактных базовых классов или на полиморфизме. Созданный изначально интерфейс должен быть закрыт для модификаций, а новые реализации как минимум соответсвуют этому изначальному интерфейсу, но могут поддерживать и другие, более расширенные.

    Пример нарушения принципа.

    В данном примере при добавлении новой фигуры придется также изменить метод Calclulate класса AreaCalculator.

    using System;

    using System.Linq;
    class AreaCalculator

    {

    private object[] _shapes;
    public AreaCalculator(object[] shapes)

    {

    _shapes = shapes;

    }
    public double Calculate()

    {

    double[] areas = new int[_shapes.Length] {};
    for (int i = 0; i < _shapes.Length; i++)

    {

    object shape = _shapes[i];
    double area = shape switch {

    Square square => Math.Pow(square.Length, 2),

    Circle circle => 2 * Math.PI * circle.Radius,

    _ => throw new ArgumentException("Invalid shape", nameof(shape)),

    };
    areas.SetValue(area, i);

    }
    return areas.Sum();

    }

    }
    class Square {

    public int Length { get; }
    public Square(int length)

    {

    Length = length;

    }

    }
    class Circle {

    public int Radius { get; }
    public Circle(int radius)

    {

    Radius = radius;

    }

    }


    Чтобы этого избежать можно создать абстрактный класс фигуры и вынести подсчет площади в этот класс в качестве абстрактного метода.

    using System;

    using System.Linq;
    class AreaCalculator

    {

    private shapes[] _shapes;
    public AreaCalculator(shapes[] shapes)

    {

    _shapes = shapes;

    }
    public double calculate()

    {

    double area = 0;
    for (int i = 0; i < _shapes.Length; i++)

    {

    area += _shapes[i].CaclArea();

    }
    return area;

    }

    }
    abstract class Shape

    {

    public abstract double CaclArea();

    }
    class Square : Shape {

    private readonly int _length;
    public Square(int length)

    {

    _length = length;

    }
    public override double CaclArea()

    {

    return Math.Pow(_length, 2);

    }

    }
    class Circle : Shape {

    private readonly int _radius;
    public Circle(int radius)

    {

    _radius = radius;

    }
    public override double CaclArea()

    {

    return 2 * Math.PI * _radius;

    }

    }


    15. Принцип подстановки Barbara Liskov (LSP). Формулировка и мотивация. Пример нарушения и устранение.

    Принцип подстановки Лисков (LCP)

    Данный принцип гласит, что «вы должны иметь возможность использовать любой производный класс вместо родительского класса и вести себя с ним таким же образом без внесения изменений». Этот принцип прост, но очень важен для понимания. Класс Child не должен нарушать определение типа родительского класса и его поведение.

    В чем же смысл этого! Посмотрите на следующее изображение:



    Employee является родительским классом, а Senior и Junior дочерними классами, унаследованными от Employee. Теперь рассмотрим пример:

    using System;
    namespace SOLID

    {
        public abstract class Employee

        {

            public virtual string GetWorkDetails(int id)

            {

                return "Base Work";

            }
            public virtual string GetEmployeeDetails(int id)

            {

                return "Base Employee";

            }

        }
        public class SeniorEmployee : Employee

        {

            public override string GetWorkDetails(int id)

            {

                return "Senior Work";

            }
            public override string GetEmployeeDetails(int id)

            {

                return "Senior Employee";

            }

        }
        public class JuniorEmployee : Employee

        {

            // Допустим, для Junior’a отсутствует информация

            public override string GetWorkDetails(int id)

            {

                throw new NotImplementedException();        }
           

            public override string GetEmployeeDetails(int id)

            {

                return "Junior Employee";
            }

        }

    }

    Возможно вам покажется что с данным кодом все в порядке. Однако, проанализируйте следующий код:

    List list = new List();
    list.Add(new JuniorEmployee());

    list.Add(new SeniorEmployee());
    foreach (Employee emp in list)

    {

        emp.GetEmployeeDetails(985);

    }

    Теперь у нас есть проблема. Для JuniorEmployee невозможно вернуть информацию о работе, поэтому вы получите необработанное исключение, что нарушит принцип LSP. Для решения этой проблемы в C# необходимо просто разбить функционал на два интерфейса IWork и IEmployee:

    public interface IEmployee

    {

        string GetEmployeeDetails(int employeeId);

    }
    public interface IWork

    {

        string GetWorkDetails(int employeeId);

    }
    public class SeniorEmployee : IWork, IEmployee

    {

        public string GetWorkDetails(int employeeId)

        {

            return "Senior Work";

        }
        public string GetEmployeeDetails(int employeeId)

        {

            return "Senior Employee";

        }

    }
    public class JuniorEmployee : IEmployee

    {

        public string GetEmployeeDetails(int employeeId)

        {

            return "Junior Employee";

        }

    }

    Теперь JuniorEmployee требует реализации только IEmployee, а не IWork. При таком подходе будет поддерживаться принцип LSP. Также на примерах была рассмотрена как правильная реализация, так и неправильная.


    16. Принцип разделения интерфейсов (ISP). Формулировка и мотивация. Пример нарушения и устранение.

    Interface Segregation Principle (ISP). Зависимости, несущие лишний груз ненужных и неиспользуемых особенностей, могут стать причиной неожиданных проблем. Если кратко, то опасно создавать зависимости от модулей, содержащих больше, чем требуется.

    Пример нарушения:



    В данной ситуации имеется несколько классов, пользующихся операциями в классе OPS. Допустим, что User1 использует только операцию op1, User2 — только op2 и User3 — только op3. Очевидно, что в такой ситуации исходный код User1 непреднамеренно будет зависеть от op2 и op3, даже при том, что он не пользуется ими. Эта зависимость означает, что изменения в исходном коде метода op2 в классе OPS потребуют повторной компиляции и развертывания класса User1, несмотря на то что для него ничего не изменилось.

    Вариантом устранения служит добавление интерфейсов, которые четко определяют доступные методы и не дают доступ к остальным:



    17. Принцип инверсии зависимостей (DIP). Формулировка и мотивация. Пример нарушения и устранение.

    Принцип инверсии зависимостей (англ. Dependency Inversion Principle, DIP) — важный принцип объектно-ориентированного программирования, используемый для уменьшения связанности в компьютерных программах. Входит в пятёрку принципов S.O.L.I.D.

    Формулировка:

    • Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.

    • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

    class PasswordReminder {

        private $dbConnection;
        public function __construct(MySQLConnection $dbConnection)

        {        

            $this->dbConnection = $dbConnection;

        }

    }

    MySQLConnection является низкоуровневым модулем, PasswordReminder - высокоуровневый. Но в соответствии с определением принципа, гласящим разделять абстракции от реализации, этот фрагмент его нарушает, т.к. класс PasswordReminder зависит от класса MySQLConnection.

    Если позже изменить ядро базы данных, то придется менять и класс PasswordReminder, что нарушает принцип открытости / закрытости.

    Класс PasswordReminder не должен беспокоиться об используемой СУБД. Для исправления этого мы должны выделить интерфейс, чтобы низкоуровневые и высокоуровневые модули зависели от абстракции:

    interface DBConnectionInterface {

         public function connect();

    }

    Интерфейс имеет метод connect и класс MySQLConnection реализует его. Также вместо проверки типа на пренадлежность передаваемого объекта классу MySQLConnection в конструкторе PasswordReminder, мы используем проверку принадлежности интерфейсу. И класс PasswordReminder больше не беспокоится о типе СУБД, которая будет использована, главное, что есть возможность соединения и принцип OCP не нарушается.

    class MySQLConnection implements DBConnectionInterface {

         public function connect() {

             return "Database connection";

         }

    }

     

    class PasswordReminder {

         private $dbConnection;

     

         public function __construct(DBConnectionInterface $dbConnection) {

             $this->dbConnection = $dbConnection;

         }

    }

    Теперь оба модуля (низкоуровневый и высокоуровневый) зависят от абстракции.

    18. Инверсия потока управления (IoC). Инъекция зависимостей (DI). Формулировка и мотивация. Пример использования.

    Инверсия управления - является ключевой частью того, что различает фреймворк и библиотеку.

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

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

    Для начала давайте взглянем на пример кода без применения инверсии управления:

    1. class TodoRepository {

    2. // other code

    3. }

    4.  

    5. class TodoService {

    6. TodoRepository todoRepository = new TodoRepository();

    7. // other code

    8. }

    Как видно из этого кода, класс TodoService самостоятельно создаёт объект класса TodoRepository. Это образует сильную связь между классами TodoService и TodoRepository. Если в классе TodoService потребуется использование другого класса вместо TodoRepository, то придётся вносить соответствующие изменения в него. Даже если из TodoRepository выделить интерфейс, связанность между классами слабее не станет, так как TodoService самостоятельно создаёт объект TodoRepository.

    Применение инверсии управления предполагает, что объект класса TodoRepository должен быть создан за пределами класса TodoService, но в дальнейшем должен быть передан объекту этого класса.

    Внедрение зависимостей

    Первый способ это сделать — применить внедрение зависимостей. Внедрение зависимостей реализуется несколькими способами, среди которых можно выделить:

    • Внедрение через конструктор

    • Внедрение через set-метод

    • Внедрение через интерфейс

    Приведем пример внедрения зависимости через конструктор:

    1. class TodoService {

    2. TodoRepository todoRepsitory;

    3. TodoService(TodoRepository todoRepository) {

    4. this.todoRepository = todoRepository;

    5. }

    6. }

    7.  

    8. class Application {

    9. public static void main(String[] args) {

    10. TodoRepository todoRepository = new TodoRepository();

    11. TodoService todoService = new TodoService(todoRepository);

    12. }

    13. }

    Мотивация

    Инверсия управления - один из популярных принципов объектно-ориентированного программирования, при помощи которого можно снизить связанность между компонентами, а так же повысить модульность и расширяемость ПО.

    19. Шаблон проектирования Фасад (Facade). Назначение. Пример использования.

    Фасад (Facade) представляет шаблон проектирования, который позволяет скрыть сложность системы с помощью предоставления упрощенного интерфейса для взаимодействия с ней.

    Данный шаблон проектирования используется:

    • Когда имеется сложная система, и необходимо упростить с ней работу. Фасад позволит определить одну точку взаимодействия между клиентом и системой.

    • Когда надо уменьшить количество зависимостей между клиентом и сложной системой. Фасадные объекты позволяют отделить, изолировать компоненты системы от клиента и развивать и работать с ними независимо.

    • Когда нужно определить подсистемы компонентов в сложной системе. Создание фасадов для компонентов каждой отдельной подсистемы позволит упростить взаимодействие между ними и повысить их независимость друг от друга.

    Пример UML диаграммы:



    Пример использования:

    class SubsystemA

    {

    public void A1()

    {}

    }

    class SubsystemB

    {

    public void B1()

    {}

    }

    class SubsystemC

    {

    public void C1()

    {}

    }

    public class Facade

    {

    SubsystemA subsystemA;

    SubsystemB subsystemB;

    SubsystemC subsystemC;

    public Facade(SubsystemA sa, SubsystemB sb, SubsystemC sc)

    {

    subsystemA = sa;

    subsystemB = sb;

    subsystemC = sc;

    }

    public void Operation1()

    {

    subsystemA.A1();

    subsystemB.B1();

    subsystemC.C1();

    }

    public void Operation2()

    {

    subsystemB.B1();

    subsystemC.C1();

    }

    }

    class Client

    {

    public void Main()

    {

    Facade facade = new Facade(new SubsystemA(), new SubsystemB(), new SubsystemC());

    facade.Operation1();

    facade.Operation2();

    }

    }

    Участники:

    • Классы SubsystemA, SubsystemB, SubsystemC и т.д. являются компонентами сложной подсистемы, с которыми должен взаимодействовать клиент

    • Client взаимодействует с компонентами подсистемы

    • Facade - непосредственно фасад, который предоставляет интерфейс клиенту для работы с компонентами
    1   2   3   4   5   6


    написать администратору сайта