jtahstu的博客

root@jtahstu.com   Github   英文博客  

最新碎语:以后没事写写小的知识点吧

您的位置:jtahstu的博客 >笔记> 设计模式从入门到放弃 - 外观模式

设计模式从入门到放弃 - 外观模式

外观模式定义

    为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度

外观模式概述

    在软件开发中,有时候为了完成一项较为复杂的功能,一个客户类需要和多个业务类交互,而这些需要交互的业务类经常会作为一个整体出现,由于涉及到的类比较 多,导致使用时代码较为复杂,此时,特别需要一个类似服务员一样的角色,由它来负责和多个业务类进行交互,而客户类只需与该类交互。外观模式通过引入一个 新的外观类(Facade)来实现该功能,外观类充当了软件系统中的“服务员”,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。在外观模式中,那些需要交互的业务类被称为子系统(Subsystem)。如果没有外观类,那么每个客户类需要和多个子系统之间进行复杂的交互,系统的耦合度将很大,如图2(A)所示;而引入外观类之后,客户类只需要直接与外观类交互,客户类与子系统之间原有的复杂引用关系由外观类来实现,从而降低了系统的耦合度,如图2(B)所示。

2 外观模式示意图

       外观模式中,一个子系统的外部与其内部的通信通过一个统一的外观类进行,外观类将客户类与子系统的内部复杂性分隔开,使得客户类只需要与外观角色打交道,而不需要与子系统内部的很多对象打交道。

外观模式结构

      外观模式没有一个一般化的类图描述,图3所示的类图也可以作为描述外观模式的结构图:

3 外观模式结构图

       由图3可知,外观模式包含如下两个角色:

      (1) Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

      (2) SubSystem(子系统角色):在 软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调 用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。

外观模式实现

      外观模式的主要目的在于降低系统的复杂程度,在面向对象软件系统中,类与类之间的关系越多,不能表示系统设计得越好,反而表示系统中类之间的耦合度太大, 这样的系统在维护和修改时都缺乏灵活性,因为一个类的改动会导致多个类发生变化,而外观模式的引入在很大程度上降低了类与类之间的耦合关系。引入外观模式 之后,增加新的子系统或者移除子系统都非常方便,客户类无须进行修改(或者极少的修改),只需要在外观类中增加或移除对子系统的引用即可。从这一点来说, 外观模式在一定程度上并不符合开闭原则,增加新的子系统需要对原有系统进行一定的修改,虽然这个修改工作量不大。

      外观模式中所指的子系统是一个广义的概念,它可以是一个类、一个功能模块、系统的一个组成部分或者一个完整的系统。子系统类通常是一些业务类,实现了一些具体的、独立的业务功能,其典型代码如下:

class SubSystemA {
	public void MethodA() {
		// 业务实现代码
	}
}

class SubSystemB {
	public void MethodB() {
		// 业务实现代码
	}
}

class SubSystemC {
	public void MethodC() {
		// 业务实现代码
	}
}
在引入外观类之后,与子系统业务类之间的交互统一由外观类来完成,在外观类中通常存在如下代码:


class Facade {
	private SubSystemA obj1 = new SubSystemA();
	private SubSystemB obj2 = new SubSystemB();
	private SubSystemC obj3 = new SubSystemC();

	public void Method() {
		obj1.MethodA();
		obj2.MethodB();
		obj3.MethodC();
	}
}
    由于在外观类中维持了对子系统对象的引用,客户端可以通过外观类来间接调用子系统对象的业务方法,而无须与子系统对象直接交互。引入外观类后,客户端代码变得非常简单,典型代码如下:
class Program {
	static void Main(string[] args) {
		Facade facade = new Facade();
		facade.Method();
	}
}

外观模式示例UML图

Java示例代码

//Light.java
public class Light {
	private String position;

	public Light(String position) {
		this.position = position;
	}

	public void on() {
		System.out.println(this.position + "灯打开!");
	}

	public void off() {
		System.out.println(this.position + "灯关闭!");
	}
}

//Fan.java
public class Fan {
	public void on() {
		System.out.println("风扇打开!");
	}

	public void off() {
		System.out.println("风扇关闭!");
	}

}

//AirConditioner.java
public class AirConditioner {
	public void on() {
		System.out.println("空调打开!");
	}

	public void off() {
		System.out.println("空调关闭!");
	}
}

//Television.java
public class Television {
	public void on() {
		System.out.println("电视机打开!");
	}

	public void off() {
		System.out.println("电视机关闭!");
	}
}

//GeneralSwitchFacade.java(总开关类) 外观类
public class GeneralSwitchFacade {
	private Light lights[] = new Light[4];
	private Fan fan;
	private AirConditioner ac;
	private Television tv;

	public GeneralSwitchFacade() {
		lights[0] = new Light("左前");
		lights[1] = new Light("右前");
		lights[2] = new Light("左后");
		lights[3] = new Light("右后");
		fan = new Fan();
		ac = new AirConditioner();
		tv = new Television();
	}

	public void on() {
		lights[0].on();
		lights[1].on();
		lights[2].on();
		lights[3].on();
		fan.on();
		ac.on();
		tv.on();
	}

	public void off() {
		lights[0].off();
		lights[1].off();
		lights[2].off();
		lights[3].off();
		fan.off();
		ac.off();
		tv.off();
	}
}

