外观模式
- 一、介绍
- 二、家庭影院项目案例使用
- 三、Java API或框架中应用分析
- 三、Spring框架ApplicationContext源码
一、介绍
外观模式(Facade Pattern)是一种结构型设计模式,它为子系统中的一组接口提供了一个统一的高层接口,使得子系统更加容易使用。外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
外观模式的主要作用有以下几点:
-
简化系统的调用复杂性。通过外观模式,客户端可以只需调用外观类中的方法就可以完成复杂的操作,无需深入了解子系统的内部工作机制。
-
减小系统的编译依赖。通过外观模式,客户端只需要与外观类发生编译依赖,而无须与子系统的其他模块发生直接依赖。
-
有利于体系结构的拓展。在有新的子系统加入时,只需创建一个新的外观类,客户端无须修改源代码,减少了客户端与子系统的耦合关系。
外观模式主要包含以下几种角色:
-
外观(Facade)角色:外观角色需要知道所有子系统的功能和职责,它是一个独立的模块,它与系统中的其他模块一起一起,构成了一个更大的系统。
-
子系统(Sub System)角色:子系统角色实现系统的部分功能,并可以和其他子系统协作以完成更复杂的功能。子系统角色不需要知道外观的存在。
-
客户(Client)角色:客户端通过外观模式访问子系统的功能。
下面是一个外观模式的简单示例:
// 子系统角色
class SubSystemA {public void operationA() {System.out.println("SubSystemA.operationA()");}
}class SubSystemB {public void operationB() {System.out.println("SubSystemB.operationB()");}
}class SubSystemC {public void operationC() {System.out.println("SubSystemC.operationC()");}
}// 外观角色
class Facade {private SubSystemA a = new SubSystemA();private SubSystemB b = new SubSystemB();private SubSystemC c = new SubSystemC();public void operation() {a.operationA();b.operationB();c.operationC();}
}// 客户端
public class Client {public static void main(String[] args) {Facade facade = new Facade();facade.operation();}
}
运行结果:
SubSystemA.operationA()
SubSystemB.operationB()
SubSystemC.operationC()
在这个示例中,Facade
类充当了外观角色,它封装了子系统SubSystemA
、SubSystemB
和SubSystemC
的功能,并提供了一个简单的operation()
方法供客户端调用。客户端只需要与外观Facade
对象交互,而不需要了解子系统的内部细节。
外观模式的优点包括:
- 减少了系统与客户端的耦合度,使系统更加容易移植和维护。
- 通过为复杂子系统提供一个统一的入口,降低了客户端的使用难度。
- 客户端代码更加简洁,系统更加易于理解和维护。
缺点包括:
- 不符合开闭原则,如果增加新的子系统功能,可能需要修改外观类源码。
- 外观类的编写比较困难,需要全面了解子系统的功能和职责。
二、家庭影院项目案例使用
需求:一个家庭影院系统,它包含了音响系统、投影仪系统和DVD播放器系统等子系统。我们需要提供一个统一的接口,让用户可以方便地控制整个家庭影院系统。
1. 定义子系统
首先,我们定义音响系统、投影仪系统和DVD播放器系统的接口和实现类:
// 音响系统
interface AudioSystem {void turnOn();void turnOff();void setVolume(int volume);
}class AudioSystemImpl implements AudioSystem {// 实现具体的音响系统操作
}// 投影仪系统
interface ProjectorSystem {void turnOn();void turnOff();void setInput(String input);
}class ProjectorSystemImpl implements ProjectorSystem {// 实现具体的投影仪系统操作
}// DVD播放器系统
interface DVDPlayer {void turnOn();void turnOff();void play(String movie);
}class DVDPlayerImpl implements DVDPlayer {// 实现具体的DVD播放器操作
}
2. 定义外观类
接下来,我们定义一个HomeTheaterFacade
类作为外观,它封装了音响系统、投影仪系统和DVD播放器系统的操作:
class HomeTheaterFacade {private AudioSystem audioSystem;private ProjectorSystem projectorSystem;private DVDPlayer dvdPlayer;public HomeTheaterFacade(AudioSystem audioSystem, ProjectorSystem projectorSystem, DVDPlayer dvdPlayer) {this.audioSystem = audioSystem;this.projectorSystem = projectorSystem;this.dvdPlayer = dvdPlayer;}public void watchMovie(String movie) {audioSystem.turnOn();projectorSystem.turnOn();projectorSystem.setInput("DVD");dvdPlayer.turnOn();dvdPlayer.play(movie);}public void endMovie() {audioSystem.turnOff();projectorSystem.turnOff();dvdPlayer.turnOff();}
}
在HomeTheaterFacade
类中,我们提供了watchMovie
和endMovie
两个方法,分别用于启动和关闭家庭影院系统。这些方法封装了对各个子系统的调用,简化了系统的使用复杂度。
3. 使用外观类
最后,在客户端代码中,我们可以直接使用HomeTheaterFacade
类来控制整个家庭影院系统:
public class Client {public static void main(String[] args) {AudioSystem audioSystem = new AudioSystemImpl();ProjectorSystem projectorSystem = new ProjectorSystemImpl();DVDPlayer dvdPlayer = new DVDPlayerImpl();HomeTheaterFacade homeTheater = new HomeTheaterFacade(audioSystem, projectorSystem, dvdPlayer);homeTheater.watchMovie("机器人总动员");// 观看电影...homeTheater.endMovie();}
}
在上面的代码中,我们创建了音响系统、投影仪系统和DVD播放器系统的实例,然后将它们传递给HomeTheaterFacade
构造函数。客户端只需要与HomeTheaterFacade
对象交互,通过调用watchMovie
和endMovie
方法即可控制整个家庭影院系统。
使用外观模式,我们将复杂的子系统操作封装在HomeTheaterFacade
类中,客户端无需了解各个子系统的内部细节,从而降低了系统的使用复杂度。同时,如果需要增加或修改子系统,只需要修改外观类,而无需更改客户端代码,提高了系统的可维护性和可扩展性。
三、Java API或框架中应用分析
- Java I/O库
Java I/O库中广泛使用了外观模式。例如java.io.File
类就是一个外观,它提供了对文件系统进行操作的简化方法,而无需直接面对复杂的操作系统底层API。
File file = new File("example.txt");
file.createNewFile(); // 创建新文件
file.delete(); // 删除文件
通过File
对象,我们可以方便地执行文件的创建、删除等操作,而不用关心底层的具体实现细节。
- Java 数据库连接(JDBC)
Java JDBC中的DriverManager
类充当了数据库连接的外观角色。它封装了获取数据库连接的复杂过程,为我们提供了一个简单的接口。
Connection conn = DriverManager.getConnection(url, username, password);
通过DriverManager.getConnection()
方法,我们可以获取到一个数据库连接对象,而不需要关注加载驱动、创建连接等繁琐步骤。
- Spring框架
Spring框架中的ApplicationContext
接口可以看作是一个外观。它为开发者提供了获取Spring Bean的统一入口,而隐藏了Bean的创建、配置、装配等复杂细节。
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyService service = (MyService) context.getBean("myService");
通过ApplicationContext
对象,我们可以方便地获取Spring管理的Bean实例,而无需了解Spring内部的工作机制。
- Java Servlet
在JavaEE的Servlet规范中,ServletRequest
和ServletResponse
接口可以看作是请求和响应对象的外观。它们为开发者提供了一系列方法来访问HTTP请求和响应的各种属性,而无需直接处理底层的HTTP协议细节。
protected void doGet(HttpServletRequest request, HttpServletResponse response) {String param = request.getParameter("name");response.setContentType("text/html");// ...
}
通过ServletRequest
和ServletResponse
对象,我们可以方便地获取请求参数、设置响应头等,而无需关注HTTP协议的具体实现细节。
三、Spring框架ApplicationContext源码
ApplicationContext
接口可以被视为一个外观(Facade)模式的典型应用。它为开发者提供了一个统一的入口来访问Spring容器中的Bean实例,而隐藏了Bean的创建、装配、初始化等复杂细节。分析一下ApplicationContext
接口的源码实现,以深入理解它是如何运用外观模式的。
ApplicationContext
接口继承自BeanFactory
接口,它定义了一些基本的方法,如getBean()
、containsBean()
等,用于获取和检查容器中的Bean。但是,ApplicationContext
接口还提供了一些额外的功能,如访问资源文件、发布事件等。这些功能由ApplicationContext
接口的不同实现类完成,如ClassPathXmlApplicationContext
、AnnotationConfigApplicationContext
等。
我们以ClassPathXmlApplicationContext
为例,看一下它的实现细节:
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {// ...@Overrideprotected Resource getResourceByPath(String path) {// 获取classpath资源return new ClassPathResource(path);}// ...
}public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {// ...@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// 加载Bean定义XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);beanDefinitionReader.loadBeanDefinitions(getConfigResources());}// ...
}
从上面的源码可以看出,ClassPathXmlApplicationContext
实现了getResourceByPath()
方法,用于从classpath中加载资源文件。而loadBeanDefinitions()
方法则负责从资源文件中加载Bean定义。这些复杂的实现细节都被封装在ApplicationContext
接口的具体实现类中,对外部客户端来说是透明的。
客户端只需要直接使用ApplicationContext
接口提供的方法即可,如:
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
MyService service = context.getBean("myService", MyService.class);
在上面的代码中,客户端仅需创建一个ApplicationContext
实例,并通过getBean()
方法获取所需的Bean实例,而不必关心Bean的创建、装配、初始化等复杂过程。
从这个角度来看,ApplicationContext
接口扮演了外观角色,它为客户端提供了一个统一的入口来访问Spring容器中的Bean,同时隐藏了Bean加载和管理的复杂细节。这种设计有效地降低了客户端代码与Spring容器实现之间的耦合度,提高了代码的可维护性和可扩展性。