设计模式第四弹 – 结构型模式[二]

哈喽,我是janker。

上篇通过小故事已经介绍了一些结构型的设计模式,今天介绍剩下的结构型模式,阿峰的故事还在继续。

正文

外观模式

阿峰身在卷都,为了能留下来,他入手了余杭区的大house。这天,阿峰来去当地房产局办房产证,来到综合窗口,一顿签字,就把登记信息、估价、公证等流程全部给办完了。跟以往你需要跑很多窗口排队,登号等。相比,舒爽很多。作业模式的转变如图所示。

设计模式第四弹 - 结构型模式[二]

这种作业模式的转变在设计模式里叫外观模式。

定义与特点

外观(Facade)模式,顾名思义为多个子系统提供统一的门面,聚合多个复杂的子系统的操作。使外部访问者不用关心内部子系统的操作。综合窗口就相当于多个办证子窗口的门面,统一对外提供访问能力。

优缺点

  1. 优点

  • 降低客户端与子系统之间的耦合,减少调用方与子系统的依赖。

  • 对调用方屏蔽子系统组件,减少客户依赖对象的数目,使得调用方使用子系统更容易。

  • 提高了安全性,尽量减少子系统的细节对外暴露。

  1. 缺点

  • 不符合开闭原则,如果要改东西很麻烦,继承重写都不合适。

结构与实现

外观(Facade)模式的结构比较简单,主要是定义了一个高层接口。它包含了对各个子系统的引用,客户端可以通过它访问各个子系统的功能。现在来分析其基本结构和实现方法。

1. 模式的结构

外观(Facade)模式包含以下主要角色。

  1. 外观(Facade)角色:为多个子系统对外提供一个共同的接口。

  2. 子系统(Sub System)角色:实现系统的部分功能,客户可以通过外观角色访问它。

  3. 客户(Client)角色:通过一个外观角色访问各个子系统的功能。

其结构图如图所示。

设计模式第四弹 - 结构型模式[二]

2. 模式的实现

子系统SubSystem(SubSystem01、SubSystem02、SubSystem03)

public class SubSystem01 {
    public void method1() {
        System.out.println("子系统01的method1()被调用!");
    }
}
public class SubSystem02 {
    public void method2() {
        System.out.println("子系统02的method2()被调用!");
    }
}
public class SubSystem03 {
    public void method3() {
        System.out.println("子系统03的method3()被调用!");
    }
}

外观角色 Facade

public class Facade {
    private SubSystem01 obj1 = new SubSystem01();
    private SubSystem02 obj2 = new SubSystem02();
    private SubSystem03 obj3 = new SubSystem03();
    public void method() {
        obj1.method1();
        obj2.method2();
        obj3.method3();
    }
}

测试类 FacadePatternTest

public class FacadePatternTest {
    public static void main(String[] args) {
        Facade f = new Facade();
        f.method();
    }
}

其实我们在日常业务开发中,对外提供聚合接口的过程其实也是一种门面设计模式,当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。

享元模式

这天,阿峰一起约小姐姐出来玩,小姐姐手机没电了,要借阿峰的充电宝用一用。阿峰于是就把充电宝共享了出来,充电宝还是那个充电宝,只是说运用共享技术来达到充电宝的复用。在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题。创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈。

比如数据库连接池,线程池等等一定程度上都使用了享元模式。

定义与特点

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

优缺点

  1. 优点

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

  1. 缺点

  • 为了使对象可以共享,需要将一些不能共享的状态外部化,这将增加程序的复杂性。

  • 读取享元模式的外部状态会使得运行时间稍微变长。

结构与实现

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相近,此时我们就将这些对象的信息分为两个部分:内部状态和外部状态。

  • 内部状态指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变;

  • 外部状态指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

享元模式的本质是缓存共享对象,降低内存消耗。

模式的结构

享元模式的主要角色有如下。

  1. 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。(充电宝充电)

  2. 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。(具体充电类)

  3. 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。(不同适配接口为非享元角色)

  4. 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。(获取充电宝工厂)

具体实体结构图如图所示:

  • BatteryAdapter 是非享元角色,里面包含了非共享的外部状态信息 info;

  • Battery 是抽象享元角色,里面包含了享元方法 charge(BatteryAdapter adapter),非享元的外部状态以参数的形式通过该方法传入;

  • ConcreteBattery 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;

  • BatteryFactory 是享元工厂角色,它是关键字 key 来管理具体享元;

  • 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。

设计模式第四弹 - 结构型模式[二]

模式的实现

非享元角色 BatteryAdapter

public class BatteryAdapter {
    private String info;

    public BatteryAdapter(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }
}

抽象享元角色 Battery

public interface Battery {
    void charge(BatteryAdapter adapter);
}

具体享元角色 ConcreteBattery

public class ConcreteBattery implements Battery{
    private String key;

    public ConcreteBattery(String key) {
        this.key = key;
        System.out.println("充电宝" + key + "被使用!");
    }

    @Override
    public void charge(BatteryAdapter adapter) {
        System.out.print("充电宝" + key + "被使用,");
        System.out.println("充电接口为:" + adapter.getInfo());
    }
}

享元工厂角色 BatteryFactory

