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


当前demo中使用接口的模块有2个,分别是作为消费者的和作为生产者的:
public class MemoryMqInput implements InputPlugin {private String topic;private Mq mq;...@Overridepublic Event input() {Message message = mq.consume(topic);Map header = new HashMap<>();header.put("topic", topic);return Event.of(header, message.payload());}...}public class AccessLogSidecar implements Socket {private final Mq mq;private final String topic...@Overridepublic void send(Packet packet) {if ((packet.payload() instanceof HttpReq)) {String log = String.format("[%s][SEND_REQ]send http request to %s",packet.src(), packet.dest());Message message = Message.of(topic, log);mq.produce(message);}...}...}
从领域模型上看,Mq接口的设计确实没有问题,它就应该包含和两个方法 。但是从客户端程序的角度上看,它却违反了ISP,对来说,它只需要方法;对来说,它只需要方法 。
一种设计方案是把Mq接口拆分成2个子接口和,让直接实现和:
// demo/src/main/java/com/yrunz/designpattern/mq/Consumable.java// 消费者接口,从消息队列中消费数据public interface Consumable {Message consume(String topic);}// demo/src/main/java/com/yrunz/designpattern/mq/Producible.java// 生产者接口,向消息队列生产消费数据public interface Producible {void produce(Message message);}// 当前提供MemoryMq内存消息队列的实现public class MemoryMq implements Consumable, Producible {...}
仔细思考一下,就会发现上面的设计不太符合消息队列的领域模型,因为Mq的这个抽象确实应该存在的 。
更好的设计应该是保留Mq抽象接口,让Mq继承自和,这样的分层设计之后,既能满足ISP,又能让实现符合消息队列的领域模型:
具体实现如下:
// demo/src/main/java/com/yrunz/designpattern/mq/Mq.java// 消息队列接口,继承了Consumable和Producible,同时又consume和produce两种行为public interface Mq extends Consumable, Producible {}// 当前提供MemoryMq内存消息队列的实现public class MemoryMq implements Mq {...}// demo/src/main/java/com/yrunz/designpattern/monitor/input/MemoryMqInput.javapublic class MemoryMqInput implements InputPlugin {private String topic;// 消费者只依赖Consumable接口private Consumable consumer;...@Overridepublic Event input() {Message message = consumer.consume(topic);Map header = new HashMap<>();header.put("topic", topic);return Event.of(header, message.payload());}...}// demo/src/main/java/com/yrunz/designpattern/sidecar/AccessLogSidecar.javapublic class AccessLogSidecar implements Socket {// 生产者只依赖Producible接口private final Producible producer;private final String topic...@Overridepublic void send(Packet packet) {if ((packet.payload() instanceof HttpReq)) {String log = String.format("[%s][SEND_REQ]send http request to %s",packet.src(), packet.dest());Message message = Message.of(topic, log);producer.produce(message);}...}...}
接口隔离可以减少模块间耦合,提升系统稳定性,但是过度地细化和拆分接口,也会导致系统的接口数量的上涨,从而产生更大的维护成本 。接口的粒度需要根据具体的业务场景来定,可以参考单一职责原则,将那些为同一类客户端程序提供服务的接口合并在一起 。
DIP:依赖倒置原则
《Clean 》中介绍OCP时有提过:如果要模块A免于模块B变化的影响,那么就要模块B依赖于模块A 。这句话貌似是矛盾的,模块A需要使用模块B的功能,怎么会让模块B反过来依赖模块A呢?这就是依赖倒置原则(The,DIP)所要解答的问题 。
DIP的定义如下:
High-levelnotfrom low-level . Bothon .noton .( )on .
翻译过来,就是: