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


最后,通过来创建对象:
Config config = YamlPipelineConfig.of(YamlInputConfig.empty(), YamlFilterConfig.empty(), YamlOutputConfig.empty());config.load(Files.readAllBytes("pipeline_0.yaml"));Pipeline pipeline = PipelineFactory.newInstance().create(config);assertNotNull(pipeline);// 运行结果:Pass
到目前为止,上述的设计看起来是合理的,运行也没有问题 。
但是,细心的读者可能会发现,每个插件工厂子类的方法的第一行代码都是一个转型语句,比如的是 conf = () ; 。所以,上一段代码能够正常运行的前提是:传入.方法的入参必须是。如果客户端程序传入的实例,.方法将会抛出转型失败的异常 。
上述这个例子就是一个违反LSP的典型场景,虽然在约定好的前提下,程序可以运行正确,但是如果有客户端不小心破坏了这个约定,就会带来程序行为异常(我们永远无法预知客户端的所有行为) 。
要纠正这个问题也很简单,就是去掉这一层抽象,让.等工厂方法的入参声明为具体的配置类,比如可以这么实现:
// demo/src/main/java/com/yrunz/designpattern/monitor/pipeline/PipelineFactory.java// pipeline插件工厂,不再实现PluginFactory接口public class PipelineFactory {...// 工厂方法入参为PipelineConfig实现类,消除转型public Pipeline create(PipelineConfig config) {InputPlugin input = InputPluginFactory.newInstance().create(config.input());FilterPlugin filter = FilterPluginFactory.newInstance().create(config.filter());OutputPlugin output = OutputPluginFactory.newInstance().create(config.output());...}}
从上述几个例子中,我们可以看出遵循LSP的重要性,而设计出符合LSP的软件的要点就是,根据该软件的使用者行为作出的合理假设,以此来审视它是否具备有效性和正确性 。
ISP:接口隔离原则
接口隔离原则(The,ISP)是关于接口设计的一项原则,这里的“接口”并不单指Java或Go上使用声明的狭义接口,而是包含了狭义接口、抽象类、具象类等在内的广义接口 。它的定义如下:
not betoonit does not use.
也即,一个模块不应该强迫客户程序依赖它们不想使用的接口,模块间的关系应该建立在最小的接口集上 。
下面,我们通过一个例子来详细介绍ISP 。
上图中,、、都依赖了,但实际上,只需使用.func1方法,只需使用.func2,只需使用.func3,那么这时候我们就可以说该设计违反了ISP 。
违反ISP主要会带来如下2个问题:
增加模块与客户端程序的依赖,比如在上述例子中,虽然和都没有调用func1,但是当修改func1还是必须通知~3,因为并不知道它们是否使用了func1 。产生接口污染,假设开发的程序员,在写代码时不小心把func1打成了func2,那么就会带来的行为异常 。也即被func2给污染了 。
为了解决上述2个问题,我们可以把func1、func2、func3通过接口隔离开:
接口隔离之后,只依赖了,而上只有func1一个方法,也即不会受到func2和func3的污染;另外,当修改func1之后,它只需通知依赖了的客户端即可,大大降低了模块间耦合 。
实现ISP的关键是将大接口拆分成小接口,而拆分的关键就是接口粒度的把握 。想要拆分得好,就要求接口设计人员对业务场景非常熟悉,对接口使用的场景了如指掌 。否则孤立地设计接口,很难满足ISP 。
下面,我们以分布式应用系统demo为例,来进一步介绍ISP的实现 。
一个消息队列模块通常包含生产()和消费()两种行为,因此我们设计了Mq消息队列抽象接口,包含和两个方法:
// 消息队列接口public interface Mq {Message consume(String topic);void produce(Message message);}// demo/src/main/java/com/yrunz/designpattern/mq/MemoryMq.java// 当前提供MemoryMq内存消息队列的实现public class MemoryMq implements Mq {...}