OCP – 可扩展的设计

在本节中,我们将看到 OCP 如何帮助我们编写可以添加新功能而无需更改代码本身的代码。这起初听起来像是不可能的,但它自然地从 DIPLSP 的结合中产生。

OCP 导致代码对扩展开放但对修改关闭。我们在研究 DIP 时看到了这个想法的应用。让我们从 OCP 的角度回顾我们所做的代码重构。

让我们从 Shapes 类的原始代码开始,如下所示:

public class Shapes {
    private final List<Shape> allShapes = new ArrayList<>();

    public void add(Shape s) {
        allShapes.add(s);
    }

    public void draw(Graphics g) {
        for (Shape s : allShapes) {
            switch (s.getType()) {
                case "textbox":
                    var t = (TextBox) s;
                    g.drawText(t.getText());
                    break;
                case "rectangle":
                    var r = (Rectangle) s;
                    for (int row = 0; row < r.getHeight(); row++) {
                        g.drawLine(0, r.getWidth());
                    }
            }
        }
    }
}

添加一种新的形状需要修改 draw() 方法内部的代码。我们将添加一个新的 case 语句来支持我们的新形状。

修改现有代码有几个缺点,如下所述:

  • 我们使之前的测试无效。这现在是与我们测试过的代码不同的代码。

  • 我们可能会引入一个错误,破坏现有对形状的支持。

  • 代码将变得更长且更难以阅读。

  • 我们可能会有几个开发人员同时添加形状,并在合并他们的工作时产生合并冲突。

通过应用 DIP 并重构代码,我们最终得到了这个:

public class Shapes {
    private final List<Shape> all = new ArrayList<>();
    private final Graphics graphics;

    public Shapes(Graphics graphics) {
        this.graphics = graphics;
    }

    public void add(Shape s) {
        all.add(s);
    }

    public void draw() {
        all.forEach(shape -> shape.draw(graphics));
    }
}

我们现在可以看到,添加一种新的形状不需要修改这段代码。这是 OCP 在工作的一个例子。Shapes 类对定义新类型的形状开放,但在添加新形状时对修改关闭。这也意味着与 Shapes 类相关的任何测试将保持不变,因为这个类的行为没有差异。这是一个强大的优势。

OCP 依赖于 DI 来工作。它或多或少是应用 DIP 的结果的重新表述。它还为我们提供了一种支持可交换行为的技术。我们可以使用 DIPOCP 来创建插件系统。

添加新类型的形状

为了看看这在实践中是如何工作的,让我们创建一个新的形状类型,RightArrow 类,如下所示:

public class RightArrow implements Shape {
    public void draw(Graphics g) {
        g.drawText(" \" ");
        g.drawText("-----");
        g.drawText(" /");
    }
}

RightArrow 类实现了 Shape 接口并定义了一个 draw() 方法。为了证明使用这个类不需要改变 Shapes 类中的任何内容,让我们回顾一些使用 Shapes 和我们的新类 RightArrow 的代码,如下所示:

package shapes;

public class ShapesExample {
    public static void main(String[] args) {
        new ShapesExample().run();
    }

    private void run() {
        Graphics console = new ConsoleGraphics();
        var shapes = new Shapes(console);

        shapes.add(new TextBox("Hello!"));
        shapes.add(new Rectangle(32, 1));
        shapes.add(new RightArrow());

        shapes.draw();
    }
}

我们看到 Shapes 类正在以完全正常的方式使用,没有改变。事实上,使用我们的新 RightArrow 类唯一需要做的改变是创建一个对象实例并将其传递给 shapesadd() 方法。

OCP

使代码对新行为开放,但对修改关闭。

OCP 的力量现在应该很清楚了。我们可以扩展代码的能力并限制更改。我们大大减少了破坏已经工作的代码的风险,因为我们不再需要更改该代码。OCP 是管理复杂性的一种很好的方式。在下一节中,我们将看看剩下的 SOLID 原则:ISP