Руководство по понятной, многоразовой, тестируемой, обслуживаемой и гибкой кодовой базе.

S.O.L.I.D - это аббревиатура, используемая в разработке программного обеспечения, которая описывает набор принципов объектно-ориентированного проектирования. Когда система реализуется с использованием этих принципов, кодовая база становится понятной, многоразовой, тестируемой, поддерживаемой и гибкой. Идея возникла у Роберта К. Мартина. С тех пор он был принят и использовался разработчиками программного обеспечения.

S.O.L.I.D означает

  • S принцип единой ответственности (SRP)
  • O принцип закрытого пера (OCP)
  • L принцип замещения исков (LSP)
  • I принцип разделения интерфейсов (ISP)
  • D принцип инверсии зависимости (DIP)

S принцип единой ответственности (SRP)

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

Рассмотрим фрагмент кода класса PaymentProcessing ниже

public class PaymentProcessing
{
    public void Process(object obj)
    {
         //process logic here
    }
}

Этот класс придерживается принципа единственной ответственности, потому что он делает только одно - обрабатывает платеж. Если бы мы добавили метод, который делает что-то, кроме обработки платежа, это нарушит принцип единственной ответственности. Причина в том, что у этого класса теперь будет несколько обязанностей. Допустим, мы хотели также обрабатывать Скидки совместно с платежами. У вас может возникнуть соблазн просто добавить логику обработки скидок в класс PaymentProcessing. Но обработка платежей и применение скидок к оплате - это разные вещи. Решением было бы изменить наш класс оплаты, чтобы вернуть обработанный объект платежа, затем создать отдельный класс Discount, а затем передать обработанный платеж в качестве параметра классу Discount. Таким образом, класс платежей по-прежнему несет одну ответственность, и теперь у нас есть класс Discount, который применяет скидки только к обработанным платежам. Изменение класса Discount не повлияет на класс PaymentProcessing и наоборот.

O принцип закрытого пера (OCP)

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

Рассмотрим фрагмент кода класса транспортного средства ниже

public class Vehicle
{
    public string Make { get; set; }
    public string Model { get; set; }
    public string Trim { get; set; }
    public string Year { get; set; }
    public string Color { get; set; }
    //vehicle related logic goes here
}

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

public class LuxuryVehicle : Vehicle
{
    public List<string> AdditionalOptions { get; set; }
    //luxury vehicle related logic goes here
}

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

L принцип замещения исков (LSP)

Этот принцип гласит, что производный класс должен заменять свой базовый класс.

Используя приведенный ранее пример кода, мы должны иметь возможность без проблем поменять наш класс LuxuryVehice на наш класс Vehicle. Предположим, мы добавляем новое свойство к нашему базовому классу с именем MSRP и добавляем новый вызов метода getHalfPrice (). Все, что делает этот метод, - это деление значения MSRP на 2. Предварительное условие базового класса состоит в том, что значение свойства всегда будет больше нуля.

Через несколько недель после того, как вы хвастаетесь, насколько крутым ваш класс транспортного средства, вы получаете новые требования, которые позволяют нулевой рекомендованной розничной цене в вашем классе LuxuryVehicle с добавленными опциями премиум-класса. Новые требования нарушают принцип подстановки Лискова. Способ решить эту проблему - создать интерфейс для каждого из классов транспортных средств с соответствующими сигнатурами методов. Класс транспортного средства будет иметь метод getHalfPrice (), затем LuxuryVehicle может иметь метод getZeroMSRPPriceWithAddedOption ().

I принцип разделения интерфейсов (ISP)

Этот принцип гласит, что клиентские интерфейсы лучше, чем один интерфейс общего назначения.

Допустим, у нас есть интерфейс под названием Staff, в котором есть такие методы, как обучение, clean, processPayment, advise. и т. д. Если бы мы создали класс Professor, нам пришлось бы реализовать все эти методы, даже если некоторые из них не связаны с профессором. Этот интерфейс нарушает принцип разделения интерфейсов.

Решением было бы создать клиентские интерфейсы, такие как Профессор, Администратор, Советник, Дворник и т. Д.. У каждого интерфейса будет соответствующий метод.

  • Профессор - ›преподаю ()
  • Администратор - ›processPayment ()
  • Советник - ›советовать ()
  • Дворник - ›чистый ()

Благодаря такому детализированному подходу теперь клиенты могут выбирать, какой из них соответствует их конкретным потребностям. Им не нужно было бы применять метод, который им не нужен.

D принцип инверсии зависимости (DIP)

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

Рассмотрим фрагмент кода MessageBoard ниже

public class MessageBoard
{
    private WhatUpMessage message;
    public MessageBoard(WhatsUpMessage message)
    {
        this.message = message;
    }
}

Модуль высокого уровня MessageBoard теперь зависит от сообщения WhatsUpMessage низкого уровня. Если бы нам нужно было распечатать базовое сообщение в модуле высокого уровня, мы бы теперь оказались во власти модуля низкого уровня. Нам нужно будет написать конкретную логику WhatsUpMessage, чтобы распечатать это сообщение. Если позже потребуется поддержка FacebookMessage, нам придется изменить высокоуровневый модуль (тесно связанный код). Это нарушает принцип инверсии зависимостей.

Способ исправить это - извлечь эту зависимость. Создайте интерфейс и добавьте все, что нужно вашему высокоуровневому модулю. Любой класс, которому необходимо использовать ваш высокоуровневый модуль, должен будет реализовать этот интерфейс.

Ваш интерфейс будет выглядеть примерно так

public interface IMessage
{
   public void PrintMessage();
}

Теперь ваша MessageBoard будет выглядеть так

public class MessageBoard
{
    private IMessage message;
    public MessageBoard(IMessage message)
    {
        this.message = message;
    }
    public void PrintMessage()
    {
        this.message.PrintMessage();
    }
}

Модуль низкого уровня будет выглядеть так

public class WhatUpMessage : IMessage
{
    public void PrintMessage()
    {
        //print whatsup message
    }
}
public class FacebookMessage : IMessage
{
    public void PrintMessage()
    {
        //print facebook message
    }
}

Эта абстракция удаляет зависимость низкоуровневого модуля в вашем высокоуровневом модуле. Модуль высокого уровня теперь полностью независим от любого модуля низкого уровня.

Использование принципов S.O.L.I.D при написании кода сделает вас лучшим разработчиком и сделает вашу жизнь намного проще. Вы даже можете стать новым популярным человеком в блоке, если будете единственным, кто это сделает. Спасибо, что дожили до конца. До следующего раза, удачного кодирования.

Если вам понравилась эта статья, возможно, вам понравятся и другие мои статьи по теме