外观(Facade)模式

文章目录

  • 外观(Facade)模式
    • 1. 意图
    • 2. 别名
    • 3. 动机
    • 4. 适用性
    • 5. 结构
    • 6. 参与者
    • 7. 协作
    • 8. 效果
    • 9. 实现
    • 10. 代码示例
    • 11. 已知应用
    • 12. 相关模式
    • 13. 设计原则口袋
    • 14. 参考文献

外观(Facade)模式

隶属类别——对象结构型模式


1. 意图

提供一个统一的接口,用来访问子系统中的一群接口,外观模式定义了一个高层接口,让子系统更容易使用。

2. 别名

NO

3. 动机

将一个系统划分成为若干个子系统有利于降低系统的复杂性。一个常见的设计目标是使子系统间的通信和互相依赖关系达到最小。达到这个目标的途径之一就是引入一个外观(facade)对象,它为子系统中较一般的设施提供了一个单一而简单的界面。

传统没有使用外观模式的系统:

外观(Facade)模式_第1张图片

使用外观模式之后:
外观(Facade)模式_第2张图片

例如有一个编程环境,它允许应用程序访问它的编译子系统。这个编译子系统包含了若干个类,如Scanner,Parser,ProgramNode,BytecodeStream和ProgramNodeBuilder,用于实现这一编译器。有些特殊应用程序需要直接访问这些类,但是大多数编译器的用户并不关心语法分析和代码生成这样的细节,他们只是希望编译一段代码。对这些用户,编译子系统中那些功能强大但层次较低的接口只会使他们的任务复杂化。

为提供一个高层的接口并且对客户屏蔽这些类。编译子系统还包括一个Complier类(facede),这个类定义了一个编译器功能的统一接口。Compiler类是一个外观,它给用户提供了一个单一而简单的编译子系统接口。它无需隐藏实现编译功能的那些类,即可将它们结合在一起。编译器的外观可方便大多数程序员使用,同时对少数懂得如何使用底层功能的人,它并不隐藏这些功能,如下图所示。

外观(Facade)模式_第3张图片

4. 适用性

在遇到以下情况使用Facade模式:

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

5. 结构

外观(Facade)模式_第4张图片

6. 参与者

  • Facade(Compiler)

    —— 知道哪些子系统类负责处理请求

    —— 将客户的请求代理给适当的子系统对象。

  • Subsystem classes(Scanner、 Parser、 ProgramNode等)

    —— 实现子系统的功能

    —— 处理有Facade对象指派的任务。

    —— 没有Facade的任何信息,底层不知道高层信息。

7. 协作

  • 客户程序通过发送请求给Facade的方式与子系统通讯, Facade将这些消息转发给适当的子系统对象。尽管是子系统中的有关对象在做实际工作,但Facade模式本身也必须将它的接口转化成子系统的接口。

8. 效果

Facade模式有下面一些优点:

  • 1**.它对客户屏蔽子系统组件**因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。

  • 2.它实现了子系统与客户之间的松耦合关系,子系统内部的功能组件往往是紧耦合的 松耦合关系使得子系统的组件变化不会影响到它的客户(看情况吧,耦合的那部分变化还是会影响的)。Facade模式有助于建立层次结构系统,也有助于对对象之间的依赖关系分层。Facade模式可以消除复杂的循环依赖关系。这一点在客户程序与子系统是分别实现的时候尤为重要

    在大型软件系统中降低编译依赖性至关重要。在子系统类改变时,希望尽量减少重编译工作以节省时间。用Facade可以降低编译依赖性。限制重要系统中较小的变化所需的重编译工作。Facade模式同样也有利于简化系统在不同平台之间的移植过程(平台移植时,可能会需要修改某个子系统中的某个类的某个方法,而不需要改变多个子系统之间Facade之间的操作。),因为编译一个子系统一般不许需要编译所有其他的子系统。

  • 3**.如果应用需要,它并不限制他们使用子系统类** 你可以让系统不完全屏蔽对子系统中功能,让想简单实用的人直接使用Facade接口,而让了解并需要子系统功能的人,可以去使用子系统中底层的功能,因此你可以在系统的易用性和通用性之间加以选择。

9. 实现

