比方说你今天不想开车,于是打电话叫了出租车。只要出租车能把你送到目的地,你不太在意车的牌子和型号。你会对出租车司机说的第一句话就是“送我去X”,X就是你想去的地方。然后出租车司机就开始执行一系列的“命令”(送刹车、换挡、踩油门等)。出租车司机抽象走了驾驶汽车的底层复杂操作的细节。他通过提供驾驶服务(简化了的接口),把你与原本复杂的车辆操作接口分离开来。出租车司机与你之间的接口只是一个简单的”送我去X”命令。并且这个命令也不依赖车辆具体的品牌或型号。
很多旧的面向对象应用程序中,可能有许多类分散于带有各种功能的系统之中。要把这些类用于某个功能,需要知道全部细节才能放在一组算法中使它们。如果从逻辑上将其中一些类组合成为一个简化的接口,可以让这些类更容易于使用,为子系统中一组不同接口提供统一的一种外观模式。
外观模式
外观模式为子系统中一组不同的接口提供统一的接口。外观定义了上层接口,通过降低复杂度和隐藏子系统间的通信及依存关系,让子系统更易于使用。这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。
比方说子系统中有一组不同的类,其中一些彼此依赖,如图:10-1所示,这让客户端难以使用子系统中的类,因为客户端需要知道每一个类。有时如果客户端只是需要其默认行为而不定制,这会是不必要的麻烦。外观起到整个子系统的入口的作用。有些客户端只需要子系统的某些基本行为,而对子系统的类不做太多的定制,外观为这样的客户端提供简化的接口。外观就像前面例子里的出租车司机,只有需要从某些子系统的类定制更多行为的客户端,才会关注外观背后的细节。出租车的场景可以提供带有路上的“默认”行为的驾驶服务。
有一位乘客和一辆出租车,出租车为驾驶出租车的一组复杂接口提供了一个简化了的接口。如果放到类图中,如下图:
由上图可知,整个出租车服务作为一个封闭系统,包括一名出租车司机、一辆车和一台计价器。同系统交互的唯一途径是通过CarDriver中定义的接口driveToLocation:x.一旦乘客向出租车司机发出driveToLocation:x消息,CarDriver就会收到这个消息。司机需要操作两个子系统Taximeter和Car。CarDriver先会启动(start)Taximeter,让它开始计价,然后司机对汽车会松刹车( releaseBrakes)、换挡(changeGears)、踩油门(pressAccelerator),把车开走。直到到达地点x,CarDriver会松油门(releaseAccelerator)、踩刹车(pressBrakes)、停止(stop)Taximeter,结束行程。一切都发生于给CarDriver的一个简单的driveToLocation:x.命令之中。无论这两个子系统有多么复杂,它们隐藏于乘客的视线之外,因此CarDriver是在为出租车子系统中的其他复杂接口提供一个简化的接口。CarDriver像“外观”一样,处于乘客与出租车子系统之间。具体实现代码如下:
定义一个Car类,该类中包含了汽车的一些基本操作
class Car: NSObject{
func releaseBrakes(){
print("releaseBrakes --- 松刹车")
}
func changeGears(){
print("changeGears --- 换挡")
}
func pressAccelerator(){
print("pressAccelerator --- 踩油门")
}
func pressBrakes(){
print("pressBrakes --- 踩刹车")
}
func releaseAccelerator(){
print("releaseAccelerator --- 松油门")
}
}
定义一个Taximeter,拥有开始、结束计价等基本操作
class Taximeter: NSObject {
func start(){
print("start --- 开始计价")
}
func stop(){
print("stop --- 停止计价")
}
}
定义一个CarDriver类,目前出租车服务系统里有两个复杂的子系统,需要一个CarDriver(出租车司机)作为“外观”以简化接口。其代码如下:
class CarDriver: NSObject{
func driveToLocation(x : String){
//启动计价器
let meter = Taximeter()
meter.start()
//操作车辆,直到抵达位子x
let car = Car()
car.releaseBrakes()
car.changeGears()
car.pressAccelerator()
print("---- 经过一段时间 ----")
//当达到目的地,听停下车和计价器
car.releaseAccelerator()
car.pressBrakes()
meter.stop()
print("------\(x)-------")
}
}
在driveToLocation方法中,首先启动一个Taximeter(计价器)对象,让它从那一刻开始计价。然后转到Car(汽车)对象,开始对它进行操作。向Car发送releaseBrakes(松刹车)、changeGears(换挡)、pressAccelerator(踩油门)等消息把车开走,当到达了目的地x,命令Car(汽车)releaseAccelerator(松油门)、pressBrakes(刹车),最后让Taximeter(计价器)对象停止,这样服务就结束了。简单使用如下:
let cardriver = CarDriver()
cardriver.driveToLocation(x: "上海宝山区万达广场")
/*
start --- 开始计价
releaseBrakes --- 松刹车
changeGears --- 换挡
pressAccelerator --- 踩油门
---- 经过一段时间 ----
releaseAccelerator --- 松油门
pressBrakes --- 踩刹车
stop --- 停止计价
------上海宝山区万达广场-------
*/
enum Eternal{
static func setObject(value: Any!,forKey defaultName: String!){
let defaults:UserDefaults = UserDefaults.standard
defaults.setValue(value, forKey: defaultName)
defaults.synchronize()
}
static func objectForKey(defaultName: String!) ->Any?{
let defaults:UserDefaults = UserDefaults.standard
return defaults.object(forKey: defaultName)
}
}
简单使用
Eternal.setObject(value: "Jack", forKey: "name")
let name = Eternal.objectForKey(defaultName: "name") as? String
模式分析
1:根据“单一职责原则”,在软件中将一个系统划分为若干个子系统有利于降低整个系统的复杂性,一个常见的设计目标是使子系统间的通信和相互依赖关系达到最小,而达到该目标的途径之一就是引入一个外观对象,它为子系统的访问提供了一个简单而单一的入口。
2:外观模式也是“迪米特法则”的体现,通过引入一个新的外观类可以降低原有系统的复杂度,同时降低客户类与子系统类的耦合度。
3:外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。
4:外观模式的目的在于降低系统的复杂程度。
5:外观模式从很大程度上提高了客户端使用的便捷性,使得客户端无须关心子系统的工作细节,通过外观角色即可调用相关功能。
优点
1:对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
2:实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
3:降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
4:只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。
缺点
1:不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
2:在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。
何时使用外观模式
1:子系统正逐渐变得复杂。应用模式的过程中演化出许多类。可以使用外观为这些子系统类简单提供一个较简单的接口。
2:可以使用外观对子系统进行分层。每个子系统级别有一个外观作为入口点。让它们通过其外观进行通信,可以简化它们的依赖关系。
总结
当程序逐渐变大变复杂时,会有越来越多小型的类从设计和应用模式中演化出来。如果没有一种简化的方式来使用这些类,客户端最终将变得越来越大、越来越难以理解,而且,维护起来会繁琐无趣。外观有助于提供一种更为简洁的方式来使用子系统中的这些类。处理这些子系统类的默认行为,可能只是定义在外观中的一个简单的方法,而不必直接去使用这些类。
参考