程序总是越来越大。随着时间的推移,程序中的类会越来越多,而且它们之间相互关联。这会导致程序结构也变得越来越复杂。我们在使用这些类之前,必须弄清它们之间的关系,注意正确的调用顺序。
特别是在调用大型程序进行处理时,需要格外注意那些数量庞大的类之间错综复杂的关系。不过与其这么做,不如为这个大型程序准备一个“窗口”。这样,就不必单独地关注每个类了,只需简单地对“窗口”提出请求即可。
窗口模式可以为互相关联在一起的错综复杂的类整理出高层接口(API)。其中的Facade角色可以让系统对外只有一个简单的接口(API)。而且,Facade角色还会考虑到习题内部各个类之间的责任关系和依赖关系,按照正确的顺序调用各个类。
示例程序:
| 名字 | 说明 |
|---|---|
| Database | 从邮箱地址中获取用户名的类 |
| HtmlWriter | 编写HTML文件的类 |
| PageMaker | 根据邮政地址编写该用户的Web页面 |
| Main | 测试程序行为的类 |
Database类:
Database类可获取指定数据库名所对应的Properties的实例,用私有的构造器所以无法生成该类的任何实例,只能通过它的getPropertes静态方法获取Properties的实例。
public class Database {private Database() { // 防止外部new出Database的实例,所以声明为private}public static Properties getProperties(String dName){String fileName = dName + ".txt";System.out.println(fileName);Properties prop = new Properties();try {prop.load(new FileInputStream(fileName));} catch (IOException e) {System.out.printf("Warning"+fileName+"is not found");}return prop;}}
数据文件:
hyuki@yuki.com=Hiroshi Yukihanako@hyuki.com=Hanaako Satotomura@hyuki.com=Tomuramamoru@hyuki.com=Mamoru Takahashi
HtmlWriter类:
HtmlWriter类用于编写简单的Web页面。在生成HtmlWriter类的实例时赋予其Writer,然后使用该Writer输出HTML;
该类中的隐藏条件是必须要先调用title方法,窗口内PageMaker使用HtmlWriter类时必须严格遵守这个限制条件。
public class HtmlWriter {private Writer writer;public HtmlWriter(Writer writer){this.writer=writer;}public void title(String title) throws IOException { // 输出段落writer.write("<html>");writer.write("<head>");writer.write("<title>"+title+"</title>");writer.write("</head>");writer.write("<body>\n");writer.write("<h1>"+title+"</h1>\n");}public void paragraph(String msg) throws IOException { // 输出段落writer.write("<p>"+msg+"</p>\n");}public void link(String href,String caption) throws IOException { // 输出超链接paragraph("<a href=\""+href+"\">"+caption+"</a>");}public void mailto(String mailAddr,String userName) throws IOException { // 输出邮件地址link("mailto:"+mailAddr,userName);}public void close() throws IOException { // 结束输出HTMLwriter.write("</body>");writer.write("</html>\n");writer.close();}}
PageMaker类:
使用Database类和HtmlWriter类来生成指定用户的Web页面。该类中只有一个方法,该方法会根据指定的邮件地址和文件名生成相应的Web页面。
对外部,PageMaker只提供了这一接口,内部通过复杂的调用完成,但是并不需要开发人员和用户感知这一操作;
public class PageMaker {public PageMaker() {}public static void makeWelcomePage(String mailAddr,String fileName){try {Properties mailProp = Database.getProperties("/Users/wangquan/Downloads/Interview/src/Facade/maildata");String userName = mailProp.getProperty(mailAddr);HtmlWriter writer = new HtmlWriter(new FileWriter(fileName));writer.title("Welcome to"+userName+" s page!");writer.paragraph(userName+"欢迎来到"+userName+"的主页");writer.paragraph("等着你的邮件哦!");writer.mailto(mailAddr,userName);writer.close();System.out.printf(fileName+"is created for "+mailAddr+"("+userName+")");} catch (IOException e) {e.printStackTrace();}}}
Main类:
public class Main {public static void main(String[] args) {PageMaker.makeWelcomePage("hyuki@hyuki.com","welcome.html");}}
Facade模式中的登场角色:
Facade(窗口):
Facade角色是代表构成系统的许多其它角色的“简单窗口”。Facade角色向系统外部提供高层接口(API)。在示例程序中,PageMaker类扮演这个角色。
构成系统的许多其他角色:
这些角色各自完成自己的工作,它们并不知道Facade角色。Facade角色调用其他角色进行工作,但是其他角色不会调用Facade角色。
Client(请求者):
拓展思路的要点:
Facade角色到底做什么工作?
Facade模式可以让复杂的东西看起来简单。其实就是Facade模式可以让我们不必再议后台工作的的这些类之间的关系和它们的使用方法。这里的重点是接口变少了。程序中如果有很多类和方法,我们在决定到底应该使用哪个类或者是方法时就很容易迷茫。有时,类和方法的调用顺序也很容易弄错。
接口变少了还意味着程序与外部的关联关系弱化了,这样更容易使我们的包作为组件被复用。
在设计类的时候,我们还需要考虑将哪些方法的可见性设为public。如果公开的方法过多,会导致类的内部的修改变得困难。字段也是一样,如果不小心将某个字段公开出去了,那么其他类可能会读取或是修改这个字段,导致难以修改该类。
与设计类一样,在设计包时,需要考虑类的可见性,如果让外部看到了类,包内部代码的修改就会变得困难。
递归地使用Facade模式:
假设现在有几个持有Facade角色的类的集合。那么,可以通过整合这几个集合来引入新的Facade角色。也就是说,可以递归地使用Facade模式。
开发人员为什么不愿意创建开窗模式:
对于熟练的开发人员而言,系统中的所有信息全部都记忆在脑中,他们对于类之间的所有相互依赖关系都一清二楚。
但是对于那些能够明确地用语言描述出来的只是,我们不应该将它们隐藏在自己脑袋中,而是应该用代码将它们表现出来。
相关的设计模式:
Abstract Factory模式:
可以看做是生成复杂实例时的Facade模式。因为它提供了“要想生成这个功能只需要调用这个方法”的简单接口。
Singleton模式:
Mediator模式:
在Facade模式中,Facade单方面地使用其它角色来提高高层接口;
而在Mediator模式中,Mediator角色作为Colleague角色间的仲裁者负责调停,是双向的。
