设计模式--外观模式(十二)
外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。
知识点的梳理:
- "最少知识"原则:只和你的密友谈话;
- 当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观;
- 外观将客户从一个复杂的子系统中解耦;
- 实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行;
-
可以为一个子系统实现多个外观;
-
先来对比外观模式,适配器模式与装饰者模式
-
开始介绍前的惯例,来个栗子
-
需求:建立自己的家庭影院:包含DVD播放器,摄影机,自动屏幕,环绕立体声,爆米花机等等;
-
- 观看一部电影,我们需要执行如下步骤:
-
将以上步骤写成类或者方法:
-
-
其它问题:
- 看完电影后难道还要反向把所有步骤来一遍?
- 如果要听CD也这么麻烦?
- 如果要升级硬件系统,还要重新学习操作步骤?
-
-
引入外观模式
- 通过外观模式,实现一个提供合理接口的外观类,可以将一个复杂的子系统变得容易使用。
-
为家庭影院创建一个外观类HomeTheaterFacade的新类,对外暴露出几个简单的方法,例如watchMovie():
-
- 这个外观类将家庭影院的诸多组件视为一个子系统,通过调用这个子系统,来实现watchMovie()方法
-
-
现在,你的客户代码可以调用此家庭影院外观所提供的方法,而不必再调用这个子系统的方法。所以,想要看电影只要调用方法watchMovie()方法即可。
- 外观只是提供更直接的操作,并未将原来的子系统阻隔起来。如果需要子系统类的更高层功能,还是可以使用原来的子系统;
-
一些问题
-
外观封装了子系统的类,如果需要低层功能的客户如何接触这些类?
- 外观只是提供了子系统的简化接口,并没有封装子系统。子系统的功能依然暴露在外;
-
外观是否可以增加新的功能,或者它只是将没一个请求转由子系统执行?
- 完全可以;
-
每个子系统只能有一个外观吗?
- 可以为一个系统创建许多个外观;
-
除了提供一个比较简单的接口,外观模式还有其它的优点吗?
- 外观模式也允许你将客户实现从任何子系统中解耦;
- 比如,你升级家庭影院,采用全新的接口。如果当初的代码是针对外观而不是针对子系统编写的,现在就不需要改变客户代码,只需要修改外观代码就可以了;
-
适配器模式和外观模式的差异在于:前者包装一个类,后者代表许多类?
- 不对。适配器将一个或多个类接口变成客户端所期望的一个接口;
- 一个外观也可以只针对一个拥有复杂接口的类提供简化的接口;
- 两者的差异不在于它们包装了几个类,而在于它们的意图;适配器的意图是,"改变"接口符合客户的期望;而外观的意图是,提供子系统的一个简化接口;
-
- 构造家庭影院
-
package hey.adapter;
public class HomeTheaterFacade { //需要的子系统组件全部在这里 Amplifier amp; Tuner tuner; DvdPlayer dvd; CdPlayer cd; Projector projector; TheaterLights lights; Screen screen; PopcornPopper popper; //外观将子系统中每一个组件的引用都传入它的构造函数中。然后外观把它们赋值给相应的实例变量 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.setSurroundSound(); amp.setVolume(5); dvd.on(); dvd.play(movie); } //该方法负责关闭一切。每项任务也都是委托子系统中合适的组件处理的 public void endMovie(){ System.out.println("Shutting movie theater down。。。"); popper.off(); lights.off(); screen.up(); projector.off(); amp.off(); dvd.stop(); dvd.eject(); dvd.off(); } } |
- 一众其它类~~~其它~~~
public class TheaterLights { public void dim(int i) { System.out.println("Theater Ceiling Lights dimming to " + i + "%"); } public void off() { System.out.println("Theater Ceiling Lights on"); } } |
public class PopcornPopper { public void on() { System.out.println("Popcorn Poper on"); } public void pop() { System.out.println("Popcorn Popper popping popcorn!"); } public void off() { System.out.println("Popcorn Poper off"); } } |
public class Screen { public void down() { System.out.println("Theater Screen going down"); } public void up() { System.out.println("Theater Screen going up"); } } |
public class Projector { public void wideScreenMode() { System.out.println("Top-O-Line Projector in widescreen mode (16*9 aspect ratio)"); } public void on() { System.out.println("Top-O-Line Projector on"); } public void off() { System.out.println("Top-O-Line Projector off"); } } |
public class Amplifier { public void on() { System.out.println("Top-O-Line Amplifier on"); } public void setDvd(DvdPlayer dvd) { System.out.println("Top-O-Line Amplifier setting DVD player to Top-O-Line DVD Player"); } public void setSurroundSound() { System.out.println("Top-O-Line Amplifier surrond sound on (5 speakers,1 subwoofer)"); } public void setVolume(int i) { System.out.println("Top-O-Line Amplifier setting volume to " + i); } public void off() { System.out.println("Top-O-Line Amplifier off"); } } |
public class DvdPlayer { private String movie; public void on() { System.out.println("Top-O-Line DVD Player on"); } public void play(String movie) { this.movie = movie; System.out.println("Top-O-Line DVD Player playing \"" + movie + "\""); } public void stop() { System.out.println("Top-O-Line DVD Player stopped \"" + movie + "\""); } public void eject() { System.out.println("Top-O-Line DVD Player eject"); } public void off() { System.out.println("Top-O-Line DVD Player off"); }
} |
- 测试
public class HomeTheaterTestDrive { public static void main(String[] args) { //实例化组件。正常的情况下,某个外观会被指派给客户使用,而不需要由客户自行创建外观 Amplifier amp = new Amplifier(); Tuner tuner = new Tuner(); DvdPlayer dvd = new DvdPlayer(); CdPlayer cd = new CdPlayer(); Projector projector = new Projector(); TheaterLights lights = new TheaterLights(); Screen screen = new Screen(); PopcornPopper popper = new PopcornPopper(); //根据子系统所有的组件来实例化外观 HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp,tuner,dvd,cd,projector,screen,lights,popper); //使用简化的接口,先开启电影,然后关闭电影 homeTheater.watchMovie("Raiders of the lost Ark"); homeTheater.endMovie(); } } |
-
结果:
-
-
定义外观模式
- 外观模式的意图是要提供一个简单的接口,好让一个子系统更易于使用;
-
类图:
-
-
"最少知识"原则
-
该原则告诉我们,要减少对象之间的交互,只留下几个"密友";
- 设计一个系统,不管是任何对象,都要注意它所交互的类有哪些,并注意它和这些类是如何交互的;
- 该原则期望我们在设计中,不要让太多的类耦合在一起,以免在修改系统时,影响其它部分;
-
如何避免这种情况?该原则提出了如下方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
- 该对象本身;
- 被当作方法的参数而传递进来的对象;
-
此方法所创建或实例化的任何对象;
- 对以上三句话的总结就是:如果某对象是调用其它的方法的返回结果,不要调用该对象的方法!
- 对象的任何组件;(把"组件"想象成是被实例变量所引用的任何对象。换句话说,把该对象想成是"有一个"关系)
-
为什么该原则会如此苛刻?
-
如果调用从另一个调用中返回的对象的方法,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求。这样的话,我们就不需要认识该对象的组件了,比如:
-
-
-
将方法限定在界限内
- 下面的汽车类,展示了调用的方法,同时遵守了最少知识原则:
-
public class Car { Engine engine;//这是一个类的组件,我们可以调用它的方法 public Car(){} public void start(Key key){ Doors doors = new Doors();//创建一个新对象,需要调用它的方法 boolean authorized = key.turns();//被当作参数传递进来的对象,其方法可以被调用 if(authorized){ engine.start();//可以调用对象组件的方法 updateDashboardDisplay();//可以调用同一个对象内的本地方法 doors.lock();//可以调用你所创建或实例化的对象的方法 } } private void updateDashboardDisplay() { //更新显示 } } |
-
最少知识原则的缺点
- 采用这个原则会导致更多的"包装"类被制造出来,以处理和其他组件的沟通,这可能导致复杂度和开发时间的增加,并降低运行时的性能;
-
外观和最少知识原则
-