首先,这是一本轻松的设计模式书,教你如何利用设计模式复用其他人的经验,如何利用设计模式提高代码的可维护性和可扩展性~
Head First陆续的介绍了策略模式、观察者模式、装饰者模式、工厂方法模式、抽象工厂模式、单件模式、命令模式、适配器模式、外观模式、模板方法模式、迭代器模式、组合模式、状态模式、代理模式,在介绍各种模式的期间,用简单的应用场景、通俗的语言引导读者去思考这些模式是如何利用和遵循相应OO原则的,然后再清晰的总结出每种模式的定义。
下面我再一一地总结一下学习到的每种设计模式。
问题/场景:
比如对于发消息的场景,系统消息、push消息的都遵循同样一组行为:行为A 校验消息内容无误,B 存储消息,C 校验是否超出发送限制,D 发送消息,假如两种消息的消息内容一致,所以两种方法的校验规则和存储方式一致,也就是行为A B是一样的,但是二者的发送限制不一致,也就是C,D行为不一致
解决方案:
定义:
策略模式定义了算法族,分别封装起来让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
其他:
我们会考虑发送消息的过程是校验消息内容、存储消息内容、校验发送限制、发送消息,当我们意识到这些行为可能会修改或扩展的时候,我们可以转换一下思考方式,发送消息的过程有4个行为,校验消息内容……
在设计代码的时候提前想到需要变化的部分,封装起来,日后的扩展便会简单一些~
要点总结:
- 封装变化
- 多用组合,少用继承
- 针对接口编程,不针对实现编程
问题/场景:
观察者模式的应用场景比较多,当目标状态发生改变的时候,需要通知多个依赖者,这种情况都可以使用观察者模式。比如对于某种套餐的订单,一个套餐订单(订单系统维护)包括会员身份(部门a维护)、邀约资源(部门b维护)、精品职位数(部门c维护)……当订单的状态改变的时候,我们需要部门a b c都做出相应的响应,处理各自维护的订单资源。
解决方案:
订单系统是真正拥有订单状态数据的对象——主题,部门a b c都是主题的依赖者,需要使用订单状态这同一个数据——观察者。
可能我们想到的方案是由主题提供查询订单状态的接口,观察者各自查询订单状态,这样每个观察者在查询的时候确实都能拿到订单状态,但是可能会导致两个问题,首先,观察者查询的时间是不确定的,也就是订单状态更新的时候,所有观察者并不能实时更新信息,直到自己主动去查询;其次当查询状态的接口变化的时候,需要所有的观察者各自作相应的修改,主题和观察者之间有很强的耦合。如果是订单状态改变的时候,主题发布一个通知,所有观察者监听这个消息。
订单系统需要实现主题接口,部门a b c分别注册为订单系统该主题的观察者,订单状态改变时,观察者都会收到订单系统发出的消息。
定义:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
其他:
我们这里忽略观察者模式和发布订阅模式的差别,观察者模式是有主题对象自己调度和发布通知,而发布订阅模式是有调度中心统一调度。
考虑到主题与观察者之间需要松耦合,方便观察者的扩展与删除,也方便主题房的修改冰箱观察者,我们可以采用观察者模式。
要点总结:
- 观察者模式定义了对象之间一对多的关系。
- 观察者与可观察者之间用松耦合的方式洁儿,互不知道对方的实现细节。
问题/场景:
估计我们平时接触到的最多的装饰者模式就是Java的IO类。在不修改FileInputStream这个基本的字节读取类的同时,增加新的功能利用缓冲来改变性能,那么我们就可以使用装饰着模式,利用BufferedInputStream这个装饰者类增加缓冲输入和readline()的功能。
解决方案:
按照上面的例子,BufferedInputStream扩展自FilterInputStream这个抽象的装饰类,BufferedInputStream拥有FileInputStream这个基础组件的行为,并且增加扩展的装饰者的行为。
定义:
装饰者模式动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。
其他:
装饰者会导致设计中出现很多的小对象,会让程序变得稍微复杂,但是对于一些动态扩展行为的场景来说,装饰者是很方便和易扩展的。
当我们在考虑需要扩展一个基本功能的组件时,我们可以考虑使用装饰者模式增强代码的弹性、可扩展性。
要点总结:
- 装饰者模式的原则是我们最常见的开闭原则。
- 使用组合和委托的方式,可以在运行时动态地加上新的行为。
问题/场景:
我们经常会有使用下面这种代码的场景:
Pizza pizza = null;
if(type == 1){
pizza = new CheesePizza();
}else if(type == 2){
pizza = new GreekPizza();
}else if ……
我们创建Pizza对象的时候可能都需要用到这段代码。
解决方案:
考虑到用到这段创建Pizza对象的地方可能会比较多,而且Pizza的种类可能需要增减,更好一点的方式,我们可能将这段放到一个专职创建Pizza对象的新对象中——“工厂”。
简单工厂我们可能会经常使用到。
public class PizzaFactory(){
Public Pizza createPizza(int type){
Pizza pizza = null;
if(type == 1){
pizza = new CheesePizza();
}else if(type == 2){
pizza = new GreekPizza();
}else if ……
return Pizza;
}
}
但是如果我们的场景进行扩充,最终创建出来的pizza,不仅依赖type——pizza种类,还需要依赖不同制造工艺style(比如芝加哥风格的,纽约风格的……)。那我们需要扩展PizzaFactory类,以满足创建pizza对象的需求,PizzaFactory的依赖的具体Pizza种类越来越多了。
为了减少依赖,我们将创建Pizza的方法作为一个抽象的方法factoryMethod,具体不同工艺的工厂来实现这个factoryMethod。
相对于前面的简单工厂的模式,当我们扩展一种加州风格的pizza时,不需要修改工厂方法,只需要增加一种工艺的工厂。(工厂与具体要创建的产品解耦)。
定义
工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
要点总结
工厂方法模式用到了依赖倒置原则——要依赖抽象,不要依赖具体类。(比如这里pizza工厂依赖的是Pizza抽象,具体的CheesePizza等也依赖Pizza抽象,即高层和底层模块都依赖Pizza这个抽象)
问题/场景:
继续扩展上面的场景,如果我们在PizzaFactory中创建CheesePizza的时候,需要考虑原料的不同,比如纽约风味的pizza都需要使用原料组合a(大蒜番茄酱料、干酪),而芝加哥需要使用另一种原料组合b(意大利白干酪和比萨草)。那么我们在createPizza创建对象的时候还需要把原料工厂传递过去。
解决方案:
CheesePizza类的构造方法换成CheesePizza(原料工厂抽象类),计提的所有原料怎么组合怎么选择,全部由具体的原料工厂决定。
原料工厂的接口如下:
public interface PizzaIngredientFactory{
public Sauce CreateSauce();//酱料
public Cheese CreateCheese();//芝士
……
}
具体的原料工厂决定具体的原料。
定义
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要只能具体类。
其他:
抽象工厂模式看起来和工厂方法模式很类似,都是负责创建对象,但是不同在于抽象工厂模式利用对象的组合,本身定义了产品的创建方法,必须要实现这些方法来完成产品家族的创建;而工厂方法利用的是继承,通过继承来覆盖工厂方法,完成产品的创建。
要点总结:
- 工厂都是用来封装对象的创建。
- 工厂方法模式使用继承,抽象工厂使用对象组合;两者利用抽象的原则,将具体的实例化过程延迟到子类。
- 工厂利用的最重要和基本的原则——依赖抽象,不要依赖具体类。
TODO 其他模式的介绍&&模式归类与比较后续添加
参考书籍:《Head First 设计模式》