public class BatteryFactory {
    private HashMap<String, Battery> flyweights = new HashMap<String, Battery>();
    public Battery getBattery(String key) {
        Battery battery = (Battery) flyweights.get(key);
        if (battery != null) {
            System.out.println("具体充电宝" + key + "已经存在,被成功获取!");
        } else {
            battery = new ConcreteBattery(key);
            flyweights.put(key, battery);
        }
        return battery;
    }
}

享元测试类 FlyweightPatternTest

public class FlyweightPatternTest {
    public static void main(String[] args) {
        BatteryFactory factory = new BatteryFactory();
        Battery f01 = factory.getBattery("a");
        Battery f02 = factory.getBattery("a");
        Battery f03 = factory.getBattery("a");
        Battery f11 = factory.getBattery("b");
        Battery f12 = factory.getBattery("b");
        f01.charge(new BatteryAdapter("Android充电接口"));
        f02.charge(new BatteryAdapter("Android充电接口"));
        f03.charge(new BatteryAdapter("Android充电接口"));
        f11.charge(new BatteryAdapter("IOS充电接口"));
        f12.charge(new BatteryAdapter("IOS充电接口"));
    }
}

运行结果

充电宝a被使用!
具体充电宝a已经存在,被成功获取!
具体充电宝a已经存在,被成功获取!
充电宝b被使用!
具体充电宝b已经存在,被成功获取!
充电宝a被使用,充电接口为:Android充电接口
充电宝a被使用,充电接口为:Android充电接口
充电宝a被使用,充电接口为:Android充电接口
充电宝b被使用,充电接口为:IOS充电接口
充电宝b被使用,充电接口为:IOS充电接口

组合模式

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

定义与特点

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

组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层的节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点,树形结构图如下。

由上图可以看出,其实根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于用一种类型。但是在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。

设计模式第四弹 - 结构型模式[二]

这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户的使用带来极大的便利。

组合模式的主要优点有:

  1. 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;

  2. 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;

其主要缺点是:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;

  2. 不容易限制容器中的构件;

  3. 不容易用继承的方法来增加构件的新功能;

结构与实现

模式的结构

组合模式包含以下主要角色。

  1. 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)

  2. 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。

  3. 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。

组合模式分为透明式的组合模式和安全式的组合模式。

(1) 透明方式

在该方式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构件本来没有 Add()、Remove() 及 GetChild() 方法,却要实现它们(空实现或抛异常),这样会带来一些安全性问题。其结构图如图 1 所示。

设计模式第四弹 - 结构型模式[二]

(2) 安全方式

在该方式中,将管理子构件的方法移到树枝构件中,抽象构件和树叶构件没有对子对象的管理方法,这样就避免了上一种方式的安全性问题,但由于叶子和分支有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,所以失去了透明性。其结构图如图 2 所示。

设计模式第四弹 - 结构型模式[二]

模式的实现

假如要访问集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其对应的树状图如图 3 所示。

设计模式第四弹 - 结构型模式[二]

透明组合模式

下面为透明式的组合模式的实现代码。

抽象构件 Component

public interface Component {
    void add(Component c);
    void remove(Component c);
    Component getChild(int i);
    void operation();
}

树叶构件 Leaf

public class Leaf implements Component {
    private String name;

    public Leaf(String name) {
        this.name = name;
    }
    @Override
    public void add(Component c) {

    }

    @Override
    public void remove(Component c) {

    }

    @Override
    public Component getChild(int i) {
        return null;
    }

    @Override
    public void operation() {
        System.out.println("树叶" + name + ":被访问!");
    }

}

树枝构件 Composite

public class Composite implements Component {
    private ArrayList<Component> children = new ArrayList<Component>();
    public void add(Component c) {
        children.add(c);
    }
    public void remove(Component c) {
        children.remove(c);
    }
    public Component getChild(int i) {
        return children.get(i);
    }
    public void operation() {
        for (Object obj : children) {
            ((Component) obj).operation();
        }
    }
}

测试类 CompositePatternTest

public class CompositePatternTest {
    public static void main(String[] args) {
        Component c0 = new Composite();
        Component c1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        c0.add(leaf1);
        c0.add(c1);
        c1.add(leaf2);
        c1.add(leaf3);
        c0.operation();
    }
}

运行结果

树叶1:被访问!
树叶2:被访问!
树叶3:被访问!

安全组合模式

安全式的组合模式与透明式组合模式的实现代码类似,只要对其做简单修改就可以了,代码如下。

首先修改 Component 代码,只保留层次的公共行为。

public interface Component {
    void operation();
}

然后修改客户端代码,将树枝构件类型更改为 Composite 类型,以便获取管理子类操作的方法。

public class CompositePatterTest{
    public static void main(String[] args) {
        Composite c0 = new Composite();
        Composite c1 = new Composite();
        Component leaf1 = new Leaf("1");
        Component leaf2 = new Leaf("2");
        Component leaf3 = new Leaf("3");
        c0.add(leaf1);
        c0.add(c1);
        c1.add(leaf2);
        c1.add(leaf3);
        c0.operation();
    }
}

末尾

以上所有的结构型模式都已经介绍完了,结构型模式描述如何将类或者对象结合在一起形成更大的结构,就像搭积木, 可以通过简单积木的组合形成复杂的、功能更为强大的结构。代码如人生,也希望自己能够好好把握自己人生的结构,把握青春。我是janker,咱们下期见。

原文始发于微信公众号(爪哇干货分享):设计模式第四弹 – 结构型模式[二]

© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容