Насколько такой важный принцип "OCP" станет причиной массовой практики дублирования кода?

OCP (открытый / закрытый принцип) является одним из принципов SOLID. Который говорит:

"Программные объекты должны быть открыты для расширения, но закрыты для модификации".

Мне нужно время, чтобы понять вышеприведенное предложение об OCP. И когда я начал читать больше об этом, я обнаружил, что это имеет смысл и настолько полезен, но в то же время я заметил, что это приводит к дублированию кода.

Насколько такой важный принцип "OCP" станет причиной массовой практики дублирования кода?

namespace SOLIDPrinciples
 {  
    public class ReportFormatter {
        public virtual void FormatReport() {
            Console.WriteLine("Formatting Report for 8-1/2 X 11 ....");
        }
    }

    public class TabloidReportFormatter : ReportFormatter {
        public override void FormatReport() {
            Console.WriteLine("Formatting Report for 11 X 17 ....");
        }   
    }
 }

Я что-то здесь упускаю? Есть ли другой способ объяснить OCP?

5 ответов

Решение

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

Приведенный вами пример на самом деле не демонстрирует принцип Open/Closed, потому что вы не продемонстрировали, как поведение данного класса может быть расширено без изменения. Принцип Open/Closed заключается в создании новых классов каждый раз, когда требуется вариант поведения. Это может быть достигнуто с помощью наследования вместе с шаблоном Template Method (т. Е. Вызовом абстрактных или виртуальных методов в базовом классе, которые переопределяются в подклассах для достижения желаемого / нового поведения), но чаще это демонстрируется с использованием композиции с использованием шаблона Strategy (т.е. инкапсуляция варианта поведения в классе и передача его в закрытый тип для использования при определении общего достигнутого поведения).

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

public class ReportWriter : IReportWriter
{
    Write(IReportData data, IReportFormatter reportFormatter)
    {
         // ...

         var formattedStream = reportFormatter.Format(data, stream);

        // ...
    }
}

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

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

То, что у тебя есть, выглядит прекрасно для меня. Вы не изменили ReportFormatter, вы расширили его.

Нечто подобное должно работать.

namespace SOLIDPrinciples
 {  
    public class ReportFormatter {
        public virtual void FormatReport() = 0;
    }

    public class VariableSizeReportFormatter : ReportFormatter {
        public void SetSize(String size);
        public override void FormatReport() {
                Console.WriteLine("implement generic layout routines");
        }       
    }

    public class TabloidReportFormatter : VariableSizeReportFormatter {
        public override void FormatReport() {
                // currently, we just do a tweaked version of generic layout ..
                SetSize("11x7");
                Super.Format(); 
                Console.WriteLine("RemoveThatColumnWeDontWant");
        }       
    }
 }

Это означает:

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

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

Нечто подобное можно решить с помощью лучшего дизайна методов. Взгляните на следующее:

namespace SOLIDPrincibles
 {  
    public class ReportFormatter {
        public virtual void FormatReport(String size) {
                Console.WriteLine("Formatting Report for " + size + " ....");
        }
    }

    public class TabloidReportFormatter : ReportFormatter {
        public override void FormatReport() {
                super.FormatReport("11 X 17");
        }       
    }
 }

Я думаю, что дизайн по этим направлениям был бы вашим лучшим способом устранения дублированного кода.

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

Зачем нужен класс форматера для каждого формата, если вы предоставляете формат в качестве аргумента конструктору? Класс ctor форматера (или даже метод статического формата класса ReportFormatter) должен иметь отчет и описание формата в качестве аргументов и соответственно форматировать отчет.

Нет необходимости в классе для каждого формата.

Другие вопросы по тегам