设计模式之外观模式、组合模式、享元模式

前言

本章节给您介绍23种设计模式的结构型模式中剩余的3种设计模式,分别为:外观模式组合模式享元模式

如有帮助记得3连 加 关注哦!

系列文章目录

第一章:7种设计原则之单一职责原则、接口隔离原则、依赖倒置原则、里氏替换原则

第二章:7种设计原则之开闭原则、迪米特法则、合成复用原则

第三章:设计模式之单例模式、工厂模式、原型模式、建造者模式

第四章:设计模式之适配器模式、桥接模式、代理模式、装饰者模式



一、外观模式

在现实生活中,常常存在办事较复杂的例子,如办房产证或注册一家公司,有时要同多个部门联系,这时要是有一个综合部门能解决一切手续问题就好了。

软件设计也是这样,当一个系统的功能越来越强,子系统会越来越多,客户对系统的访问也变得越来越复杂。这时如果系统内部发生改变,客户端也要跟着改变,这违背了“开闭原则”,也违背了“迪米特法则”,所以有必要为多个子系统提供一个统一的接口,从而降低系统的耦合度,这就是外观模式的目标。

外观(Facade)模式又叫作门面模式,是一种通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。该模式对外有一个统一接口,外部应用程序不用关心内部子系统的具体细节,这样会大大降低应用程序的复杂度,提高了程序的可维护性。

1.1、实现方式

1.1.1、外观模式角色

  • 外观(Facade)角色:为多个子系统对外提供一个共同的接口。
  • 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。
  • 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

1.1.2、代码实现

通过智能家居案例实现:家中有点电视、空调、电灯等家电,当我们说打开时,所有家电都打开,说关闭时所有家电都关闭。
其实就是通过一个操作入口,内部聚合了一系列的操作,不妨想一想智能语音助手。

// 灯类
public class Light {

    public void on() {
        System.out.println("打开灯。。。。。。");
    }

    public void off() {
        System.out.println("关闭灯。。。。。。");
    }
}

// 电视类
public class TV {
    public void on() {
        System.out.println("打开电视。。。。。。");
    }

    public void off() {
        System.out.println("关闭电视。。。。。。");
    }
}

// 空调类
public class AirCondition {
    public void on() {
        System.out.println("打开空调。。。。。。");
    }

    public void off() {
        System.out.println("关闭空调。。。。。。");
    }
}

// 门面类
public class SmartAppliancesFacade {

    // 聚合
    private Light light;

    private TV tv;

    private AirCondition airCondition;

    public SmartAppliancesFacade() {
        this.light = new Light();
        this.tv = new TV();
        this.airCondition = new AirCondition();
    }
	
    public void say(String msg) {
        if("打开".equals(msg)) {
            on();
        }else if("关闭".equals(msg)) {
            off();
        }else {
            System.out.println("我还听不懂你在说什么");
        }
    }

    private void on() {
        light.on();;
        tv.on();
        airCondition.on();
    }

    private void off() {
        light.off();
        tv.off();
        airCondition.off();
    }
}

// 客户端类
public class Client {
    public static void main(String[] args) {
        SmartAppliancesFacade facade = new SmartAppliancesFacade();
        facade.say("打开");
        System.out.println("===睡觉===");
        facade.say("关闭");
    }
}

1.2、外观模式优缺点

优点

  1. 降低了子系统与客户端之间的耦合度,使得子系统的变化不会影响调用它的客户类。
  2. 对客户屏蔽了子系统组件,减少了客户处理的对象数目,并使得子系统使用起来更加容易。
  3. 降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点

  1. 不能很好地限制客户使用子系统类,很容易带来未知风险。
  2. 增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

1.3、应用场景

  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将它们分离,从而提高子系统的独立性和可移植性。

二、组合模式

在现实生活中,存在很多“部分-整体”的关系,例如,大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣服与衣柜、以及厨房中的锅碗瓢盆等。在软件开发中也是这样,例如,文件系统中的文件与文件夹、窗体程序中的简单控件与容器控件等。对这些简单对象与复合对象的处理,如果用组合模式来实现会很方便。

组合(Composite Pattern)模式的定义:有时又叫作整体-部分(Part-Whole)模式,它是一种将对象组合成树状的层次结构的模式,用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。

2.1、实现方式

2.1.1、组合模式角色

  1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

2.1.2、代码实现

通过一个 管理系统 左侧菜单栏的案例实现

// 组件菜单:属于抽象根节点
public abstract class MenuComponent {

    // 菜单组件名称
    protected String name;

    // 菜单组件层级
    protected int level;

    // 添加子菜单
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 移除子菜单
    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    // 获取指定的子菜单
    public MenuComponent getChild(int index) {
        throw new UnsupportedOperationException();
    }

    // 获取菜单或菜单项的名称
    public String getName() {
        return name;
    }

    // 打印菜单名称的方法(包含子菜单和子菜单项)
    public abstract void print();
}

// 菜单类:属于树枝节点
public class Menu extends MenuComponent{

    // 菜单可以有子菜单
    private List<MenuComponent> menuComponentList = new ArrayList<>();

    public Menu(String name,int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponentList.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int index) {
        return menuComponentList.get(index);
    }

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        // 打印本菜单名称
        System.out.println(name);

        // 打印子菜单名
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}

// 菜单项类
public class MenuItem extends MenuComponent{

    public MenuItem(String name,int level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        // 打印名称
        System.out.println(name);
    }
}
// 创建对应的菜单,指定层级,并添加菜单项
public class Client {
    public static void main(String[] args) {

        MenuComponent menu1 = new Menu("菜单管理", 2);

        menu1.add(new MenuItem("页面访问",3));
        menu1.add(new MenuItem("展开菜单",3));
        menu1.add(new MenuItem("编辑菜单",3));
        menu1.add(new MenuItem("删除菜单",3));
        menu1.add(new MenuItem("新增菜单",3));


        MenuComponent menu2 = new Menu("权限管理", 2);

        menu2.add(new MenuItem("页面访问",3));
        menu2.add(new MenuItem("提交保存",3));

        MenuComponent menu3 = new Menu("角色管理", 2);

        menu3.add(new MenuItem("页面访问",3));
        menu3.add(new MenuItem("新增角色",3));
        menu3.add(new MenuItem("修改角色",3));

        // 创建1级菜单
        MenuComponent menu = new Menu("系统管理", 1);

        menu.add(menu1);
        menu.add(menu2);
        menu.add(menu3);

        menu.print();
    }
}

2.2、组合模式优缺点

优点

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

缺点

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;

2.3、应用场景

  1. 在需要表示一个对象整体与部分的层次结构的场合。
  2. 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合

三、享元模式

在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域网中的路由器、交换机和集线器,教室里的桌子和凳子等。这些对象有很多相似的地方,如果能把它们相同的部分提取出来共享,则能节省大量的系统资源,这就是享元模式的产生背景。

享元(Flyweight)模式的定义:运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

3.1、实现方式

3.1.1、享元模式中角色

  1. 享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

3.1.2、代码实现

案例:俄罗斯方块中有固定的几种形状。我们可以将这些形状进行共享

// 抽象的形状类
public abstract class AbstractBox {

    // 获取图形的方法
    public abstract String getShape();

    // 显示图形颜色
    public void display(String color) {
        System.out.println("方块形状:" + getShape() + ", 颜色:" + color);
    }
}


public class IBox extends AbstractBox{
    @Override
    public String getShape() {
        return "I";
    }
}

public class LBox extends AbstractBox{
    @Override
    public String getShape() {
        return "L";
    }
}


public class OBox extends AbstractBox{
    @Override
    public String getShape() {
        return "O";
    }
}

// 图形工厂
public class BoxFactory {
	// 存储形状
    private HashMap<String,AbstractBox> box;
    // 创建单例的工厂对象
    private static BoxFactory boxFactory = new BoxFactory();
    public BoxFactory() {
        this.box = new HashMap<>();
        box.put("I",new IBox());
        box.put("O",new OBox());
        box.put("L",new LBox());
    }

    public static BoxFactory getBoxFactory() {
        return boxFactory;
    }

    public AbstractBox getShape(String type) {
        return box.get(type);
    }
}

public class Client {
    public static void main(String[] args) {

        BoxFactory boxFactory = BoxFactory.getBoxFactory();
        AbstractBox iShape = boxFactory.getShape("I");
        iShape.display("灰色");
        AbstractBox iShap2 = boxFactory.getShape("I");
        iShap2.display("红色");
        System.out.println(iShape == iShap2);
    }
}

上边的案例急事通过一个Map来存储,如果需要什么对象直接从Map中获取即可,获取的都是同一个对象

3.2、享元模式优缺点

优点

  1. 相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点

  1. 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。
  2. 读取享元模式的外部状态会使得运行时间稍微变长。

3.2.1、外部状态和内部状态

享元对象共享的关键是区分了内部状态(Intrinsic State)和外部状态(Extrinsic State)。

内部状态:存储在享元对象内部并且不会随环境改变而改变的状态,内部状态可以共享,比如【构造方法的参数】
外部状态:享元对象的外部状态通常由客户端保存,并在享元对象被创建之后,需要使用的时候再传入到享元对象内部,比如【 方法参数)】。随环境改变而改变的、不可以共享的状态。一个外部状态与另一个外部状态之间是相互独立的

3.3、享元模式应用场景

当系统中多处需要同一组信息时,可以把这些信息封装到一个对象中,然后对该对象进行缓存,这样,一个对象就可以提供给多出需要使用的地方,避免大量同一对象的多次创建,降低大量内存空间的消耗。

享元模式其实是 工厂方法模式 的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法模式生成对象的,只不过享元模式为工厂方法模式增加了缓存这一功能。

享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构【比如上述例子中的Map,Map本身就要占用一定的内存】,所以应当在有足够多的享元实例时才值得使用享元模式【如果享元的数据较少,还额外开销一个Map,emmm…不建议了】。

总结

本章节给您介绍了结构型设计模式中的后三种,至此设计模式中的创建型结构型设计模式已经介绍完毕。接下来会继续更新最后的 行为型 设计模式。敬请期待!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石添的编程哲学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值