您现在的位置是:首页 > 正文

java设计模式之:外观模式

2024-02-29 10:40:29阅读 2

前言

举个现实生活中例子,泡茶和去茶馆喝茶的区别,如果是自己泡茶需要自行准备茶叶、茶具和开水,而去茶馆喝茶,最简单的方式就是跟茶馆服务员说想要一杯什么样的茶,是铁观音、碧螺春还是西湖龙井?正因为茶馆有服务员,顾客无须直接和茶叶、茶具、开水等交互,整个泡茶过程由服务员来完成,顾客只需与服务员交互即可,整个过程非常简单省事。

以上这种场景类似设计模式中的外观模式,也叫做门面模式。外观模式通过引入一个新的外观类来实现该功能,外观类充当了“服务员”的角色,它为多个业务类的调用提供了一个统一的入口,简化了类与类之间的交互。本文我们一起来聊聊外观模式。

外观模式介绍

外观模式是一种使用频率非常高的结构型设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互,为复杂的子系统调用提供一个统一的入口,降低子系统与客户端的耦合度,且客户端调用非常方便。

外观模式结构图:

image.png

(1) Facade(外观角色):在客户端可以调用它的方法,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任;在正常情况下,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理。

(2) SubSystem(子系统角色):在软件系统中可以有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能;每一个子系统都可以被客户端直接调用,或者被外观角色调用,它处理由外观类传过来的请求;子系统并不知道外观的存在,对于子系统而言,外观角色仅仅是另外一个客户端而已。

案例场景

场景一:自己泡茶,整个过程应该是“烧开水->拿茶具->泡茶叶”然后喝茶。

image.png

定义一个饮用水类:DrinkableWater

/**
 * 烧水
 */
public class DrinkableWater {

    public DrinkableWater(){
        System.out.println("水准备好了");
    }

    //烧水
    public void facadeWater(){
        System.out.println("水烧开了");
    }
}

定义一个茶叶类:Tea

/**
 * 茶叶
 */
public class Tea {

    public Tea(){
        System.out.println("茶叶准备好了");
    }

    //取茶
    public void facadeTea(){
        System.out.println("可以泡茶了");
    }
}

定义一个茶杯类:TeaCup

/**
 * 茶杯
 */
public class TeaCup {

    public TeaCup(){
        System.out.println("茶杯准备好了");
    }

    //泡茶
    public void facadeTeaCup(Tea tea){
        tea.facadeTea();
        System.out.println("茶叶泡进茶杯了");
        System.out.println("茶冲好了");
    }
}

准备就绪,开始泡茶

public static void main(String[] args) {
    System.out.println("准备泡茶...");
    DrinkableWater drinkableWater = new DrinkableWater();
    TeaCup teaCup = new TeaCup();
    Tea tea = new Tea();

    drinkableWater.facadeWater();
    teaCup.facadeTeaCup(tea);
    System.out.println("喝茶...");
}

运行结果

准备泡茶...
水准备好了
茶杯准备好了
茶叶准备好了
水烧开了
可以泡茶了
茶叶泡进茶杯了
茶冲好了
喝茶...

场景二:去茶馆喝茶,不用自己动手泡茶了,直接告诉茶馆的服务员就行了。

image.png

定义一个服务员类:

/**
 * 服务员
 */
public class Waiter {

    private DrinkableWater drinkableWater = new DrinkableWater();
    private TeaCup teaCup = new TeaCup();
    private Tea tea = new Tea();

    //获得一杯茶
    public void getTea(){
        drinkableWater.facadeWater();
        teaCup.facadeTeaCup(tea);
    }
}

客户类:Customer

public class Customer {

    public static void main(String[] args) {
        //叫店小二
        Waiter waiter = new Waiter();
        //从店小二那获得一杯茶
        waiter.getTea();
    }
}

运行结果

水准备好了
茶杯准备好了
茶叶准备好了
水烧开了
可以泡茶了
茶叶泡进茶杯了
茶冲好了

在上面的泡茶的例子中,客人就是客户角色,茶馆服务员就是门面角色,茶具、饮用水、茶叶就是子系统角色。

代码看上去都很简单,也许你感觉二者区别不是很大,假如在软件开发中三个子系统之间有先后顺序,还有来自不同网络开销,我们通过门面模式提供的方法,就屏蔽了这些差异,让我们只需要调用门面角色提供给我们的方法即可。

外观模式优点

  1. 减少系统的相互依赖

如果我们不使用门面模式, 外界访问直接深入到子系统内部, 相互之间是一种强耦合关系, 你死我就死, 你活我才能活, 这样的强依赖是系统设计所不能接受的, 门面模式的出现就很好地解决了该问题, 所有的依赖都是对门面对象的依赖, 与子系统无关。

  1. 提高安全性

想让你访问子系统的哪些业务就开通哪些逻辑, 不在门面上开通的方法, 你休想访问到。

外观模式缺点

  1. 不能很好地限制客户端直接使用子系统类,如果对客户端访问子系统类做太多的限制则减少了可变性和灵活性。

  2. 如果设计不当,增加新的子系统可能需要修改外观类的源代码,违背了开闭原则。

外观模式应用场景

  1. 解决易用性问题

门面模式可以用来封装系统的底层实现,隐藏系统的复杂性,提供一组更加简单易用、更高层的接口。

  1. 解决性能问题

通过将多个接口调用替换为一个门面接口调用,减少网络通信成本,提高客户端的响应速度。

  1. 解决分布式事务问题

需要调用多个子系统的接口方法,而这些接口要么都成功,要么都失败,我们就可以利用门面模式包裹这些子系统接口,然后通过某种方法保证这些接口在一个事务中完成。

总结

在几乎所有的软件中都能够找到外观模式的应用,如绝大多数B/S系统都有一个首页或者导航页面,大部分C/S系统都提供了菜单或者工具栏,在这里,首页和导航页面就是B/S系统的外观角色,而菜单和工具栏就是C/S系统的外观角色,通过它们用户可以快速访问子系统,降低了系统的复杂程度。所有涉及到与多个业务对象交互的场景都可以考虑使用外观模式进行重构。

很多时候不是设计模式没有用,而是自己编程开发经验不足导致即使学了设计模式也很难驾驭。毕竟这些知识都是经过一些实际操作提炼出来的精华。

网站文章

  • js input 关闭输入法 chrome_Vim和中文输入法的完美结合

    这个问题真的是老掉牙了。现在还反复地被很多人讨论来讨论去。实际上我2016年就提供了一个完美的解决方案。分析见,https://blog.binchen.org/posts/how-to-input-...

    2024-02-29 10:40:00
  • 初学Java-多态

    初学Java-多态

    当我运行时,两个方法中的打印内容我没有写一样的,输出结果是:“狗吃肉”。访问的是子类中的方法。如果我删除掉子类中的eat方法,不进行方法重写,输出结果的是:“爱吃肉”。什么是多态,顾名思义,就是研究对...

    2024-02-29 10:39:52
  • Java数据结构与算法1

    Java数据结构与算法1

    算法是程序的灵魂,优秀的程序可以在海量数据计算时,依然保持高速计算数据结构和算法的关系程序 = 数据结构 + 算法数据结构是算法的基础, 换言之,想要学好算法,需要把数据结构学到位。数据结构和算法学习大纲数据结构可以简单的理解为数据与数据之间所存在的一些关系,数据的结构分为数据的存储结构和数据的逻辑结构。

    2024-02-29 10:39:44
  • eclipse连接数据库sql server 2008

    eclipse连接数据库sql server 2008

    fgkjskdj从弱鸡开始学java真是不容易呐,到处都是坑,为了避免你们以后入同样的坑,写篇博客教小白们如何正确连接数据库1.sql server 2008R22.Eclipse如果想要在Eclipse编写java代码连接上数据库首先需要安装SQL Server 2008R2数据库驱动sqljdbc4.jar   这是下载地址sqljdbc4.jar将sqljdbc4.jar

    2024-02-29 10:39:15
  • Java学习 | 面向对象 | this关键字

    Java学习 | 面向对象 | this关键字

    this的本质分析this是一个关键字,翻译为:这个this是一个变量之引用,this变量中保存了本身所在对象的内存地址,即this引用指向自身所在对象,this存储在JVM堆内存Java对象的内部。...

    2024-02-29 10:39:09
  • 【Godot4.1】Godot实现闪烁效果(Godot使用定时器实现定时触发的效果)

    【Godot4.1】Godot实现闪烁效果(Godot使用定时器实现定时触发的效果)

    在上面代码中,我们定义了 StartBlinking() 方法,该方法会设置 Timer 的等待时间和循环触发,并连接 Timer 的 timeout 信号到 OnTimerTimeout() 方法。...

    2024-02-29 10:39:00
  • Dockerfile打包nginx镜像

    Dockerfile打包nginx镜像

    default.conf配置。

    2024-02-29 10:38:52
  • 服务器装系统提示获取分区失败,U盘重装系统时获取硬盘分区失败如何解决?...

    服务器装系统提示获取分区失败,U盘重装系统时获取硬盘分区失败如何解决?...

    使用U盘重装系统非常方便,但是当我们使用U盘一键装系统时,如果出现获取硬盘分区失败,这时候应该怎么办?下面小编就来教你具体的解决方法。U盘一键重装系统,小编推荐>>>系统之家U盘一键重装,一键重装,速度非常快!U盘重装系统时获取硬盘分区失败如何解决?先回到PE系统中,尝试打开资源管理器中能否看到盘符,如果不行说明是硬盘问题,需要更换硬盘;如果可以,重启电脑后进入BIOS,在BIO...

    2024-02-29 10:38:23
  • 复化梯形公式和复化辛普森公式matlab_科学计算第五讲(Newton-Cotes公式与复化公式及其误差估计,逐次分半与加速收敛)...

    第五章、数值积分5.1Newton-Cotes公式目标:定积分 难点: 原函数不好求思路: ,其中 是插值多项式5.1.1梯形求积公式使用 两个点的插值多项式. 用梯形面积近似积分5.1.2抛物线形求积公式(Simpson公式)使用 三个点的插值多项式. 用二次抛物线的积分近似5.1.3Newton-Cotes公式将 等分为 个区间,使用 个点. 其分点为 插值多项式为 用插值多...

    2024-02-29 10:38:15
  • 修改服务器端口为百兆,修改服务器3389端口为其它端口

    一、服务器不使用防火墙情况1、cmd进入regedit,2、找到:①计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal S...

    2024-02-29 10:38:08