определение открытого закрытого принципа
Существует два различных определения открытого и закрытого принципов: самое примитивное определение 1980-х годов и более позднее, более современное определение, которое развивает первое.
Определение Мейера
Программные объекты должны разрешать расширения, но не модификации
- «Объектно-ориентированное проектирование программного обеспечения»
Мартин
«Открыть для расширения.» Это означает, что поведение модуля является расширяемым. Когда требования к приложению меняются, мы можем расширить его модули, чтобы обеспечить новое поведение, соответствующее этим требованиям. Другими словами, мы можем изменить функциональность модуля.
«закрыто для модификации.» Расширения поведения модуля не требуют внесения изменений в исходный или двоичный код модуля. Бинарную исполняемую версию модуля, будь то подключаемая библиотека, библиотека DLL или файл Java .jar, изменять не нужно.
— «Гибкая разработка программного обеспечения: принципы, шаблоны и практика»
закрыто для модификации
Обратите внимание, что «закрыто для модификации» имеет два исключения:
1.Внесены изменения для исправления ошибок
2.Изменения, не воспринимаемые клиентом
Исправлена ошибка
Дефекты распространены в программном обеспечении и не могут быть полностью устранены. Когда появляются ошибки, нам нужно исправить существующий код. Программные исправления явно склоняются к прагматизму, а не придерживаются принципа открытого-закрытого.
Осведомленность клиентов
Если изменения в одном классе вызывают изменения в другом, то эти два класса тесно связаны. И наоборот, если изменения в одном классе всегда независимы и не вызывают изменений в других классах, то эти классы слабо связаны. мы должны помнить,В любом случае слабая связь лучше жесткой.. Если мы вносим изменения в существующий код, не затрагивая клиентский код, то нарушения принципа открытости-закрытости нет.
открыт для продления
точка расширения
нет точки расширения
Класс TradeProcessorClient напрямую зависит от класса TradeProcessor. При получении нового требования, в котором необходимо изменить класс TradeProcessor, чтобы не менять исходный тип, создается новый тип (TradeProcessor2) для реализации новой функции, предложенной требованием. Но побочным эффектом этого изменения является необходимость изменить класс TradeProcessorClient, чтобы он мог полагаться на новый класс TradeProcessor2.
Если изменения в существующем коде не влияют на клиентов, то нет необходимости создавать новые типы. Но если изменение существующего кода изменяет сигнатуру метода класса TradeProcessor, это не просто изменение реализации класса, а изменение интерфейса.Поскольку клиент всегда тесно связан с интерфейсом службы, любые изменения в интерфейсе вызовут изменения в коде клиента.
виртуальный метод
Другая реализация класса TradeProcessor содержит точку расширения: ProcessTrades — это виртуальный метод.
Любой класс с членами виртуального метода открыт для внешнего мира, и это расширение осуществляется посредством наследования. Вы можете изменить метод ProcessTrades его подкласса, не изменяя исходный код исходного класса TradeProcessor. В настоящее время нет необходимости изменять класс TradeProcessorClient, и можно использовать полиморфизм для предоставления клиенту экземпляра новой версии класса TradeProcessor2.
Но возможности повторной реализации с использованием виртуальных методов ограничены. Доступ к базовому классу можно получить в подклассе, поэтому метод ProcessTrades класса TradeProcessor можно вызывать напрямую, но какой-либо код в этом методе изменить нельзя. Либо вызывать одноименный метод базового класса в методе подкласса и реализовывать новую функцию до и после него, либо полностью переопределять метод подкласса.Виртуальные методы не имеют промежуточного состояния. Кроме того, подкласс может получить доступ только к защищенным и общедоступным членам базового класса.Если базовый класс имеет много закрытых членов, к которым у подкласса нет доступа, может потребоваться изменить реализацию базового класса. Однако это опять же нарушает принцип открытого-закрытого.
абстрактный метод
Другой более гибкой точкой расширения, использующей наследование реализации, являются абстрактные методы.
Клиенты полагаются на абстрактные базовые классы, поэтому предоставление клиенту любого конкретного подкласса (или подкласса для поддержки новых требований) не нарушает принцип открытого-закрытого.
наследование интерфейса
Последняя точка расширения — это альтернатива наследованию реализации: наследование интерфейса. Интерфейс делегата клиента заменяет зависимость клиента от класса.
Наследование интерфейса намного лучше, чем наследование реализации. На основе наследования реализации все подклассы (существующие и будущие) являются клиентами базового класса. Изменения, добавляющие новые элементы в верхний узел графа наследования, влияют на все элементы иерархии, а интерфейсы гораздо более гибкие, чем классы. Это, конечно, не означает, что точки расширения, предоставляемые виртуальными и абстрактными методами, представляющими наследование реализации, бесполезны, но они не обеспечивают такой же уровень адаптивности, как интерфейсы.
предотвратить мутацию
Хотя мы уже знаем, как реализовать точки расширения, должны ли мы хранить точки расширения везде? Предотвращение мутации — еще один важный принцип, связанный с принципом «открыто-закрыто»:
Определите предсказуемые точки изменений и создайте вокруг них стабильный интерфейс.
предсказуемые изменения
идентифицироватьТребования, которые могут изменитьсяилиЧасть кода, которая особенно громоздка для реализации, затем спрячьте их за точкой расширения.
стабильный интерфейс
Самым большим преимуществом использования интерфейсов является то, что интерфейсы меняются гораздо реже, чем реализации.Все интерфейсы, используемые для выражения точек расширения, должны быть стабильными.. Поскольку клиент напрямую зависит от интерфейса, при изменении интерфейса клиент также должен внести соответствующие изменения.
В конце концов
Убедившись, что код открыт для расширения и закрыт для модификации, вы можете эффективно предотвратить изменение существующих классов последующими изменениями, поскольку более поздние кодировщики могут присоединять вновь созданные классы только к зарезервированным вами точкам расширения. Код может быть жестким, с небольшим расширением и усовершенствованием, а может быть и гибким, с большим количеством точек расширения, готовых к новым требованиям. В обоих вариантах нет ничего плохого, их просто нужно выбирать и применять в конкретных сценариях.
Ссылаться на
«Практика гибкой разработки на C#»