设计模式
外观模式
在现实生活中,常常存在办事较复杂的例子;如去医院看病,需要挂号、门诊、划价、化验、收费、取药等,病人需要与多个部门打交道,这不是一件容易的事。又比如我们办房产证或者注册一个网站等都需要与多个部门联系。
软件设计也是这样,随着需求的扩展功能的增多,系统会原来越复杂,设计师处理复杂系统的一个常见方法便是将其“分而治之”,把一个系统划分为几个较小的子系统,随着子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的管理接口,从而降低系统的耦合度,这就是外观模式的目标。
定义与特点
外观模式(Facade)又叫做门面模式,是一种通过多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不必关注子系统的实现细节,这样就大大降低应用程序的复杂度,提高了程序的可维护性。外观模式是 “迪米特法则” 的典型应用模式。
在日常编码过程中,我们都在有意无意的大量使用外观模式。只要是高层模块需要调用多个子模块,我们都会自觉的创建一个类来封装这些子模块,提供精简的接口以供外部调用【如一个 service 中调用多个 mapper 实现某一功能】。
外观模式的优点:
- 松散耦合:外观模式松散了客户端与子系统的耦合关系,让子系统内部的模块更容易扩展和维护。
- 简单易用:外观模式让子系统更加易用,客户端不需要了解子系统的内部实现,也不需要跟众多的子系统交互,只需要跟它们的外观类交互。
- 更好的访问层次划分:通过合理的使用外观模式,可以帮助我们更好的划分访问的层次。有些方法是面对系统外的,有些方法是面对系统内部的。把需要暴露的功能集中到外观类中,这样既方便客户端的调用,也很好的隐藏了内部的实现细节。
缺点:
- 不能很好的限制客户端使用子系统类,很容易带来未知风险。
- 增加新的子系统可能需要修改外观类或者客户端,违背了 “开闭原则”。
模式的结构
外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能
外观模式主要包含以下角色:
- 外观角色(Facade):为多个子系统对外提供一个共同的接口。
- 子系统角色(Sub System):实现系统的部分功能,可以同时有一个或者多个子系统。每个子系统都可以被客户端直接调用,或者被外观角色调用。子系统并不知道外观角色的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。
- 客户角色(Client):通过一个外观角色访问各个子系统的功能。
其 UML 图如下:
模式的使用
这里我们以 “体检” 为例介绍外观模式的使用。本实例外观角色是 “体检中心” ,它拥有 7个子系统角色,它们分别是体检中的各个科室,分别负责体检流程中的某一项或几项。外观类 “体检中心” 提供一个对外的
healthExamination()
方法,由体检中心负责各个体检科室的调度来完成整个体检流程,而客户端不需要关心各个体检科室的实现细节。
/**
* 抽象接口 - 体检科室
*/
public interface BodyKeyDepartments {
/**
* 体检
*/
void healthExamination();
}
/**
* 子系统 - 化验科
*/
public class TestSection implements BodyKeyDepartments{
@Override
public void healthExamination() {
System.out.println("化验科进行常规的抽血化验,包括血常规,生化,尿,便等检查 ");
}
}
/**
* 子系统 - 眼科
*/
public class Ophthalmology implements BodyKeyDepartments{
@Override
public void healthExamination() {
System.out.println("眼科检查 视力");
}
}
/**
* 子系统 - 内科
*/
public class InternalMedicine implements BodyKeyDepartments{
@Override
public void healthExamination() {
System.out.println("内科检查 血压,血糖");
}
}
/**
* 子系统 - 外科
*/
public class Surgery implements BodyKeyDepartments{
@Override
public void healthExamination() {
System.out.println("外科检查 身高,体重");
}
}
/**
* 子系统 - 超声科
*/
public class Ultrasonography implements BodyKeyDepartments{
@Override
public void healthExamination() {
System.out.println("超声科进行腹部,乳腺,甲状腺等超声检查");
}
}
/**
* 子系统 - 放射科
*/
public class Radiology implements BodyKeyDepartments {
@Override
public void healthExamination() {
System.out.println("放射科检查 胸片,CT等");
}
}
/**
* 子系统 - 五官科
*/
public class EntDept implements BodyKeyDepartments {
@Override
public void healthExamination() {
System.out.println("五官科检查 耳鼻喉");
}
}
/**
* 外观角色 - 体检中心
*/
public class MedicalCenter {
/**
* 化验科
*/
private TestSection testSection = new TestSection();
/**
* 眼科
*/
private Ophthalmology ophthalmology = new Ophthalmology();
/**
* 内科
*/
private InternalMedicine internalMedicine = new InternalMedicine();
/**
* 外科
*/
private Surgery surgery = new Surgery();
/**
* 超声科
*/
private Ultrasonography ultrasonography = new Ultrasonography();
/**
* 放射科
*/
private Radiology radiology = new Radiology();
/**
* 五官科
*/
private EntDept entDept = new EntDept();
/**
* 体检
*/
public void healthExamination() {
System.out.println("----------体检开始----------");
entDept.healthExamination();
ophthalmology.healthExamination();
surgery.healthExamination();
internalMedicine.healthExamination();
testSection.healthExamination();
ultrasonography.healthExamination();
radiology.healthExamination();
System.out.println("==========体检结束==========");
}
}
/**
* 外观模式测试
*/
public class FacadeTest {
public static void main(String[] args) {
MedicalCenter medicalCenter = new MedicalCenter();
medicalCenter.healthExamination();
}
}
运行程序,结果如下:
模式的应用场景
外观模式通常适用于以下场景:
- 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
- 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
- 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。