设计模式之外观模式和备忘录模式
外观模式
概述
外观模式,我们通过外观的包装,使应用程序只能看到外观对象,而不会看到具体的细节对象,这样无疑会降低应用程序的复杂度,并且提高了程序的可维护性。
为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
例子:一个电源总开关可以控制四盏灯、一个风扇、一台空调和一台电视机的启动和关闭。该电源总开关可以同时控制上述所有电器设备,电源总开关即为该系统的外观模式设计。
类图
外观模式与适配器的差异
外观模式:提供简化的接口,也将客户从组件的子系统中解耦,仍然可以直接调用子系统中的接口,其主要是提供一个整洁的一致的接口给客户端
适配器:增加适配接口,将一个接口通过适配来间接转换为另一个接口,以符合客户的期望
外观和适配器可以“包装”许多类,差异不在于它们包装了几个类,而在于它们的意图:适配器的意图是改变接口符合客户的期望,外观模式的意图是提供子系统的一个简化接口实例
/**
* 子系统类
*
*/
class familyAppliances {
private String state = "off";
public String getState() {
return state;
}
public void turnOn() {
this.state = "turn on";
}
public void turnOff() {
this.state = "turn off";
}
}
// 电灯
class Light extends familyAppliances {
}
// 电扇
class Fan extends familyAppliances {
}
// 空调
class AirConditioner extends familyAppliances {
}
// 电视
class Television extends familyAppliances {
}
/**
* 外观类
*
*/
class SwitchFacade {
Light light = new Light();
Fan fan = new Fan();
AirConditioner ac = new AirConditioner();
Television tv = new Television();
// 晚上需要对灯(light)进行操作
public void night(String button) {
if (button == "ON") {
this.light.turnOn();
this.fan.turnOn();
this.ac.turnOn();
this.tv.turnOn();
} else {
this.light.turnOff();
this.fan.turnOff();
this.ac.turnOff();
this.tv.turnOff();
}
}
// 白天不需要对灯(light)进行操作
public void day(String button) {
if (button == "ON") {
this.fan.turnOn();
this.ac.turnOn();
this.tv.turnOn();
} else {
this.fan.turnOff();
this.ac.turnOff();
this.tv.turnOff();
}
}
// 打印当前家电的状态
public void printState() {
System.out.println("light state:" + this.light.getState());
System.out.println("fan state:" + this.fan.getState());
System.out.println("ac state:" + this.ac.getState());
System.out.println("tv state:" + this.tv.getState());
System.out.println("operation success! \n");
}
}
/**
* 客户类
*
*/
public class clientFacade {
public static void main(String[] args) {
SwitchFacade facade = new SwitchFacade();
System.out.println("initialize...");
facade.printState();
facade.day("ON");
System.out.println("day ON...");
facade.printState();
facade.night("OFF");
System.out.println("night OFF...");
facade.printState();
}
}
Run result:
优缺点
Facade模式有下面一些优点:- 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
- 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
- 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
- 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
- 最少知识原则:只和你的密友谈话
Facade模式的缺点
- 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
- 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
备忘录模式
概述
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。设计角色
Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。
Memento(备忘录):负责存储Originator对象的内部状态,并可以防止Originator以外的其他对象访问备忘录。
备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据。- Caretaker(管理者):负责备忘录Memento,不能对Memento的内容进行访问或者操作。
类图
实例
//发起人
class Originator {
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Memento createMemento() {
return new Memento(this.state);
}
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
//备忘录
class Memento {
private String state = "";
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
//管理者
class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
//客户类
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("state one"); //初始化发起人状态
System.out.println("first state:" + originator.getState());
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento()); //将发起人的当前状态存储到备忘录中
originator.setState("state two"); //设置第二种状态
System.out.println("changed state:" + originator.getState());
originator.restoreMemento(caretaker.getMemento()); //将发起人状态恢复成备忘录中的状态
System.out.println("restored state:" + originator.getState());
}
}
Run result:
Memento优缺点
备忘录模式的优点:- 当发起人角色中的状态改变时,有可能是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。
- 备份的状态是保存在发起人角色之外,这样发起人角色就不需要对各个备份的状态进行管理
备忘录模式的缺点:
- 在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗比较严重。
适用场景
如果有需要提供回滚操作的需求,使用备忘录模式非常适合,比如文本编辑器的Ctrl+Z操作等。