2、8外观/门面模式
定义
1)为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
2)要求一个子系统的外部与其内部的通信必须通过一个统一的对象进行。门面模式提供一个高层次的接口,使得子系统更易于使用。
3)完美的体现了依赖倒置原则和迪米特法则的思想。
通用类图
门面模式注重”统一的对象”,也就是提供一个访问子系统的接口,除了这个接口不允许有任何访问子系统的行为发生。门面模式是外界访问子系统内部的唯一通道。
Facade门面角色
客户端可以调用这个角色的方法。此角色知晓子系统的所有功能和责任。一般情况下,本角色会从客户端发来的请求委派/代理到相应的子系统中去,也就说该角色没有实际的业务逻辑,只是一个委托类。
Subsystem classes子系统角色
可以同时有一个或者多个子系统。每一个子系统都不是一个单独的类,而是一个类的集合。子系统并不知道门面的存在。对于子系统而言,门面仅仅是另外一个客户端而已。
通用案例
案例
public class Client1 {
}
class ClassA{
public void doSomething(){
}
}
class ClassB{
public void doSomething(){
}
}
class ClassC{
public void doSomething(){
}
}
class Facade{
//被委托的对象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
ClassC classC = new ClassC();
//提供给外部访问的方法
public void methodA(){
this.classA.doSomething();
}
public void methodB(){
this.classB.doSomething();
}
public void methodC(){
this.classC.doSomething();
}
}
一个子系统可以有多个门面
/**
* 一个子系统有多个门面的情况:
* 1)门面已经庞大到不能忍受的程度;
* 2)子系统可以提供不同访问路径(也可将此子系统分成几个层次).
* 增加的门面非常简单,委托给了已经存在的门面对象Facade进行处理,
* 为什么使用委托而不再编写一个委托到子系统的方法呢?
* 那是因为在面向对象的编程中,尽量保持相同的代码只写一遍.
*/
public class Client2 {
}
class ClassA{
public void doSomething(){
}
}
class ClassB{
public void doSomething(){
}
}
class ClassC{
public void doSomething(){
}
}
class Facade{
//被委托的对象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
ClassC classC = new ClassC();
//提供给外部访问的方法
public void methodA(){
this.classA.doSomething();
}
public void methodB(){
this.classB.doSomething();
}
public void methodC(){
this.classC.doSomething();
}
}
class Facade2{
//引用原有的门面
Facade facade = new Facade();
//提供给外部访问的方法
public void methodB(){
this.facade.methodB();
}
}
门面不参与子系统内的业务逻辑
优化前
/**
* 下面案例中在methodC()中加入了this.classA.doSomething()方法;
* 这样的设计不靠谱,因为现在已经让门面对象参与了业务逻辑,门面对象只是提供一个访问子系统的一个路径而已,
* 它不应该也不能参与具体的业务逻辑,否则就会产生一个倒依赖的问题:
* 子系统必须依赖门面才能被访问,这是设计上一个严重错误,不仅违反了单一职责原则,同时也破坏了系统的封装性.
* 看下一个案例Client4的解决方案.
*/
public class Client3 {
}
class ClassA{
public void doSomething(){
}
}
class ClassB{
public void doSomething(){
}
}
class ClassC{
public void doSomething(){
}
}
class Facade{
//被委托的对象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
ClassC classC = new ClassC();
//提供给外部访问的方法
public void methodA(){
this.classA.doSomething();
}
public void methodB(){
this.classB.doSomething();
}
public void methodC(){
this.classA.doSomething();
this.classC.doSomething();
}
}
优化后
/**
* 建立一个封装类Context,封装完毕后提供给门面对象.
* 该封装类的作用就是产生一个业务规则complexMethod(),并且它的生存环境是在子系统内,
* 仅仅依赖两个相关的对象,门面对象通过对它的访问完成一个复杂的业务逻辑.
* 通过这样一次封装后,门面对象就不参与业务逻辑了.
*/
public class Client4 {
}
class ClassA{
public void doSomething(){
}
}
class ClassB{
public void doSomething(){
}
}
class ClassC{
public void doSomething(){
}
}
class Facade{
//被委托的对象
ClassA classA = new ClassA();
ClassB classB = new ClassB();
Context context = new Context();
//提供给外部访问的方法
public void methodA(){
this.classA.doSomething();
}
public void methodB(){
this.classB.doSomething();
}
public void methodC(){
this.context.complexMethod();
}
}
class Context{
//委托处理
ClassA classA = new ClassA();
ClassC classC = new ClassC();
//复杂的计算
public void complexMethod(){
this.classA.doSomething();
this.classC.doSomething();
}
}
业务(寄信)案例(依次优化)
案例1
图形结构
代码示例
public class Client5 {
public static void main(String[] args) {
ILetterProcess letterProcess = new LetterProcessImpl();
letterProcess.writeContext(null);
letterProcess.fillEnvelope(null);
letterProcess.letterInotoEnvelope();
letterProcess.sendLetter();
}
}
/**
* 写信过程接口
*/
interface ILetterProcess{
//首先要写信的内容
void writeContext(String context);
//其次写信封
void fillEnvelope(String address);
//然后把信访到信封里
void letterInotoEnvelope();
//最后邮寄
void sendLetter();
}
/**
* 写信过程的实现
*/
class LetterProcessImpl implements ILetterProcess{
@Override
public void writeContext(String context) {
System.out.println("首先要写信的内容");
}
@Override
public void fillEnvelope(String address) {
System.out.println("其次写信封");
}
@Override
public void letterInotoEnvelope() {
System.out.println("然后把信放到信封里");
}
@Override
public void sendLetter() {
System.out.println("最后邮寄");
}
}
案例2
图形结构
代码示例
public class Client6 {
public static void main(String[] args) {
ModenPostOffice modenPostOffice = new ModenPostOffice();
modenPostOffice.sendLetter(null,null);
}
}
/**
* 扩展后的现代化邮局
*/
class ModenPostOffice{
private ILetterProcess letterProcess = new LetterProcessImpl();
private Police police = new Police();
/**
* 封装一体化
* (下面参数可以再次封装)
* @param context
* @param address
*/
public void sendLetter(String context,String address){
letterProcess.writeContext(context);
letterProcess.fillEnvelope(address);
police.checkLetter(letterProcess);
letterProcess.letterInotoEnvelope();
letterProcess.sendLetter();
}
}
/**
* 信件检查类
*/
class Police{
public void checkLetter(ILetterProcess letterProcess){
System.out.println("信件检查完成");
}
}
/**
* 写信过程接口
*/
interface ILetterProcess{
//首先要写信的内容
void writeContext(String context);
//其次写信封
void fillEnvelope(String address);
//然后把信访到信封里
void letterInotoEnvelope();
//最后邮寄
void sendLetter();
}
/**
* 写信过程的实现
*/
class LetterProcessImpl implements ILetterProcess{
@Override
public void writeContext(String context) {
System.out.println("首先要写信的内容");
}
@Override
public void fillEnvelope(String address) {
System.out.println("其次写信封");
}
@Override
public void letterInotoEnvelope() {
System.out.println("然后把信放到信封里");
}
@Override
public void sendLetter() {
System.out.println("最后邮寄");
}
}
优点
1)减少系统的相互依赖(所有的依赖都是门面对象的依赖,与子系统无关);
2)提高了灵活性(依赖减少了,灵活性自然就提高了。不管子系统内部如何变化,只要不影响到门面对象,可以自由的修改);
3)提高安全性(针对性的开通子系统的业务);
缺点
最大的缺点就是不符合开闭原则。修改门面角色的代码风险很大。
使用场景
1)为一个复杂的模块或子系统提供一个供外界访问的接口;
2)子系统相对独立--外界对子系统的访问只要黑箱操作即可;
3)预防低水平人员带来的风险扩散.
首先,在设计初期阶段,应该要有意识的将不同的两个层分离,比如经典的三层结构,就需要考虑在数据访问层和业务逻辑层、业务逻辑层和表示层的层与层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,使得耦合大大降低。
其次,在开发阶段,子系统往往因为不断的重构演化而变得越来越复杂,大多数的模式使用时也都会产生很多很小的类,这本是好事,但也给外部调用它们的用户程序带来了使用上的困难,增加外观Facade可以提供一个简单的接口,减少它们之间的依赖。
第三,在维护一个遗留的大型系统时,可能这个系统已经非常难以维护和扩展了,但因为它包含非常重要的功能,新的需求开发必须要依赖于它。此时用外观模式Facade也是非常合适的。
所以可以为新系统开发一个外观Facade类,来提供设计粗糙或高度复杂的遗留代码的比较清晰简单的接口,让新系统与Facade对象交互,Facade与遗留代码交互所有复杂的工作。
注意事项
在门面模式中,门面角色应该是稳定,它不应该经常变化,一个系统一旦投入运行它就不应该被改变,它是一个系统对外的接口,那变来变去怎样保证其他模块的稳定运行呢?但是,业务逻辑是会经常变化的,我们已经把它的变化封装在子系统内部,无论你如何变化,对外界的访问者来说,都还是同一个门面、同样的方法--这才是架构师最希望见到的看到的结构。
其他
最少知识原则
概念
要减少对象之间的交互,只和你的”密友”沟通。
这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统中的一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它需要花许多成本维护,也会因为太复杂而不容易被其他人了解。
但是采用这个原则也会导致更多的”包装”类被制造出来,以处理和其他组件的沟通,这可能会导致复杂度和开发时间的增加,并降低运行时的性能。
描述
如何不要赢得太多的朋友和影响太多的对象?避免方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:
1)该对象本身;
2)被当做方法的参数而传递进来的对象;
3)此方法所创建或实例化的任何对象;
4)对象的任何组件;
解释1)2)3)如果某对象是调用其他的方法的返回结果(如果调用从另一个调用中返回的对象的方法)(这两句话有点绕,看下面的案例),不要调用该对象的方法!害处是:如果这样做,相当于向另一个对象的子部分发请求(而增加我们直接认识的对象数目)。在这种情况下,原则要我们改为要求该对象为我们做出请求,这么一来,我们就不需要认识该对象的组件了(让我们的朋友圈子维持在最小的状态)。比方说:
//不采用的原则:这里,我们从气象站取得温度计对象,然后再从温度计对象取得温度。
public float getTemp(){
Thermometer model = station.getThermometer();
return model.getTemperature();
}
//采用的原则:应用此原则时,我们在气象站中加进一个方法,用来向温度计请求温度。这可以减少我们所依赖的类的数目。
public float getTemp(){
return station.getTemperature();
}
解释4)把”组件”想象成是被实例变量所引用的任何对象,换句话说,把这想象成是”有一个”(HAS-A)的关系。
适配器模式和外观模式要点
(适配器部分和外观部分要一起修改,装饰者也加上)
1)当需要使用一个现有的类而其接口并不符合你的需要时,就使用适配器;
2)当需要简化并统一一个很大的接口或者一群复杂的接口时,就使用外观;
3)适配器改变接口以符合客户的期望;
4)外观将客户从一个复杂的子系统中解耦;
5)实现一个适配器可能需要一番功夫,也可能不费工夫,视目标接口的大小与复杂度而定;
6)实现一个外观,需要将子系统组合进外观中,然后将工作委托给子系统执行;
7)适配器模式有两种形式:对象适配器和类适配器。类适配器需要用到多重继承;
8)你可以为一个子系统实现一个以上的外观;
9)适配器将一个对象包装起来以改变(改变原有,用新的)其接口;装饰者将一个对象(原有基础)包装起来以增加新的行为和责任;而外观将一群对象”包装”起来以简化其接口。
标题:设计模式(八)结构型模式(对象结构型模式)门面/外观模式-FACADE
作者:yazong
地址:https://blog.llyweb.com/articles/2020/06/17/1592404640549.html