承上,该篇浅谈11种行为型设计模式中的6种:观察者、模板方法、策略、状态、命令、备忘录。
走起:
1、观察者模式(Observer Pattern)
定义对象间一对多的依赖关系,以便一个对象发生变化时,所有依赖它的对象都得到通知并自动刷新。
实现观察者模式有很多形式,比较直观的是“注册——通知——撤销注册”的形式。
角色:
观察者:观察者将自己注册到被观察对象中,被观察对象将观察者存放在一个容器中。
被观察者:当被观察对象发生某种变化时,从容器中得到所有注册过的观察者,并将变化通知观察者。
撤销观察:观察者告诉被观察者要撤销观察,被观察者从容器中将观察者去除。
其中观察者角色基于接口实现,会给程序提供更大灵活性(多态特性)。
复制代码
上代码:
public interface Observer{//观察者
void update();
}
public class Observable{被观察者(以下代码为精简代码,只为理解思想用)
private List<Observer> obs = new ArrayList();
public void registerObserver(Observer o){//注册
obs.addElement(o);
}
public void unRegisterObserver(Observer o){//反注册
obs.remove(o);
}
public void notifyObservers(){//通知
for(Observer o : obs) {
o.update();
}
}
}
public class XiaoMiCompany extends Observable{//具体被观察者
public void publishMi9(){
notifyObservers();
}
}
public class Person implements Observer{//具体观察者
@Override
public void update(){
//自行实现
}
}
main() {
XiaoMiCompany miCompany = new XiaoMiCompany();
Person p1 = new Person();
Person p2 = new Person();
miCompany.registerObserver(p1);
miCompany.registerObserver(p2);
miCompany.publishMi9();
}
复制代码
注:以上代码仅供理解观察者模式思想,具体实现请自行查阅java中Observer/Observable源码(源码优秀,参数/同步/判空等等)。
观察者模式与接口回调区别:
重点在于接口回调的定义:
百度百科:接口回调其本质与上转型是一样的,不同的是:接口回调是用接口句柄来得到并调用实现这个接口的子类的引用;而上转型则是用父类句柄来得到并调用继承此父类的子类的引用。
解释一下:两者均是父引用指向子类对象(多态),上转型是类继承关系,接口回调则是接口与实现类关系。
百度文库:通常情况下,我们创建一个对象,并马上直接使用它的方法。然而有些情况下,希望能在某些场景出现后或条件满足时才调用该对象方法。回调就是解决这个延迟调用对象方法的问题。这个被调用方法的对象被称为回调对象。
个人理解:多态思想完美契合,运行时才最终确定执行对象。
综上,易知:
1、观察者模式是设计模式中的一种,由观察者、被观察者两个角色构成,需提供相应注册/反注册/通知等方法。
2、接口回调则是多态表现形式中的一种,凡是父类引用(特指接口)指向子类对象即可视为接口回调。
当然如若对两者洞察秋毫,请自行拓展。
显然绝大部分设计模式(观察者模式也不例外)都使用了接口回调,但其他设计模式和观察者模式有
本质区别(不过设计模式也往往配合使用:如android适配器模式adapter中使用了观察者模式)
复制代码
2、模板方法模式(Template Method Pattern)
模板方法模式定义了一个算法的步骤,并允许子类别为一个或多个步骤提供其实现方式。
让子类别在不改变算法架构的情况下,重新定义算法中的某些步骤。
常用在:
1、某些算法中实现了相同方法,造成程式码重复;
2、控制子类必须遵守的一些事项。
复制代码
talk is cheap:
public abstract class PhoneDevelop{//定义研发手机流程
public void developPhone(){
assemblyHardWare();//组装硬件
loadSoftWare();//预装软件
}
public abstract void assemblyHardWare();
public abstract void loadSoftWare();
}
public class XiaoMiCompany extends PhoneDevelop{//小米研发手机
@Override
public void assemblyHardWare(){}//自行实现
@Override
public void loadSoftWare(){}//自行实现
}
main(){
XiaoMiCompany xiaomiCompany = new XiaoMiCompany();
xiaomiCompany.developPhone();
}
复制代码
该模式核心:抽象类中已经定义过抽象方法及其调用顺序,子类只需要实现对应抽象方法即可。
该模式经常使用:如诸位BaseActivity/BaseFragment中的initView()、initData()方法。
模板方法模式与建造者模式有异曲同工之妙,均是定义好抽象流程方法,子类去具体实现。
不同点在于:
1、建造者模式是创建型,即目的是组建复杂对象(必有返回对象),模板方法模式则可有可无;
2、标准建造者模式由抽象建造者/具体建造者/指挥者角色构成,显然模板方法模式没有;
3、建造者模式中只有建造部件方法(均围绕build展开),模板方法模式则可有已经实现的默认
方法(如BaseActivity中可已默认实现沉浸式/网络/日志等公有方法);
4、省略抽象建造者/指挥者的建造者模式可视为特殊的模板方法模式。
复制代码
3、策略模式(Strategy Pattern)
定义:指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。
要点:
1、定义了一组算法(业务规则);
2、封装了每个算法;
3、这族的算法可互换代替(interchangeable)。
组成:
抽象策略角色: 策略类通常由一个接口或者抽象类实现。
具体策略角色:包装相关的算法和行为。
环境角色:持有一个策略类引用,最终给客户端调用。
应用场景:
1、 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
2、 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
3、 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
优点:
1、提供管理相关算法族的办法。策略类的等级结构定义了一个算法或行为族。
恰当使用继承可以把公共代码转移到父类,从而避免重复代码。
2、提供可替换继承关系的办法。即算法可相互替代。
3、避免使用多重条件转移语句。多重转移语句(简言之即if else)不易维护。
复制代码
show the code:
public interface SaleStrategy{//抽象策略
String saleMethod();
}
public class HungryStrategy{//具体策略(饥饿营销)
@Override
public String saleMethod(){
return "无限造势,且没现货";
}
}
public class DiscountStrategy{//具体策略(降价折扣)
@Override
public String saleMethod(){
return "突出一个字:便宜";
}
}
public class XiaoMiMarket{//环境类
private SaleStrategy saleStrategy;
public XiaoMiMarket(SaleStrategy saleStrategy){
this.saleStrategy = saleStrategy();
}
public String saleMiPhone(){
return saleStrategy.saleMethod();
}
}
main(){
//自行拓展其他销售策略
SaleStrategy hungryStrategy = new HungryStrategy();
XiaoMiMarket miMarket = new XiaoMiMarket(hungryStrategy);
miMarket.saleMiPhone();
}
复制代码
所有策略完成的都是相同工作,只是实现不同。
4、状态模式(State Pattern)
定义:允许一个对象在其内部状态改变时改变其行为,该对象看起来似乎修改了它的类。
目的:当控制一个对象状态的条件表达式过于复杂时,把状态的判断逻辑转移到表示不同
状态的一系列类中,可以把复杂的判断逻辑简化。
复制代码
示例:
public interface LogisticsState{//抽象状态(物流状态)
String transport();
}
public class SortState implements LogisticsState{//分拣状态
@Override
public String transport(Mi9Phone mi9Phone){
boolean sortFinish = true;//自行定义分拣逻辑
if(sortFinish){
mi9Phone.setLogisticsState(new TransportState());//切换至下一状态
return mi9Phone.transport();
}else{
return "分拣中,请耐心等待";
}
}
}
public class TransportState implements LogisticsState{//运输状态
@Override
public String transport(){
boolean transportFinish = true;//自行定义运输逻辑
if(transportFinish){
mi9Phone.setLogisticsState(new DistributionState());//切换至下一状态
return mi9Phone.transport();
}else{
return "运输中,不要着急哦";
}
}
}
public class DistributionState implements LogisticsState{//配送状态
@Override
public String transport(){
boolean distributionFinish = true;//自行定义配送逻辑
if(distributionFinish){//最后状态无需切换下一状态
return "已到货,请注意查收";
}else{
return "配送中,马上就到了";
}
}
}
public class Mi9Phone{//环境类
private LogisticsState state;
public Mi9Phone(LogisticsState state){
this.state = state;
}
public String transport(){//判断逻辑已转至状态类中
return state.transport();
}
get();//省略get/set
set();
}
main(){
//自行拓展其他状态
LogisticsState sortState = new SortState();
Mi9Phone miPhone = new Mi9Phone(sortState);
miPhone.transport();
}
复制代码
状态模式与策略模式的结构组成完全一致:接口+实现+环境类。
不同点在于:
1、策略模式强调的是同一功能的不同算法实现(算法可互相替代);
2、状态模式强调的是同一对象的不同状态切换(状态可按逻辑切换)。
复制代码
5、命令模式(Command Pattern)
定义:将一组行为抽象为对象,实现行为请求者和行为实现者之间的解耦,就是命令模式。
组成:
Command:定义命令接口,声明执行方法。
ConcreteCommand:"虚实现",通常持有接收者,并调用接收者功能。
Receiver:接收者,真正执行命令的对象。任何能实现命令规定功能的类都可成为一个接收者。
Invoker:要求命令对象执行请求,通常持有命令对象。是使用命令对象的入口。
Client:创建具体的命令对象,并设置命令对象的接收者。即组装命令对象和接收者。
模式分析:
1、本质是对命令封装,将发出命令和执行命令的责任分开;
2、请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作;
3、允许请求的一方和接收的一方独立开来,使请求的一方不必知道接收请求的一方的接口,更不
必知道请求是怎么被接收,以及操作是否被执行、何时被执行、如何被执行。
4、使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
5、关键在于引入了抽象命令接口只有实现了抽象命令接口的具体命令才能与接收者相关联。
优点:
1、降低对象间耦合;
2、易扩展新命令;
3、易组合命令。
4、调用同一方法实现不同的功能。
适用环境:
1、需将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
2、需在不同的时间指定请求、将请求排队和执行请求;
3、需支持命令的撤销(Undo)和恢复(Redo)操作;
4、需将一组操作组合在一起,即支持宏命令。
复制代码
示例:
public interface ICommand{
void execute();
}
public class KeyBackCommand implements ICommand{
private Mi9Phone mi9Phone;
public KeyBackCommand(Mi9Phone mi9Phone){
this.mi9Phone = mi9Phone;
}
@Override
public void execute(){
mi9Phone.keyBack();
}
}
public class KeyMenuCommand implements ICommand{
private Mi9Phone mi9Phone;
public KeyMenuCommand(Mi9Phone mi9Phone){
this.mi9Phone = mi9Phone;
}
@Override
public void execute(){
mi9Phone.keyMenu();
}
}
public class Mi9Phone{//接收者,真正执行操作
public void keyBack(){}//具体实现
public void keyMenu(){}//具体实现
}
public class Mi9Invoker{//持有命令对象
private ICommand command;
public Mi9Invoker(ICommand command){
this.command = command;
}
public void executeCommand(){
command.execute();
}
}
main(){
Mi9Phone mi9Receviver = new Mi9Phone();
KeyBackCommand keyBackCommand = new KeyBackCommand(mi9Receviver);
Mi9Invoker mi9Invoker = new Mi9Invoker(keyBackCommand);
mi9Invoker.executeCommand();
}
复制代码
理解一波(与上述适用场景一一对应):
1、命令调用者执行者解耦显然易见;
2、对命令请求的时长、队列、执行可在invoker.executeCommand()中统一处理(添加命令集合);
3、显然invoker.executeCommand()中易支持Undo、Redo操作(添加命令集合);
4、invoker.executeCommand()也可进行命令的组合(添加命令集合)。
复制代码
so,命令模式的巧妙之处: 1、命令执行者与命令请求者解耦; 2、对命令的一系列操作及其容易(因为所有的命令入口统一在invoker.executeCommand中)。
android中的handler个人理解是命令模式的变体,即将msg.what理解为命令,handler理解为命令请求者,looper理解为命令执行者(其实并不是,真正的执行者为msg/handler)。
6、备忘录模式(Memento Pattern)
定义:又叫做快照模式(Snapshot Pattern)或Token模式。在不破坏封闭的前提下,捕获一个对象
的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
构成:
Originator(发起人):负责创建一个备忘录Memento,记录当前自身的内部状态,并可使用备忘录恢复内 部状态。Originator可根据需要决定Memento存储自己的哪些内部状态。
Memento(备忘录):负责存储Originator对象的内部状态,并防止除Originator以外的其他对象访问备忘 录。备忘录有两个接口:提供给Caretaker的窄接口,他只能将备忘录传递给其他对象。提供给
Originator的宽接口,允许它访问恢复先前状态的所需数据。
Caretaker(管理者):负责备忘录Memento,不能对Memento的内容进行访问或操作。
优点:
1、有时发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须由发起人对象自己读取,这 时,备忘录模式可把发起人内部信息对其他对象屏蔽,从而恰当保持封装边界。(即数据安全)
2、该模式简化了发起人类。发起人不再需要管理和保存其内部状态的个个版本,客户端可自行管理他 们所需的状态版本。(灵活保存各状态并恢复)
3、发起人角色状态改变时,可能该状态无效,此时可使用备忘录将状态复原。(状态有误时可还原)
复制代码
OK,简单:
public class Mi9Phone{//发起人
private String systemFile;
private String systemSoft;
public Mi9Memento createMemento(){
Mi9Memento memento = new Mi9Memento();
memento.setSystemFile(this.systemFile);
memento.setSystemSoft(this.systemSoft);
return memento;
}
public void resetMemento(Mi9Memento memento){
this.systemFile = memento.systemFile;
this.systemSoft = memento.systemSoft;
}
set()//省略get/set方法
get()
}
public class Mi9Memento{//米9备忘录
private String systemFile;
private String systemSoft;
set()//省略get/set方法
get()
}
public class Mi9Caretaker{//米9管理者
Mi9Memento memento;
public Mi9Memento getMemento() {
return memento;
}
public void setMemento(Mi9Memento memento) {
this.memento = memento;
}
}
main(){
Mi9Phone phone = new Mi9Phone();
phone.setSystemFile("DCIM+Android");
phone.setSystemSoft("小爱同学");//假定为出厂设置
Mi9Memento memento = Mi9phone.createMemento();
Mi9Caretaker caretaker = new Mi9Caretaker();
caretaker.setMemento(memento);
phone.setSystemSoft("知乎");//装其他软件
phone.resetMemento(caretaker.getMemento());//一键恢复出厂设置
}
复制代码
该模式并非面向接口,在设计模式里算是少见。
该示例跟各资料的代码基本吻合,but:
1、笔者似乎并没看到Memento的宽窄接口;
2、Caretaker存在的价值和意义是什么呐(仅是Memento的get,set方法而已)。
以上两点疑惑,还请各位大佬指教。
复制代码
7、个人总结:
好吧,陆陆续续该篇已拖了一个周,不禁感慨写博客确实是个费劲的事。
总结待续…………
复制代码
//TODO 未完待续…………
注:笔者水平有限,以上均为个人理解,仅为抛砖引玉。如有疏误,还请各位不吝赐教。
资料来源:
百度百科;
大话设计模式;
其他相关博客;
以及个人理解。
复制代码