大名鼎鼎的 23 种设计模式可谓是人尽皆知,但是都使用过的人恐怕是寥寥无几。模式一词最早来自于建筑,建筑师克里斯托弗·亚历山大曾把建筑中相关的模式总结成册,最后经过 Kent Beck 和 Ward Cunningham 的探索将其应用到了软件开发领域,真正在软件开发领域流行起来的时候是自《设计模式》这本书出版之后,而今天我们熟知的 23 种设计模式就是来自这本书。
那么你是否有种即便看过 23 种设计模式,也还是有种不知如何应用。就像你即便道理都懂仍然无法过好这一生。那么我们该如何学习设计模式?
如何学习设计模式
在具体开始介绍设计模式之前,我们从熟悉的东西开始,由浅入深。欧氏几何大家都很熟悉,它是平面几何公理,平面几何的所有定理(或者命题),都是以公理体系为出发点,利用纯逻辑推理方法就可以推导出来。如直线的角度都是 180 度;三角形内角和是 180 度..... 如果说公理体系就像一棵大树,那么公理就是大树的根,定理就是大树的枝叶。那么设计模式的公理是什么呢?答案是设计原则,而设计模式则是定理。
设计模式是特定场景的可复用的解决方案,所以想把已知的设计模式统统学一遍,即便可能,也没有必要,因为设计模式也在不断的更新,就像夸父追日。最后发现不仅学的幸苦,而且大部分情况根本用不着。
所以学习设计模式的 12 字箴言:不求全面,应用场景,代码编写。
原则与设计模式
设计模式之所以会成为一种模式,是因为它确实是一种好的做法,符合软件设计原则,而设计原则就是这些模式背后的东西,就像每个成功的男人后面都站着一个女人。
掌握了这些设计原则,即便不刻意的使用某个设计模式往往也能写出符合某个设计模式的代码。
如当新用户注册完后,需要发送相关信息到后台汇总以便后面我们进行相关数据的分析。
interface UserSender {
void send(User user);
}
// 把用户信息发送给后台数据汇总模块
class UserCollectorSender implements UserSender {
private UserCollectorChannel chan;
public void send(final User user) {
channel.send(user);
}
}
复制代码
同时我们还需要把注册成功的消息通过短信通知给用户,这里会调用第三方短信服务,需要相关认证:
class UserSMSSender implements UserSender {
private String appKey;
private String appSecret;
private UserSMSChannel chan;
public void send(final User user) {
chan.send(appKey, appSecret, user);
}
}
复制代码
如果现在我们需要对用户信息做一些处理,如对密码等进行脱敏处理,同时,希望在短信发送成功之后,统计一些我们发送了多少信息。
如果不加思索的直接加上这段逻辑,那么两个类必然都会有着相同的逻辑,本着单一职责的原则,我们把这个处理放到父类中,于是代码变成了:
// 抽象父类
class BaseUserSender implements UserSender {
// 脱敏方法
protected User sanitize(final User user) {
// ...
}
// 收集信息
protected void collectMsgSendTimes(final User user) {
// ....
}
}
// 信息收集类
class UserCollectorSender extends BaseUserSender {
private UserCollectorChannel chan;
public void send(final User user) {
// 调用父类方法脱敏
User sanitizedUser = sanitize(user);
chan.send(sanitizedUser);
// 收集短信
collectMsgSendTimes(user);
}
}
// 短信发送
class UserSMSSender extends BaseUserSender {
private String appKey;
private String appSecret;
private UserSMSChannel chan;
public void send(final User user) {
// 调用父类方法脱敏
User sanitizedUser = sanitize(user);
chan.send(appKey, appSecret, user);
// 收集短信
collectMsgSendTimes(user);
}
}
复制代码
但是这两段代码除了发送部分不一样外,其他部分都是一样的。所以我们就可以考虑把共性的部分抽取出来,让子类实现差异性部分。
// 抽象父类
class BaseUserSender implements UserSender {
public void send(final User user) {
User sanitizedUser = sanitize(user);
// 发送部分,子类实现
doSend(user);
collectMsgSendTimes(user);
}
public void abstract doSend(final User user);
// 脱敏方法
private User sanitize(final User user) {
// ...
}
// 收集信息
private void collectMsgSendTimes(final User user) {
// ....
}
}
// 信息收集类
class UserCollectorSender extends BaseUserSender {
private UserCollectorChannel chan;
public void doSend(final User user) {
chan.send(sanitizedUser)
}
}
// 短信发送
class UserSMSSender extends BaseUserSender {
private String appKey;
private String appSecret;
private UserSMSChannel chan;
public void doSend(final User user) {
chan.send(appKey, appSecret, user);
}
}
复制代码
至此,其实我们已经实现了一个设计模式 -- 模板方法。我们只是遵循了单一职责原则,把重复代码一点点消除,结果就得到一个设计模式。在真实项目中,其实很难看出当前场景到底符合哪个设计模式,比较好的做法是遵循设计原则,不断的调整代码。
其实,只要我们遵循同样的原则,大多数设计模式是可以一点点推演出来的,设计模式是特定场景下的应用。
另眼看模式
首先需要看到模式的语言局限性。虽然设计模式本身不局限于语言,但是设计模式的出现本身就是受到语言自身的局限。Google 公司的研发总监 Peter Norving 在 1996 年分享的《动态语言设计模式》一文中就指出,设计模式在某种意义上就是为了解决语言自身缺陷的一种权宜之计。
随着技术和语言的发展,设计模式也在不断的演进更新。如单例设计模式,如果仅从单例的使用上来说,保证系统中只有一个对象,那么 Bean 容器完全可以替代单例模式。但是在某些特殊场景下就另当别论了。
此外,好的设计模式正在被框架或者类库所吸收,甚至被语言所吸收。如 JDK 中的 Observer 模式;Spring 框架中的监听器模式;以及 GO 语言中的用组合代替继承。
我们前面说过,好的设计模式正在被类库不断的吸收,而 Annotation 或者语法糖是消除设计模式的利器。
如 lombok 的 @Builder 注解就可以替代 Builder 模式;Java8 引入了 Lambda,Command 模式的写法会得到简化,比如写一个文件的操作的宏记录器,之前的版本需要声明很多类,类似下面这种:
Macro macro = new Macro();
macro.record(new OpenFile(fileRecevier));
macro.record(new ReadFile(fileRecevier));
macro.record(new WriteFile(fileRecevier));
macro.run();
复制代码
而有了 lamda,就可以简化一些:
Macro macro = new Macro();
macro.record(() -> fileRecevier.openFile());
macro.record(() -> fileRecevier.writeFile());
macro.record(() -> fileRecevier.readFile());
macro.run();
复制代码
总结
在实际工作中我们需要用变化的眼光去看待设计模式,代码需要根据场景需要动态的根据设计原则不断调整,而不是上来就按照某个设计模式去设计。此外有的设计模式是为了解决语言局限性的,所以不同的语言有些设计模式可能并不适应。
设计模式基于原则,而不限于模式,原则就像是河流的发源地,而终点是大象万千,千姿百态。
公众号:EthanBai
评论