设计模式汇总:结构型模型(下)

Posted 流云易采

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式汇总:结构型模型(下)相关的知识,希望对你有一定的参考价值。

总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
其实还有两类:并发型模式和线程池模式。
前面已经介绍了几个结构型模型:《设计模式汇总:结构型模型(上)》

四、外观模式(Fasade)

外观模式:为子系统中的一组接口提供一个一致的界面, Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
引入外观角色之后,用户只需要直接与外观角色交互,用户与子系统之间的复杂关系由外观角色来实现,从而降低了系统的耦合度。
即提供一个Facade类,来统一操作子系统;适用于子系统比较复杂的情况。

主要解决问题:组件的客户和组件中各种复杂的子系统有了过多的耦合,随着外部客户程序和各子系统的演化,这种过多的耦合面临很多变化的挑战。
举个例子:
比如,现在有一辆汽车,我们(客户程序)要启动它,那我们就要发动引擎(子系统1),使四个车轮(子系统2)转动。但是实际中我们并不需要用手推动车轮使其转动,我们踩下油门,此时汽车再根据一些其他的操作使车轮转动。油门就好比系统给我们留下的接口,不论汽车是以何种方式转动车轮,车轮变化成什么牌子的,我们要开走汽车所要做的还是踩下油门。
使用外观模式之后的效果:
这里写图片描述
类图:
这里写图片描述

其中涉及到的角色有
外观角色(Facade):是模式的核心,他被客户client角色调用,知道各个子系统的功能。同时根据客户角色已有的需求预订了几种功能组合。
子系统角色(Subsystem classes):实现子系统的功能,并处理由Facade对象指派的任务。对子系统而言,facade和client角色是未知的,没有Facade的任何相关信息;即没有指向Facade的实例。

代码:

// 子系统类
class SubClass1 {
    void operation1() {
        System.out.println("SubClass1 operation1");
    }
}

class SubClass2 {
    void operation2() {
        System.out.println("SubClass2 operation2");
    }
}

class SubClass3 {
    void operation3() {
        System.out.println("SubClass3 operation3");
    }
}

// 外观类
class Facade {
    SubClass1 subClass1;
    SubClass2 subClass2;
    SubClass3 subClass3;

    public Facade() {
        subClass1 = new SubClass1();
        subClass2 = new SubClass2();
        subClass3 = new SubClass3();
    }

    public void method() {
        subClass1.operation1();
        subClass2.operation2();
        subClass3.operation3();
    }
}

// 用户类
class Client {
    public void doSth() {
        Facade facade = new Facade();
        facade.method();
    }
}

适用场合:
1)当需要为复杂子系统提供一个外部简单接口来供用户调用使用时,子系统往往会因为不断演化而变得越来越复杂;当一些用户不需要定制子系统时,facade可以为用户提供一个默认缺省的视图来供使用,当有其他需要定制的用户,可以越过facade进行自行定制;这样的模式在开源项目中比较常见,比如Retrofit这些。
2)为了避免客户程序和抽象类存在很大的依赖性,引入facade来使得子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
3)减少子系统之间的耦合;当需要构建一个层次结构的子系统时,使用 facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,可以让子系统仅通过facade进行通讯,从而简化了它们之间的依赖关系。

Facade模式的优缺点:
优点:
1)松散耦合:门面模式松散了客户端与子系统的耦合关系,让子系统内部的模块能更容易扩展和维护
简单易用:门面模式让子系统更加易用,客户端不再需要了解子系统内部的实现,也不需要跟踪多个子系统内部的模块进行交互,只需要跟门面类交互就可以了
更好的划分访问层次:通过合理的使用Facade,可以帮助我们更好的划分访问的层次。有些方法是对系统外的,有些方法是系统内部使用的。把需要暴露给外部的功能集中到门面中,这样既方便客户端使用,也很好地掩藏了内部的细节。
缺点:
1) 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
2) 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

参考博客:http://blog.csdn.net/u013256816/article/details/51009480

五、桥接模式(Bridge)

意图:将抽象部分与实现部分分离,使它们都可以独立的变化。
适用场景:将抽象与实现分离
设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:
第一种设计方案是为每一种形状都提供一套各种颜色的版本。
第二种设计方案是根据实际需要对形状和颜色进行组合
对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。
类图:
这里写图片描述

实现上面的案例得:

// Abstraction抽象类
abstract class IShape {
    protected IColor color;

    public IShape(IColor color) {
        this.color = color;
    }

    abstract void operation();
}

// Implementor实现类接口
abstract class IColor {
    abstract void operationImpl();
}

// ConcreteImplementor具体实现类
class ColorRed extends IColor{
    @Override
    void operationImpl() {
        System.out.print("Color is Red");
    }
}