使用Facade模式时需要注意以下几点:

  • 1.降低客户-子系统之间的耦合度 用抽象类实现Facade而它的具体子类对应于不同的子系统实现,这可以进一步降低客户与子系统的耦合度。这样,客户就可以通过抽象的Facade类与子系统(的Facade)通讯。这种抽象耦合关系使得客户不知道它使用的是子系统中的哪一个实现。>

    除了生成子类的方法以外,另外一种方法是使用不同的子系统对象(子系统的Facade)对象配置Facade对象。为定制Facade,仅需要对它的子系统facade对象(一个或者多个)进行替换(应该可以用策略(Strategy)模式)。

  • 2.公共子系统类和私有子系统类 一个子系统与一个类的相似之处是,它们都有接口并且它们都封装了一些东西——类封装了状态和操作,而子系统封装了一些类。从一个类的公共和私有接口是有益的,可以控制访问,我们联想到子系统的公共和私有接口。

    子系统的公共接口包括所有客户程序可以访问的类,私有接口仅用于对子系统进行扩充。当然,Facade类是公共接口的一部分,但它不是唯一部分,子系统的其他部分也可能是公共的,例如,编译子系统中的Parser类和Scanner类是公共接口的一部分。

    私有化子系统类确实有用,如今很多面对对象语言可以实现(起码Java和C++)可以实现,比如C++的命名空间(个人觉得和Java中对应的包类似),Java中的包,都可以仅暴露公共子系统类。从而达到部分子系统部分私有,部分公共。

10. 代码示例

Subsystem classes—— Amplifier & CdPlayer & DvdPlayer & PopcornPopper & Projector & Screen & TheaterLights & Tuner

Amplifier.java

public class Amplifier {
     
	String description;
	Tuner tuner;
	DvdPlayer dvd;
	CdPlayer cd;
	
	public Amplifier(String description) {
     
		this.description = description;
	}
 
	public void on() {
     
		System.out.println(description + " on");
	}
 
	public void off() {
     
		System.out.println(description + " off");
	}
 
	public void setStereoSound() {
     
		System.out.println(description + " stereo mode on");
	}
 
	public void setSurroundSound() {
     
		System.out.println(description + " surround sound on (5 speakers, 1 subwoofer)");
	}
 
	public void setVolume(int level) {
     
		System.out.println(description + " setting volume to " + level);
	}

	public void setTuner(Tuner tuner) {
     
		System.out.println(description + " setting tuner to " + dvd);
		this.tuner = tuner;
	}
  
	public void setDvd(DvdPlayer dvd) {
     
		System.out.println(description + " setting DVD player to " + dvd);
		this.dvd = dvd;
	}
 
	public void setCd(CdPlayer cd) {
     
		System.out.println(description + " setting CD player to " + cd);
		this.cd = cd;
	}
 
	@Override
	public String toString() {
     
		return description;
	}
}

Cdplayer.java

public class CdPlayer {
     
	String description;
	int currentTrack;
	Amplifier amplifier;
	String title;
	
	public CdPlayer(String description, Amplifier amplifier) {
     
		this.description = description;
		this.amplifier = amplifier;
	}
 
	public void on() {
     
		System.out.println(description + " on");
	}
 
	public void off() {
     
		System.out.println(description + " off");
	}

	public void eject() {
     
		title = null;
		System.out.println(description + " eject");
	}
 
	public void play(String title) {
     
		this.title = title;
		currentTrack = 0;
		System.out.println(description + " playing \"" + title + "\"");
	}

	public void play(int track) {
     
		if (title == null) {
     
			System.out.println(description + " can't play track " + currentTrack + 
					", no cd inserted");
		} else {
     
			currentTrack = track;
			System.out.println(description + " playing track " + currentTrack);
		}
	}

	public void stop() {
     
		currentTrack = 0;
		System.out.println(description + " stopped");
	}
 
	public void pause() {
     
		System.out.println(description + " paused \"" + title + "\"");
	}
 
	@Override
	public String toString() {
     
		return description;
	}
}

DvDplayer.java

public class DvdPlayer {
     
	String description;
	int currentTrack;
	Amplifier amplifier;
	String movie;
	
	public DvdPlayer(String description, Amplifier amplifier) {
     
		this.description = description;
		this.amplifier = amplifier;
	}
 
	public void on() {
     
		System.out.println(description + " on");
	}
 
	public void off() {
     
		System.out.println(description + " off");
	}

        public void eject() {
     
		movie = null;
                System.out.println(description + " eject");
        }
 
	public void play(String movie) {
     
		this.movie = movie;
		currentTrack = 0;
		System.out.println(description + " playing \"" + movie + "\"");
	}

	public void play(int track) {
     
		if (movie == null) {
     
			System.out.println(description + " can't play track " + track + " no dvd inserted");
		} else {
     
			currentTrack = track;
			System.out.println(description + " playing track " + currentTrack + " of \"" + movie + "\"");
		}
	}

	public void stop() {
     
		currentTrack = 0;
		System.out.println(description + " stopped \"" + movie + "\"");
	}
 
	public void pause() {
     
		System.out.println(description + " paused \"" + movie + "\"");
	}

	public void setTwoChannelAudio() {
     
		System.out.println(description + " set two channel audio");
	}
 
	public void setSurroundAudio() {
     
		System.out.println(description + " set surround audio");
	}
 
	@Override
	public String toString() {
     
		return description;
	}
}

PopcornPopper.java

public class PopcornPopper {
     
	String description;
	
	public PopcornPopper(String description) {
     
		this.description = description;
	}
 
	public void on() {
     
		System.out.println(description + " on");
	}
 
	public void off() {
     
		System.out.println(description + " off");
	}

	public void pop() {
     
		System.out.println(description + " popping popcorn!");
	}
 
	@Override
    public String toString() {
     
    	return description;
    }
}

Projector.java

public class Projector {
     
	String description;
	DvdPlayer dvdPlayer;
	
	public Projector(String description, DvdPlayer dvdPlayer) {
     
		this.description = description;
		this.dvdPlayer = dvdPlayer;
	}
 
	public void on() {
     
		System.out.println(description + " on");
	}
 
	public void off() {
     
		System.out.println(description + " off");
	}

	public void wideScreenMode() {
     
		System.out.println(description + " in widescreen mode (16x9 aspect ratio)");
	}

	public void tvMode() {
     
		System.out.println(description + " in tv mode (4x3 aspect ratio)");
	}
  
	@Override
	public String toString() {
     
		return description;
	}
}

Screen.java

public class Screen {
     
	String description;

	public Screen(String description) {
     
		this.description = description;
	}

	public void up() {
     
		System.out.println(description + " going up");
	}

	public void down() {
     
		System.out.println(description + " going down");
	}

	@Override
	public String toString() {
     
		return description;
	}
}

TheaterLights.java

public class TheaterLights {
     
	String description;

	public TheaterLights(String description) {
     
		this.description = description;
	}

	public void on() {
     
		System.out.println(description + " on");
	}

	public void off() {
     
		System.out.println(description + " off");
	}

	public void dim(int level) {
     
		System.out.println(description + " dimming to " + level  + "%");
	}

	@Override
	public String toString() {
     
		return description;
	}
}

Tuner.java

public class Tuner {
     
	String description;
	Amplifier amplifier;
	double frequency;

	public Tuner(String description, Amplifier amplifier) {
     
		this.description = description;
	}

	public void on() {
     
		System.out.println(description + " on");
	}

	public void off() {
     
		System.out.println(description + " off");
	}

	public void setFrequency(double frequency) {
     
		System.out.println(description + " setting frequency to " + frequency);
		this.frequency = frequency;
	}

	public void setAm() {
     
		System.out.println(description + " setting AM mode");
	}

	public void setFm() {
     
		System.out.println(description + " setting FM mode");
	}

	@Override
	public String toString() {
     
		return description;
	}
}

下面是Facade——HomeTheaterFacada.java 这里构造应该是Builder模式进行构造

public class HomeTheaterFacade {
     
	private Amplifier amp;
	private Tuner tuner;
	private DvdPlayer dvd;
	private CdPlayer cd;
	private Projector projector;
	private TheaterLights lights;
	private Screen screen;
	private PopcornPopper popper;
	// can use builder pattern here
	public HomeTheaterFacade(Amplifier amp, 
		       Tuner tuner, 
		       DvdPlayer dvd, 
		       CdPlayer cd, 
		       Projector projector, 
		       Screen screen, 
		       TheaterLights lights, 
		       PopcornPopper popper) {
     
		this.amp = amp;
		this.tuner = tuner;
		this.dvd = dvd;
		this.cd = cd;
		this.projector = projector;
		this.screen = screen;
		this.lights = lights;
		this.popper = popper;
	}
	
	public void watchMovie(String movie) {
     
		System.out.println("Get ready to watch a movie...");
		popper.on();
		popper.pop();
		lights.dim(10);
		screen.down();
		projector.on();
		projector.wideScreenMode();
		amp.on();
		amp.setDvd(dvd);
		amp.setVolume(5);
		dvd.on();
		dvd.play(movie);
	}
	
	public void endMovie() {
     
		System.out.println("Shutting movie theater down...");
		popper.off();
		lights.on();
		screen.up();
		projector.off();
		amp.off();
		dvd.stop();
		dvd.eject();
		dvd.off();
	}	
}


这里我用采用的是构造方法进行初始化引用(代码时当初写的),我现在觉得很有问题,其实更好的方法是在Facade内部实例化好,而不需要在Client再重新实例化,让Client与子系统中的类进行强制耦合,让Client必须知道底层子系统才能实现其实是很有问题的,让Client直接调用Facade,使用其方法,这样才真正达到简化接口的目标。也更符合Facade模式的思想

最后是Client也是测试类——HomeTheaterTestDrive.java

public class HomeTheaterTestDrive {
     
	public static void main(String[] args) {
     
		Amplifier amp = new Amplifier("Top-O-Line Amplifier");
		Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
		DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
		CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
		Projector projector = new Projector("Top-O-Line Projector", dvd);
		TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
		Screen screen = new Screen("Theater Screen");
		PopcornPopper popper = new PopcornPopper("Popcorn Popper");
		
		
		HomeTheaterFacade homeTheater = 
				new HomeTheaterFacade(amp, tuner, dvd, cd,
						projector, screen, lights, popper);
		homeTheater.watchMovie("Always");
		System.out.println();
		homeTheater.endMovie();
	}

}

以及对应的测试结果:

Get ready to watch a movie...
Popcorn Popper on
Popcorn Popper popping popcorn!
Theater Ceiling Lights dimming to 10%
Theater Screen going down
Top-O-Line Projector on
Top-O-Line Projector in widescreen mode (16x9 aspect ratio)
Top-O-Line Amplifier on
Top-O-Line Amplifier setting DVD player to Top-O-Line DVD Player
Top-O-Line Amplifier setting volume to 5
Top-O-Line DVD Player on
Top-O-Line DVD Player playing "Always"

Shutting movie theater down...
Popcorn Popper off
Theater Ceiling Lights on
Theater Screen going up
Top-O-Line Projector off
Top-O-Line Amplifier off
Top-O-Line DVD Player stopped "Always"
Top-O-Line DVD Player eject
Top-O-Line DVD Player off

因为上述的Facade和Client个人觉得很不妥当,所以这里重写,把子系统的初始化让Facade完成,让Client不必去了解底层子系统。

Facade——HomeTheaterBetterFacade.java

public class HomeTheaterBetterFacade {
     

	private Amplifier amp = new Amplifier("Top-O-Line Amplifier");
	private Tuner tuner = new Tuner("Top-O-Line AM/FM Tuner", amp);
	private DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD Player", amp);
	private CdPlayer cd = new CdPlayer("Top-O-Line CD Player", amp);
	private Projector projector = new Projector("Top-O-Line Projector", dvd);
	private TheaterLights lights = new TheaterLights("Theater Ceiling Lights");
	private Screen screen = new Screen("Theater Screen");
	private PopcornPopper popper = new PopcornPopper("Popcorn Popper");
	
	public void watchMovie(String movie) {
     
		System.out.println("Get ready to watch a movie...");
		popper.on();
		popper.pop();
		lights.dim(10);
		screen.down();
		projector.on();
		projector.wideScreenMode();
		amp.on();
		amp.setDvd(dvd);
		amp.setVolume(5);
		dvd.on();
		dvd.play(movie);
	}
	
	public void endMovie() {
     
		System.out.println("Shutting movie theater down...");
		popper.off();
		lights.on();
		screen.up();
		projector.off();
		amp.off();
		dvd.stop();
		dvd.eject();
		dvd.off();
	}	
}

Client也是测试类——HomeTheaterBetterTestDrive.java

public class HomeTheaterBetterTestDrive {
     

	public static void main(String[] args) {
     
		// TODO Auto-generated method stub		
		HomeTheaterBetterFacade betterFacade = new HomeTheaterBetterFacade();
		betterFacade.watchMovie("Always");
		
		System.out.println();
		
		betterFacade.endMovie();
	}

}

相应的结果(结果完全是一样的,但是对于Client和底层子系统直接的解耦完全不一样)

Get ready to watch a movie...
Popcorn Popper on
Popcorn Popper popping popcorn!
Theater Ceiling Lights dimming to 10%
Theater Screen going down
Top-O-Line Projector on
Top-O-Line Projector in widescreen mode (16x9 aspect ratio)
Top-O-Line Amplifier on
Top-O-Line Amplifier setting DVD player to Top-O-Line DVD Player
Top-O-Line Amplifier setting volume to 5
Top-O-Line DVD Player on
Top-O-Line DVD Player playing "Always"

Shutting movie theater down...
Popcorn Popper off
Theater Ceiling Lights on
Theater Screen going up
Top-O-Line Projector off
Top-O-Line Amplifier off
Top-O-Line DVD Player stopped "Always"
Top-O-Line DVD Player eject
Top-O-Line DVD Player off

最后附上对应HomeTheater的类图的UML图(使用的是我认为后面更好的Facade和Client即HomeTheaterBetterFacade.java和HomeTheaterBetterTestDrive.java):

外观(Facade)模式_第5张图片

11. 已知应用

在ET++应用框架[WGM88]中,应用程序可以用一个内置的浏览工具,用于在运行时刻监视它的对象。这些浏览工具在一个独立的子系统中实现。这一子系统包含一个称为Program-mingEnvironment的Facade类,这个facade定义了一些操作(如InspectObject和InspectClass等)用于访问这些浏览器。

Choices操作系统使用facade模式将多个框架组合在一起。Choices中的关键抽象是进程(process),存储(storage)和地址空间(address space).每个抽象有一个相应的子系统,用框架实现,支持Choices系统在不同硬件平台之间移植。其中两个子系统有"代表"(facade),这两个代表分别是存储(FileSystemInterface)和地址空间(Domain).

外观(Facade)模式_第6张图片

例如,虚拟内存康佳将Domain作为其facade,一个Domain代表一个地址空间。它提供了虚存地址到内存对象、文件系统或后背存储设备(backing store)的偏移量之间的一个映射。Domain支持在一个特定地址增加内存对象、删除内存对象以及处理页面错误。

正如上图所示,虚拟存储子系统内部有以下一些组件:

  • MemoryObject表示数据存储
  • MemoryObjectCache将MemoryObject数据缓存在物理存储器中。MemoryObjectCache 实际上是一个Strategy模式,由它定位缓存策略。
  • AddressTranslation封装了地址翻译硬件

当发生缺页中断时,调用RepairFault操作,Domain在引起缺页终端的地址处找到内存对象并将RepairFault操作代理给这个内存对象相关的魂村。可以改变Domain的组件对Domain进行定制。

12. 相关模式

  • Abstract Factory(抽象工厂)模式: 抽象工厂模式可以与Facade模式一起使用以提供一个接口,这一接口可以用来以一种子系统独立的方式创建子系统对象。Abstract Facatory 也可以代替Facade模式隐藏那些与平台相关的类。(有点理解了)
  • Mediator(中介者)模式: Mediator模式与Facade模式的相似之处是,它抽象了一些已有的类的功能,然而,Mediator的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能,Mediator的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信,相对而言,Facade仅对子系统对象的接口(方法访问入口)进行抽象,从而使它们更容易使用,它并不定义新的功能,而且子系统也不知道facade的存在
  • Singleton(单例)模式: 仅需一个Facade对象,因此Facade通常属于Singleton模式。

13. 设计原则口袋

  • 封装变化
  • 针对接口编程,不要针对实现编程
  • 多用组合,少用继承
  • 为交互对象之间的松耦合设计而努力
  • 类应该对扩展开放,对修改关闭
  • 依赖抽象,不要依赖具体类
  • 最少知识原则,只和你的密友谈话.

14. 参考文献

《HeadFirst设计模式》

《设计模式:可复用面向对象软件的基础》

你可能感兴趣的:(设计模式,Java,设计模式,外观模式)