//Client.java
public class Client {
	public static void main(String args[]) {
		GeneralSwitchFacade gsf = new GeneralSwitchFacade();
		gsf.on();
		System.out.println("-----------------------");
		gsf.off();
	}
}
PHP代码演示

<?php
/**
 * 外观模式
 *
 */
class SwitchFacade {
	private $_light = null;
	//电灯
	private $_ac = null;
	//空调
	private $_fan = null;
	//电扇
	private $_tv = null;
	//电视

	public function __construct() {
		$this -> _light = new Light();
		$this -> _fan = new Fan();
		$this -> _ac = new AirConditioner();
		$this -> _tv = new Television();
	}

	/**
	 * 晚上开电灯
	 *
	 */
	public function method1($isOpen = 1) {
		if ($isOpen == 1) {
			$this -> _light -> on();
			$this -> _fan -> on();
			$this -> _ac -> on();
			$this -> _tv -> on();
		} else {
			$this -> _light -> off();
			$this -> _fan -> off();
			$this -> _ac -> off();
			$this -> _tv -> off();
		}

	}

	/**
	 * 白天不需要电灯
	 *
	 */
	public function method2() {
		if ($isOpen == 1) {
			$this -> _fan -> on();
			$this -> _ac -> on();
			$this -> _tv -> on();
		} else {
			$this -> _fan -> off();
			$this -> _ac -> off();
			$this -> _tv -> off();
		}
	}

}

/******************************************子系统类 ************/
/**
 *
 */
class Light {
	private $_isOpen = 0;
	public function on() {
		echo 'Light is open', '<br/>';
		$this -> _isOpen = 1;
	}

	public function off() {
		echo 'Light is off', '<br/>';
		$this -> _isOpen = 0;
	}

}

class Fan {
	private $_isOpen = 0;
	public function on() {
		echo 'Fan is open', '<br/>';
		$this -> _isOpen = 1;
	}

	public function off() {
		echo 'Fan is off', '<br/>';
		$this -> _isOpen = 0;
	}

}

class AirConditioner {
	private $_isOpen = 0;
	public function on() {
		echo 'AirConditioner is open', '<br/>';
		$this -> _isOpen = 1;
	}

	public function off() {
		echo 'AirConditioner is off', '<br/>';
		$this -> _isOpen = 0;
	}

}

class Television {
	private $_isOpen = 0;
	public function on() {
		echo 'Television is open', '<br/>';
		$this -> _isOpen = 1;
	}

	public function off() {
		echo 'Television is off', '<br/>';
		$this -> _isOpen = 0;
	}

}

/**
 * 客户类
 *
 */
class client {
	static function open() {
		$f = new SwitchFacade();
		$f -> method1(1);
	}

	static function close() {
		$f = new SwitchFacade();
		$f -> method1(0);
	}

}

client::open();

模式扩展

一个系统有多个外观类:

        在外观模式中,通常只需要一个外观类,并且此外观类只有一个实例,换言之它是一个单例类。在很多情况下为了节约系统资源,一般将外观类设计为单例类。当然这并不意味着在整个系统里只能有一个外观类,在一个系统中可以设计多个外观类,每个外观类都负责和一些特定的子系统交互,向用户提供相应的业务功能。

不要试图通过外观类为子系统增加新行为:

       不要通过继承一个外观类在子系统中加入新的行为,这种做法是错误的。外观模式的用意是为子系统提供一个集中化和简化的沟通渠道,而不是向子系统加入新的行为,新的行为的增加应该通过修改原有子系统类或增加新的子系统类来实现,不能通过外观类来实现。

外观模式与迪米特法则:

       外观模式创造出一个外观对象,将客户端所涉及的属于一个子系统的协作伙伴的数量减到最少,使得客户端与子系统内部的对象的相互作用被外观对象所取代。外观类充当了客户类与子系统类之间的“第三者”,降低了客户类与子系统类之间的耦合度,外观模式就是实现代码重构以便达到“迪米特法则”要求的一个强有力的武器。

抽象外观类的引入:

    外观模式最大的缺点在于违背了开闭原则,

    当增加新的子系统或者移除子系统时需要修改外观类,可以通过引入抽象外观类在一定程度上解决该问题,客户端针对抽象外观类进行编程。对于新的业务需求,不修改原有外观类,而对应增加一个新的具体外观类,由新的具体外观类来关联新的子系统对象,同时通过修改配置文件来达到不修改源代码并更换外观类的目的。

UML:


外观模式优点

1、对客户屏蔽子系统组件减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。

2、实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。

3、降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。

4、只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类

外观模式的缺点

1、 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性

2、 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”

外观模式适用性

1、 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。facade可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过facade层。
    2、客户程序与抽象类的实现部分之间存在着很大的依赖性。引入 facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性 和可移植性。
    3、当你需要构建一个层次结构的子系统时,使用 facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过facade进行通讯,从而简化了它们之间的依赖关系。

参考

  1. CSDN http://blog.csdn.net/lovelion/article/details/8258121
  2. CSDN2 http://blog.csdn.net/hguisu/article/details/7533759
  3. 课本

---

本文章采用 知识共享署名2.5中国大陆许可协议 进行许可,欢迎转载,演绎或用于商业目的。

---

二维码加载中...

扫一扫移动端访问O(∩_∩)O

发表评论

10 + 20 =
路人甲 表情
看不清楚?点图切换 Ctrl+Enter快速提交
正在加载中……