实践GoF的23种设计模式:SOLID原则( 五 )


最后,的实现如下,只依赖于、和三个抽象接口 。后续再有需求变更,只需扩展对应的接口即可,无须再变更:
// demo/src/main/java/com/yrunz/designpattern/monitor/pipeline/Pipeline.java// ETL流程定义public class Pipeline implements Plugin {final InputPlugin input;final FilterPlugin filter;final OutputPlugin output;final AtomicBoolean isClose;public Pipeline(InputPlugin input, FilterPlugin filter, OutputPlugin output) {this.input = input;this.filter = filter;this.output = output;this.isClose = new AtomicBoolean(false);}// 运行pipelinepublic void run() {while (!isClose.get()) {Event event = input.input();event = filter.filter(event);output.output(event);}}...}
OCP是软件设计的终极目标,我们都希望能设计出可以新增功能却不用动老代码的软件 。但是100%的对修改封闭肯定是做不到的,另外,遵循OCP的代价也是巨大的 。它需要软件设计人员能够根据具体的业务场景识别出那些最有可能变化的点,然后分离出去,抽象成稳定的接口 。这要求设计人员必须具备丰富的实战经验,以及非常熟悉该领域的业务场景 。否则,盲目地分离变化点、过度地抽象,都会导致软件系统变得更加复杂 。
LSP:里氏替换原则
上一节介绍中,OCP的一个关键点就是抽象,而如何判断一个抽象是否合理,这是里氏替换原则(The,LSP)需要回答的问题 。
LSP的最初定义如下:
If for eacho1 of type S there is ano2 of type T such that for allPin terms of T, theof P iswhen o1 isfor o2 then S is aof T.
简单地讲就是,子类型必须能够替换掉它们的基类型,也即基类中的所有性质,在子类中仍能成立 。一个简单的例子:假设有一个函数f,它的入参类型是基类B 。同时,基类B有一个派生类D,如果把D的实例传递给函数f,那么函数f的行为功能应该是不变的 。
由此可以看出,违反LSP的后果很严重,会导致程序出现不在预期之内的行为错误 。下面,我们看一个经典反面例子,矩形与正方形 。
假设现在有矩形,可以通过方法设置宽度,方法设置长度,area方法得到矩形面积:
// 矩形定义public class Rectangle {private int width; // 宽度private int length; // 长度// 设置宽度public void setWidth(int width) {this.width = width;}// 设置长度public void setLength(int length) {this.length = length;}// 返回矩形面积public int area() {return width * length;}}
另外,有一个客户端程序Cient,它的方法f以作为入参,逻辑为校验矩形的逻辑:
// 客户端程序public class Client {// 校验矩形面积为长*宽public void f(Rectangle rectangle) {rectangle.setWidth(5);rectangle.setLength(4);if (rectangle.area() != 20) {throw new RuntimeException("rectangle's area is invalid");}System.out.println("rectangle's area is valid");}}// 运行程序public static void main(String[] args) {Rectangle rectangle = new Rectangle();Client client = new Client();client.f(rectangle);}// 运行结果:// rectangle's area is valid
现在,我们打算新增一种新的类型,正方形 。因为从数学上看,正方形也是矩形的一种,因此我们让继承了 。另外,正方形要求长宽一致,因此重写了和方法:
// 正方形,长宽相等public class Square extends Rectangle {// 设置宽度public void setWidth(int width) {this.width = width;// 长宽相等,因此同时设置长度this.length = width;}// 设置长度public void setLength(int length) {this.length = length;// 长宽相等,因此同时设置长度this.width = length;}}
下面,我们把实例化后作为入参传入Cient.f上: