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


public static void main(String[] args) {Square square = new Square();Client client = new Client();client.f(square);}// 运行结果:// Exception in thread "main" java.lang.RuntimeException: rectangle's area is invalid//at com.yrunz.designpattern.service.mediator.Client.f(Client.java:8)//at com.yrunz.designpattern.service.mediator.Client.main(Client.java:16)
我们发现Cient.f的行为发生了变化,子类型并不能替代基类型,违反了LSP 。
出现上面的这种违反LSP的设计,主要原因还是我们孤立地进行模型设计,没有从客户端程序的角度来审视该设计是否正确 。我们孤立地认为在数学上成立的关系(正方形 IS-A 矩形),在程序中也一定成立,而忽略了客户端程序的使用方法(先设置宽度为5,长度为4,然后校验面积为20) 。
这个例子告诉我们:一个模型的正确性或有效性,只能通过客户端程序来体现 。
下面,我们总结一下在继承体系(IS-A)下,要想设计出符合LSP的模型所需要遵循的一些约束:
基类应该设计为一个抽象类(不能直接实例化,只能被继承) 。子类应该实现基类的抽象接口,而不是重写基类已经实现的具体方法 。子类可以新增功能,但不能改变基类的功能 。子类不能新增约束,包括抛出基类没有声明的异常 。
前面的矩形和正方形的例子中,几乎把这些约束都打破了,从而导致了程序的异常行为:1)的基类不是一个抽象类,打破约束1;2)重写了基类的和方法,打破约束2;3)新增了没有的约束,长宽相等,打破约束4 。
除了继承之外,另一个实现抽象的机制是接口 。如果我们是面向接口的设计,那么上述的约束1~3其实已经满足了:1)接口本身不具备实例化能力,满足约束1;2)接口没有具体的实现方法(Java中接口的方法比较例外,本文先不考虑),也就不会被重写,满足约束2;3)接口本身只定义了行为契约,并没有实际的功能,因此也不会被改变,满足约束3 。
因此,使用接口替代继承来实现多态和抽象,能够减少很多不经意的错误 。但是面向接口设计仍然需要遵循约束4,下面我们以分布式应用系统demo为例,介绍一个比较隐晦地打破约束4,从而违反了LSP的实现 。
还是以监控系统为例,为例实现ETL流程的灵活配置,我们需要通过配置文件定义的流程功能(数据从哪获取、需要经过哪些加工、加工后存储到哪里) 。当前需要支持json和yaml两种配置文件格式,以yaml配置为例,配置内容是这样的:
# src/main/resources/pipelines/pipeline_0.yamlname: pipeline_0 # pipeline名称type: single_thread # pipeline类型input: # input插件定义(数据从哪里来)name: input_0 # input插件名称type: memory_mq # input插件类型context: # input插件的初始化上下文topic: access_log.topicfilter: # filter插件定义(需要经过哪些加工)- name: filter_0 # 加工流程filter_0定义,类型为log_to_jsontype: log_to_json- name: filter_1 # 加工流程filter_1定义,类型为add_timestamptype: add_timestamp- name: filter_2 # 加工流程filter_2定义,类型为json_to_monitor_eventtype: json_to_monitor_eventoutput: # output插件定义(加工后存储到哪里)name: output_0 # output插件名称type: memory_db # output插件类型context: # output插件的初始化上下文tableName: monitor_event_0
首先我们定义一个接口来表示“配置”这一抽象:
// demo/src/main/java/com/yrunz/designpattern/monitor/config/Config.javapublic interface Config {// 从json字符串中加载配置void load(String conf);}
另外,上述配置中的input、、子项,可以认为是、、插件的配置项,由插件的配置项组合在一起,因此我们定义了如下几个的抽象类: