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


那么,在编程语言中,我们用什么来表示“数据库”这一抽象呢?是接口!

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

文章插图
数据库最常见的几个操作就是CRUD,因此我们可以设计这么一个Db接口来表示“数据库”:
public interface Db {Record query(String tableName, Condition cond);void insert(String tableName, Record record);void update(String tableName, Record record);void delete(String tableName, Record record);}
这样,业务模块和数据库模块之间的依赖关系就变成如下图所示:
满足OCP的另一个关键点就是分离变化,只有先把变化点识别分离出来,我们才能对它进行抽象化 。下面以我们的分布式应用系统demo为例,解释如何实现变化点的分离和抽象 。
在demo中,监控系统主要负责对服务的 log进行ETL操作,也即涉及如下3个操作:1)从消息队列中获取日志数据;2)对数据进行加工;3)将加工后的数据存储在数据库上 。
我们把整一个日志数据的处理流程称为,那么我们可以这么实现:
public class Pipeline implements Plugin {private Mq mq;private Db db;...public void run() {while (!isClose.get()) {// 1、从消息队列中获取数据Message msg = mq.consume("monitor.topic");String accessLog = msg.payload();// 2、对数据进行清理操作,转换为json字符串对格式ObjectNode logJson = new ObjectNode(JsonNodeFactory.instance);logJson.put("content", accessLog);String data = http://www.kingceram.com/post/logJson.asText();// 3、存储到数据库上db.insert("logs_table", logId, data);}}...}
现在考虑新上线一个服务,但是这个服务不支持对接消息队列了,只支持传输数据,于是我们得在上新增一个来判断是否使用输入源:
public class Pipeline implements Plugin {...public void run() {while (!isClose.get()) {String accessLog;// 使用消息队列为消息来源if (inputType == InputType.MQ) {Message msg = mq.consume("monitor.topic");accessLog = msg.payload();}else {// 使用socket为消息来源Packet packet = socket.receive();accessLog = packet.payload().toString();}...}}}
过一段时间,有需求需要给 log打上一个时间戳,方便后续的日志分析,于是我们需要修改的数据加工逻辑:
public class Pipeline implements Plugin {...public void run() {while (!isClose.get()) {...// 对数据进行清理操作,转换为json字符串的格式ObjectNode logJson = new ObjectNode(JsonNodeFactory.instance);logJson.put("content", accessLog);// 新增一个时间戳字段logJson.put("timestamp", Instant.now().getEpochSecond());String data = http://www.kingceram.com/post/logJson.asText();...}}}
很快,又有一个需求,需要将加工后的数据存储到ES上,方便后续的日志检索,于是我们再次修改了的数据存储逻辑:
public class Pipeline implements Plugin {...public void run() {while (!isClose.get()) {...// 存储到ES上if (outputType == OutputType.DB) {db.insert("logs_table", logId, data);} else {// 存储到ES上es.store(logId, data)}}}}
在上述的例子中,每次新增需求都需要修改模块,明显违反了OCP 。下面,我们来对它进行优化,使它满足OCP 。
第一步是分离变化点,根据的业务处理逻辑,我们可以发现3个独立的变化点,数据的获取、加工和存储 。第二步,我们对这3个变化点进行抽象,设计出以下3个抽象接口:
// demo/src/main/java/com/yrunz/designpattern/monitor/input/InputPlugin.java// 数据获取抽象接口public interface InputPlugin extends Plugin {Event input();void setContext(Config.Context context);}// demo/src/main/java/com/yrunz/designpattern/monitor/filter/FilterPlugin.java// 数据加工抽象接口public interface FilterPlugin extends Plugin {Event filter(Event event);}// demo/src/main/java/com/yrunz/designpattern/monitor/output/OutputPlugin.java// 数据存储抽象接口public interface OutputPlugin extends Plugin {void output(Event event);void setContext(Config.Context context);}