开放封闭原则
I have to admit the first time I peeked at the academic definition of the Open/Closed Principle, its predicate was surprisingly clear to me. Leaving all of the technical jargon out of the picture, of course, its dictate was pretty much the mantra that we’ve heard so many times before: “Don’t hack the core”.
我不得不承认,我第一次看到开放式/封闭式原理的学术定义时,它的谓词对我来说是非常清楚的。 当然,将所有技术术语都排除在外,这几乎是我们之前多次听到的口头禅:“不要破解内核”。
Well, admittedly there’s a pinch of ambiguity since there are at least two common approaches to keeping the “core” neatly preserved while extending its functionality. The first one (and why I used deliberately the term “extending”) would be appealing to Inheritance. Inheritance is perhaps the most overrated beast for reusing implementation; it’s ridiculously easy to implement but runs the risk of modeling brittle hierarchies. The second approach is Composition. It isn’t quite as straightforward as Inheritance, but it’s still a neat way to extend a program module while keeping the module in question untouched.
好吧,诚然存在一点歧义,因为在扩展其功能的同时,至少有两种常用方法可以使“核心”保持整洁。 第一个(以及为什么我故意使用“扩展”一词)将吸引继承。 继承也许是重用实现的最被高估的野兽。 它非常容易实现,但是冒着对脆弱的层次结构建模的风险。 第二种方法是合成。 它不像继承那样简单明了,但是它仍然是扩展程序模块同时又保持模块不变的一种好方法。
The issue isn’t which option is the best for extending functionality per se. The Inheritance vs. Composition debate is so old and boring that bringing it to the table would be a waste of time. The actual problem is choosing a programming methodology that permits a decent level of closure for the target module but at the same time providing it with a “hook” opening it up for extension. In theory, both Inheritance and Composition are good candidates for accomplishing this as long as they rely on the intrinsic benefits provided by an abstraction rather than a concrete, rigid implementation.
问题不是哪个选项本身就是扩展功能的最佳选择。 继承与合成的辩论是如此古老而无聊,以至于将其带到桌面上将是浪费时间。 实际的问题是选择一种编程方法,该方法允许对目标模块进行适当程度的封闭,但同时为其提供“挂钩”以将其打开以进行扩展。 从理论上讲,继承和组合都是实现此目标的良好候选者,只要它们依赖于抽象所提供的内在利益,而不是具体的,僵化的实现。
It could also be said that concrete implementations are the principle’s worst plague. Not only do they push hard against the Open/Closed Principle’s commandments, but they make it impossible to create polymorphic modules that can be consumed by client code without ugly refactoring or smelly conditionals. In the last instance, the principle’s primary area of concern is to favor the creation of highly-decoupled components whose dependencies and the modules themselves are conceptually modeled in the form of abstract contracts or interfaces, thus promoting the ubiquitous use of Polymorphism, even if the components live and breath in different application layers.
也可以说,具体的实施是该原则最严重的灾难。 它们不仅违反开放式/封闭式原则的命令,而且使创建多态模块成为不可能,而无需丑陋的重构或有条件的条件,客户端代码就可以使用这些模块。 在最后一种情况下,该原则的主要关注领域是支持创建高度解耦的组件,这些组件的依赖关系和模块本身在概念上以抽象协定或接口的形式进行建模,从而促进了多态的普遍使用,即使组件在不同的应用程序层中生活和呼吸。
As usual, a good way to grasp what’s actually behind the curtain of the principle and how to take advantage of its benefits is by example. In this article I’ll be setting up a few approachable ones in an attempt to unveil the principle’s driving forces.
像往常一样,以示例的方式来掌握该原理幕后的实质以及如何利用其优势的一种好方法。 在本文中,我将建立一些平易近人的方法,以期揭示该原理的驱动力。
While the idea stems mostly from the pragmatism of experience, sometimes it’s easier to understand a concept by first showing the wrong way of doing something. In this case, I’m going to cling to that pragmatic approach and demonstrate why the implementation of system modules that aren’t designed around the idea of “Modification Closure” stated by the Open/Closed Principle may cause an explosion of fragility/rigidity artifacts.
尽管这个想法主要源于经验的实用主义,但有时通过首先显示错误的做事方式来理解一个概念会更容易。 在这种情况下,我将坚持使用这种务实的方法,并说明为什么未按照开放式/封闭式原则所述的“修改闭包”思想设计的系统模块的实现可能会导致脆弱性/刚性的爆炸式增长。文物。
Say we need to build a quick and dirty HTML renderer module capable of dumping a few HTML objects modeling divs, paragraphs, and the like. Following an irreverently sloppy and non-polymorphic approach, the corresponding HTML classes and the consumer module could be written as follows:
假设我们需要构建一个快速且肮脏HTML渲染器模块,该模块能够转储一些建模div,段落等HTML对象。 遵循一种毫不拘泥的,非多态的方法,可以将相应HTML类和使用者模块编写如下:
<?php namespace LibraryView; class HtmlDiv { private $text; private $id; private $class; public function __construct($text, $id = null, $class = null) { $this->setText($text); if ($id !== null) { $this->setId($id); } if ($class !== null) { $this->setClass($class); } } public function setText($text) { if (!is_string($text) || empty($text)) { throw new InvalidArgumentException( "The text of the element is invalid."); } $this->text = $text; return $this; } public function setId($id) { if (!preg_match('/^[a-z0-9_-]+$/', $id)) { throw new InvalidArgumentException( "The attribute value is invalid."); } $this->id = $id; return $this; } public function setClass($class) { if (!preg_match('/^[a-z0-9_-]+$/', $id)) { throw new InvalidArgumentException( "The attribute value is invalid."); } $this->class = $class; return $this; } public function renderDiv() { return '<div' . ($this->id ? ' id="' . $this->id . '"' : '') . ($this->class ? ' class="' . $this->class . '"' : '') . '>' . $this->text . '</div>'; } } <?php namespace LibraryView; class HtmlParagraph { private $text; private $id; private $class; public function __construct($text, $id = null, $class = null) { $this->setText($text); if ($id !== null) { $this->setId($id); } if ($class !== null) { $this->setClass($class); } } public function setText($text) { if (!is_string($text) || empty($text)) { throw new InvalidArgumentException( "The text of the element is invalid."); } $this->text = $text; return $this; } public function setId($id) { if (!preg_match('/^[a-z0-9_-]+$/', $id)) { throw new InvalidArgumentException( "The attribute value is invalid."); } $this->id = $id; return $this; } public function setClass($class) { if (!preg_match('/^[a-z0-9_-]+$/', $id)) { throw new InvalidArgumentException( "The attribute value is invalid."); } $this->class = $class; return $this; } public function renderParagraph() { return '<p' . ($this->id ? ' id="' . $this->id . '"' : '') . ($this->class ? ' class="' . $this->class . '"' : '') . '>' . $this->text . '</p>'; } } <?php namespace LibraryView; class HtmlRenderer { private $elements = array(); public function __construct(array $elements = array()) { if (!empty($elements)) { $this->addElements($elements); } } public function addElement($element) { $this->elements[] = $element; return $this; } public function addElements(array $elements) { foreach ($elements as $element) { $this->addElement($element); } return $this; } public function render() { $html = ""; foreach ($this->elements as $element) { if ($element instanceof HtmlDiv) { $html .= $element->renderDiv(); } else if ($element instanceof HtmlParagraph) { $html .= $element->renderParagraph(); } } return $html; } }The responsibilities of HtmlDiv and HtmlParagraph are limited to rendering the corresponding HTML elements based on a few common input arguments, such as the inner text, and the “id” and “class” attributes.
HtmlDiv和HtmlParagraph的职责仅限于基于一些常见的输入参数(例如内部文本以及“ id”和“ class”属性)呈现相应HTML元素。
The class that begs being put under a spotlight is the contrived HtmlRenderer, which certainly can be extended further down the road either via Inheritance or Composition (read open for extension). Exposing such virtue to the outside world, what could be wrong with the renderer then? Well, its most clumsy facet rests on the fact that its level of closure for modification is in this case just a bluff because the HTML objects it handles are non-polymorphic. In its current state the class is just capable of rendering batches of HtmlDiv and HtmlParagraph objects and …sorry, nothing else. If we ever want to add a new object type to its repertoire, the render() method needs to be refactored and polluted with more conditionals. To express this in the Open/Closed Principle’s terms, the class is by no means closed for modification.
引人注目的类是HtmlRenderer设计的HtmlRenderer ,它当然可以通过Inheritance或Composition(可以扩展阅读)进一步扩展。 将这样的美德暴露给外界,渲染器可能会出什么问题? 好吧,它最笨拙的方面在于,在这种情况下,它的关闭修改级别只是虚张声势,因为它处理HTML对象是非多态的。 在当前状态下,该类仅能够呈现HtmlDiv和HtmlParagraph对象的批处理……对不起,别无其他。 如果我们想将新的对象类型添加到其库中,则render()方法需要重构并受到更多条件的污染。 为了用开放式/封闭式原则的术语表达这一点,该类绝不是封闭式的。
Implementing an effective solution is be a two-step process: first and foremost, the classes responsible for engendering the HTML objects should be turned into polymorphic structures that adhere to a shared contract. Then the HtmlRenderer class should be refactored to handle any implementer of the contract without checking explicitly if it belongs to a certain type.
实施有效的解决方案包括两个步骤:首先,最重要的是,负责产生HTML对象的类应转换为遵循共享协定的多态结构。 然后,应重构HtmlRenderer类以处理合同的任何实现者,而无需明确检查其是否属于某种类型。
A segregated interface that gets the job done nicely is this:
一个可以很好地完成工作的隔离界面是这样的:
<?php namespace LibraryView; interface HtmlElementInterface { public function render(); }With this contract in place, now it’s time to do some quick clean up and encapsulate all the logic shared by the HTML classes inside the boundaries of an abstract Layer Supertype.
有了这个契约,现在是时候进行一些快速清理,并将HTML类共享的所有逻辑封装在抽象Layer Supertype的边界内。
<?php namespace LibraryView; abstract class AbstractHtmlElement implements HtmlElementInterface { protected $text; protected $id; protected $class; public function __construct($text, $id = null, $class = null) { $this->setText($text); if ($id !== null) { $this->setId($id); } if ($class !== null) { $this->setClass($class); } } public function setText($text) { if (!is_string($text) || empty($text)) { throw new InvalidArgumentException( "The text of the element is invalid."); } $this->text = $text; return $this; } public function setId($id) { $this->checkAttribute($id); $this->id = $id; return $this; } public function setClass($class) { $this->checkAttribute($class); $this->class = $class; return $this; } protected function checkAttribute($value) { if (!preg_match('/^[a-z0-9_-]+$/', $value)) { throw new InvalidArgumentException( "The attribute value is invalid."); } } }Things are now looking more appealing as we’ve managed to put implementation that’s common to all of the HTML objects beneath the shell of a supertype. Though simplistic, this change automatically turns the revamped versions of the HtmlDiv and HtmlParagraph classes into slim implementers of the same interface:
现在,由于我们设法将对所有HTML对象都通用的实现放在超类型的外壳下,因此事情看起来更具吸引力。 尽管很简单,但是此更改会自动将HtmlDiv和HtmlParagraph类的修订版转变为具有相同接口的苗条实现程序:
<?php namespace LibraryView; class HtmlDiv extends AbstractHtmlElement { public function render() { return '<div' . ($this->id ? ' id="' . $this->id . '"' : '') . ($this->class ? ' class="' . $this->class . '"' : '') . '>' . $this->text . '</div>'; } } <?php namespace LibraryView; class HtmlParagraph extends AbstractHtmlElement { public function render() { return '<p' . ($this->id ? ' id="' . $this->id . '"' : '') . ($this->class ? ' class="' . $this->class . '"' : '') . '>' . $this->text . '</p>'; } }Considering that HtmlDiv and HtmlParagraph are now beautifully polymorphic structures that honor a common contract, it’s simple to refactor the pertaining HTML renderer into a consumer of any implementer of the HtmlElementInterface interface:
考虑到HtmlDiv和HtmlParagraph现在是HtmlDiv通用协定的精美的多态结构,将相关HTML渲染器重构为HtmlElementInterface接口的任何实现的使用者很简单:
<?php namespace LibraryView; class HtmlRenderer { private $elements = array(); public function __construct(array $elements = array()) { if (!empty($elements)) { $this->addElements($elements); } } public function addElement(HtmlElementInterface $element) { $this->elements[] = $element; return $this; } public function addElements(array $elements) { foreach ($elements as $element) { $this->addElement($element); } return $this; } public function render() { $html = ""; foreach ($this->elements as $element) { $html .= $element->render(); } return $html; } }I’m aware that the new implementation of the HtmlRenderer class is a far cry from being mind blowing, but still it’s now a solid module that adheres to the Open/Closed Principle’s predicates. Furthermore, not only does it expose a nice level of closure for modification, as it’s feasible to feed it at runtime with multiple implementers of the HtmlElementInterface interface without amending a single chunk of it, but this feature on its own proves that it’s entirely open for extension as well. Feel free to pat yourself on the back because we’ve won the battle on two fronts!
我知道HtmlRenderer类的新实现与HtmlRenderer ,但是现在它仍然是一个牢固的模块,遵循“开放式/封闭式原理”的谓词。 此外,它不仅公开了一个很好的修改闭包级别,因为在运行时将其与HtmlElementInterface接口的多个实现者一起提供而不修改单个块是可行的,而且此功能本身就证明了它是完全开放的。扩展。 随便拍打自己,因为我们在两条战线上都赢了!
Here’s how the module could be put to work for rendering a couple of HTML objects:
以下是该模块可以用于呈现几个HTML对象的方法:
<?php use LibraryLoaderAutoloader, LibraryViewHtmlDiv, LibraryViewHtmlParagraph, LibraryViewHtmlRenderer; require_once __DIR__ . "/Library/Loader/Autoloader.php"; $autoloader = new Autoloader(); $autoloader->register(); $div = new HtmlDiv("This is the text of the div.", "dID", "dClass"); $p = new HtmlParagraph("This is the text of the paragraph.", "pID", "pClass"); $renderer = new HtmlRenderer(array($div, $p)); echo $renderer->render();In this article I’ve shown you an example of how to create functional software components that faithfully adhere to the Open/Closed Principle’s commandments. Being one of the most relevant (if not the most) SOLID principles, the principle is certainly a fundamental OOP pillar whose benefits are so remarkable that it’s a shame it has remained unfairly underrated. But needless to say, the key to accomplishing such a goal requires designing the components as polymorphic elements whose behavior is explicitly defined through abstract contracts. From that point onward, what approach is the most effective to extend them (yep, the Inheritance/Composition duet shows up again), and what level of closure they must expose to the outside world, is entirely up to you.
在本文中,我向您展示了如何创建忠实遵守开放式/封闭式原则的功能性软件组件的示例。 作为最重要(甚至不是最重要)的SOLID原则之一,该原则无疑是OOP的基本Struts,其好处如此之大,以至于遗憾的是它一直被低估。 但是不用说,实现这一目标的关键是需要将组件设计为多态元素,其行为是通过抽象协定明确定义的。 从那时起,哪种方法最有效地扩展它们(是的,继承/组合二重奏又出现了),它们必须暴露给外部世界的关闭级别完全取决于您。
Image via Fotolia
图片来自Fotolia
翻译自: https://www.sitepoint.com/the-open-closed-principle/
开放封闭原则