class ColorBlue extends IColor{
    @Override
    void operationImpl() {
        System.out.print("Color is Blue");
    }
}

// RefinedAbstraction扩充抽象类
class Rectangle extends IShape {
    public Rectangle(IColor color) {
        super(color);
    }

    @Override
    void operation() {
        System.out.print("It is a rectangle");
        color.operationImpl();
    }
}

class Oval extends IShape {
    public Oval(IColor color) {
        super(color);
    }

    @Override
    void operation() {
        System.out.print("It is a Oval");
        color.operationImpl();
    }
}

// Cient使用类
class Client {
    public void doTest() {
        IShape shape1 = new Rectangle(new ColorRed());
        IShape shape2 = new Oval(new ColorBlue());
        shape1.operation();
        shape2.operation();
    }
}

优缺点:
1)优点:
分离抽象接口及其实现部分。
桥接模式有时类似于多继承方案,但是多继承方案违背了类的单一职责原则(即一个类只有一个变化的原因),复用性比较差,而且多继承结构中类的个数非常庞大,桥接模式是比多继承方案更好的解决方法。
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
实现细节对客户透明,可以对用户隐藏实现细节。
2)缺点 :
桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进 行设计与编程。
桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

适用环境:
如果你不希望在抽象和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象和实现部分分开,然后在程序运行期间来动态的设置抽象部分需要用到的具体的实现,还可以动态切换具体的实现。
如果出现抽象部分和实现部分都应该可以扩展的情况,可以采用桥接模式,让抽象部分和实现部分可以独立的变化,从而可以灵活的进行单独扩展,而不是搅在一起,扩展一边会影响到另一边。
如果希望实现部分的修改,不会对客户产生影响,可以采用桥接模式,客户是面向抽象的接口在运行,实现部分的修改,可以独立于抽象部分,也就不会对客户产生影响了,也可以说对客户是透明的。
如果采用继承的实现方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看是否能分离成不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

六、组合模式(Composite)

概念:将对象组合成树形结构以表示“部分-整体”的层次结构。 组合模式使得用户对单个对象和组合对象的使用具有唯一性。
应用:组合模式比较典型的应用就是
1)文件目录结构
2)android中的ViewGroup和View
这里写图片描述

适用性:组合模式解耦了客户程序与复杂元素内部结构,从而使客户程序可以向处理简单元素一样来处理复杂元素。
如果你想要创建层次结构,并可以在其中以相同的方式对待所有元素,那么组合模式就是最理想的选择。
1)表示对象的部分-整体层次结构,如树形菜单、文件夹菜单、部门组织架构等。
2)用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
类图:
这里写图片描述
涉及角色:
1) Component:是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component.
2) Leaf:在组合中表示叶子节点对象,叶子节点没有子节点。
3) Composite:定义树枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加和删除等。
代码:

abstract class Component {
    protected String name;

    public Component(String name) {
        this.name = name;
    }

    abstract void operation();
    abstract void add(Component component);
    abstract void remove(Component component);
    abstract Component getChild(int i);
}

class Leaf extends Component {
    public Leaf(String name) {
        super(name);
    }

    @Override
    void operation() {
        System.out.println("Leaf " + name);
    }

    @Deprecated
    void add(Component component) {}

    @Deprecated
    void remove(Component component) {}

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

class Composite extends Component {
    ArrayList<Component> childs;

    public Composite(String name) {
        super(name);
        childs = new ArrayList<>();
    }

    @Override
    void operation() {
        for (Component child : childs)
            child.operation();
    }

    @Override
    void add(Component component) {
        childs.add(component);
    }

    @Override
    void remove(Component component) {
        childs.remove(component);
    }

    @Override
    Component getChild(int i) {
        return childs.get(i);
    }
}

class Client {
    public void doTest() {
        Component leaf1 = new Leaf("leaf1");
        Composite com = new Composite("Composite");
        com.add(leaf1);
        com.add(new Leaf("leaf2"));

        Composite com2 = new Composite("Composite2");
        com2.add(new Leaf("l3"));
        com2.add(com);
    }
}

优缺点:
优点:
1)可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
2)客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
3)定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
4)更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
缺点 :使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联

七、享元模式(FlyWeight)

背景:面向对象可以非常方便的解决一些扩展性的问题,但是在这个过程中系统务必会产生一些类或者对象,如果系统中存在对象的个数过多时,将会导致系统的性能下降。对于这样的问题解决最简单直接的办法就是减少系统中对象的个数。
享元模式提供了一种解决方案,使用共享技术实现相同或者相似对象的重用。也就是说实现相同或者相似对象的代码共享。
Java中最典型的应用就是String:

String s1 = "a";
String s2 = "a";
System.out.println(s1 == s2);// String采用享元模式 存储在常量池

定义:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
概念:在了解享元模式之前我们先要了解两个概念:内部状态、外部状态。
内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
由于享元模式区分了内部状态和外部状态,所以我们可以通过设置不同的外部状态使得相同的对象可以具备一些不同的特性,而内部状态设置为相同部分。在我们的程序设计过程中,我们可能会需要大量的细粒度对象来表示对象,如果这些对象除了几个参数不同外其他部分都相同,这个时候我们就可以利用享元模式来大大减少应用程序当中的对象。如何利用享元模式呢?这里我们只需要将他们少部分的不同的部分当做参数移动到类实例的外部去,然后再方法调用的时候将他们传递过来就可以了。这里也就说明了一点:内部状态存储于享元对象内部,而外部状态则应该由客户端来考虑。

享元模式分为两种:单纯享元模式复合享元模式两种形式;
(一)单纯享元模式:
在单纯的享元模式中,所有的享元对象都是可以共享的。
类图:
这里写图片描述
涉及角色:
Flyweight: 抽象享元类。所有具体享元类的超类或者接口,通过这个接口,Flyweight可以接受并作用于外部专题
ConcreteFlyweight: 具体享元类。指定内部状态,为内部状态增加存储空间。
FlyweightFactory: 享元工厂类。用来创建并管理Flyweight对象,它主要用来确保合理地共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory就会提供一个已经创建的Flyweight对象或者新建一个(如果不存在)。
享元模式的核心在于享元工厂类,享元工厂类的作用在于提供一个用于存储享元对象的享元池,用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户,并在享元池中保存该新增对象。
代码:

abstract class Flywight {
    abstract void operation(String externalState);
}

class ConcreteFlyweight extends Flywight {
    private String internalState;

    public ConcreteFlyweight(String internalState) {
        this.internalState = internalState;
    }

    @Override
    void operation(String externalState) {
        System.out.println("内部状态:" + internalState);
        System.out.println("外部状态:" + externalState);
    }
}

class FlyweightFactory {
    HashMap<String, Flywight> map = new HashMap<>();

    public Flywight getFlyweight(String state) {
        Flywight flywight = map.get(state);
        if (flywight == null) {
            flywight = new ConcreteFlyweight(state);
            map.put(state, flywight);
        }
        return flywight;
    }
}

(二)复合享元模式:
在单纯享元模式中,所有的享元对象都是单纯享元对象,也就是说都是可以直接共享的。还有一种较为复杂的情况,将一些单纯享元使用合成模式加以复合,形成复合享元对象。这样的复合享元对象本身不能共享,但是它们可以分解成单纯享元对象,而后者则可以共享。

类图:
这里写图片描述
角色:
抽象享元(Flyweight) :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
具体享元(ConcreteFlyweight):实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
复合享元(ConcreteCompositeFlyweight) :复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象。
享元工厂(FlyweightFactory) :负责创建和管理享元角色。

代码:

abstract class Flywight {
    abstract void operation(String externalState);
}

class ConcreteFlyweight extends Flywight {
    private String internalState;

    public ConcreteFlyweight(String internalState) {
        this.internalState = internalState;
    }

    @Override
    void operation(String externalState) {
        System.out.println("内部状态:" + internalState);
        System.out.println("外部状态:" + externalState);
    }
}

class ConcreteCompositeFlyweight extends Flywight {
    Map<String, Flywight> list = new HashMap<>();

    public void add(String state, Flywight flywight) {
        list.put(state, flywight);
    }

    @Override
    void operation(String externalState) {
        for (Map.Entry<String, Flywight> data : list.entrySet()) {
            data.getValue().operation(externalState);
        }
    }
}

class FlyweightFactory {
    HashMap<String, Flywight> map = new HashMap<>();

    public Flywight getFlyweight(List<String> states) {
        ConcreteCompositeFlyweight ccFlyweight = new ConcreteCompositeFlyweight();

        for (String state : states)
            ccFlyweight.add(state, getFlyweight(state));

        return ccFlyweight;
    }

    public Flywight getFlyweight(String state) {
        Flywight flywight = map.get(state);
        if (flywight == null) {
            flywight = new ConcreteFlyweight(state);
            map.put(state, flywight);
        }
        return flywight;
    }
}

优缺点:
享元模式的优点在于它大幅度地降低内存中对象的数量。但是,它做到这一点所付出的代价也是很高的:
1)享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
2)享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。

以上是关于设计模式汇总:结构型模型(下)的主要内容,如果未能解决你的问题,请参考以下文章

设计模式汇总:结构型模型(下)

设计模式汇总:结构型模型(上)

设计模式汇总:创建型模式

设计模式汇总

Java设计模式汇总

设计模式汇总