设计模式(1)
这一系列是我学习设计模式过程中的总结与感受。当然许多人都说设计模式需要大量的实践才能真正学会,因此目前写下的一定是一些很粗浅的内容。在以后的开发过程中,我会进一步记录我对设计模式的体会。
这一篇文章包括策略模式和观察者模式。
设计模式的意义
设计模式是历经验证的一系列OO设计经验、通用的解决方案。仅仅知道基础的OO概念如抽象、封装、继承、多态并不一定能设计出合理的系统。设计模式的目的在于提高系统的可复用、可扩充、可维护性。
策略模式(Strategy Pattern)
定义:定义算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。
让我们考虑这样一个场景,假如有一个OO的父类和它的许多子类,在维护系统时,需要增加新功能,这一个新功能可能被许多子类公用,那么这个新功能应该写到哪里呢?如果考虑可复用性,似乎应该写成父类中的一个方法,那么子类就可以直接使用。但是这样有明显的缺点:我们不能确定所有的子类都用到这个方法,或是不能保证将来不会添加不需要该方法的子类,这样的代码不具有可维护性。那如果我们使用接口呢?把该新功能写成一个接口,让需要该方法的子类去实现,显然这样的方式也很蠢,因为代码完全无法复用,每个子类都需要去增加对应的代码。
我们需要一个原则来改进我们的代码——将代码中可能变化的地方独立出来,不要与固定不变的代码混在一起。根据这一原则,我们可以把永远不会变化的东西写入父类中,而可能实现的功能(策略模式定义中的算法族)从父类中分离出来。那么这些分离出来的可能变化的东西该如何实现呢?根据另一个原则——针对接口编程,而不是针对实现编程,我们可以想到一种聪明的方法,把一类相同的功能(算法)定义成一个接口(或JAVA中的抽象类,这里的接口只是一个粗略的说法),而功能的每一种具体实现(一族算法的每一个算法)在实现对应接口的单独的类中去实现。这样我们就不需要在原本的类中去硬编码一个算法的实现,只需要在运行中指定具体实现的对象(利用多态指定功能的某一个具体实现)。
总结策略模式的思想,我们不在一个类中硬编码功能的具体实现,而是声明为一个接口类型,在运行时再引用具体的实现,实现了之前提到的原则,不针对实现编程。而在运行时引用具体实现也有不同的方式,我们可以静态地在具体子类中直接指明需要的实现,也可以实现方法来动态指定,使我们的代码具有很强的可维护性和可扩充性。
总体上来看策略模式,我们实际上是把一个类和其他实现功能的类组合起来,避免了直接向父类继承的弊端。由此我们得到第三个原则——多用组合,少用继承。我们容易理解,继承实际上是将具体功能的实现硬编码,降低了系统的可维护性,而使用组合可以使系统具有很大的弹性,在运行时动态指定实现,只要实现符合对应的接口即可。
观察者模式(Observer Pattern)
定义:定义了对象之间的一对多依赖,当一个对象的状态改变时,它的所有依赖者都会收到通知并自动更新。
我们可以设想一个观察者模式最简单的应用情况,有一个类会不定时产生数据(状态改变,如何改变我们并不关心),还有一系列的类需要实时得知新数据并做出相应。要实现上述功能,最简单的方式应该是产生数据的类在每次产生数据时主动调用其他依赖类的对应的响应的方法。但这种方式明显违背了之前针对接口编程,而不是针对实现编程的原则,我们也很容易想到,如果有新的类需要响应产生数据的类,我们甚至需要在核心的产生数据的类中去添加代码,原有的类取消响应或是响应的方法发生变化,也需要更改产生数据的类的代码,这样的系统显然不具有高可维护性,或者说,类之间具有很高的耦合。
那么观察者模式如何解决这个问题呢?实际上类似于订阅报纸的机制,每个住户需要报纸时,向邮局订阅,邮递员会在有新报纸时送到订阅的用户,如果不需要报纸,住户接触订阅即可。具体的说,观察者模式定义了两个接口:一个是Subject,对应之前提到的产生数据的类,这个接口有订阅和解除订阅的方法,供其他类调用,还有一个通知订阅类的方法,每次状态发生变化时,调用该方法,向记录的已经订阅的类去传递消息;另一个是Observer接口,对应需要响应Subject的类,该接口只有一个更新的方法,供Subject通知时调用。
观察者模式有什么好处呢?首先我们容易想到,它降低了类之间的耦合。我们之前的代码在增加或减少需要获取信息的类时,需要具体去改变产生信息的中心类的代码,而使用观察者模式,我们仅需要调用Subject的一个方法即可,并不会改变Subject本身,使系统具有更大弹性。
当然,我们上述的模型是一个“推式”的模型,由Subject主动向Observer通知变化。我们也很容易可以实现一个“拉式”的模型,由Observer在需要时向Subject拉取数据。
观察者模式在很多框架或者库中都有影子,如果你写过GUI或是用JavaScript写过Web界面,那你一定不会对事件这个概念陌生。如在JavaScript中,我们要添加一个按钮,在按下按钮时调用一个函数,我们就需要把按下按钮这个事件与需要执行的函数绑定,这样一个过程实际上就是观察者模式中Observer向Subject注册的过程。我们很容易体会这种方式的优越性,它把事件与响应之间的耦合降到了最低,我们为一个事件实现不同的响应时并不需要修改事件本身的代码。
Original author: 马旭
Original link: https://bhmaxu.github.io/2020/02/10/设计模式(1)/
Copyright Notice: Please indicate the source of the reprint (must retain the author's signature and link)