【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)

【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)

  • 1、设计模式
  • 2、结构型模式
        • 2.1 概述
        • 2.2 七大结构型设计模式
          • 2.2.1 适配器模式
          • 2.2.2 桥接模式
          • 2.2.3 组合模式
          • 2.2.4 装饰者模式
          • 2.2.5 外观模式
          • 2.2.6 享元模式
          • 2.2.7 代理模式

1、设计模式

  • 之前聊了设计模式的概念,分类和五种创建型模式,今天聊设计模式之结构型模式。
  • 创建型详见:【设计模式】设计模式之创建型模式(单例、原型、工厂、建造者)

2、结构型模式

2.1 概述

  • 结构型模式(Structural Pattern): 描述如何将类或者对象结合在一起形成更大的结构,就像搭积木,可以通过简单积木的组合形成复杂的、功能更为强大的结构
  • 结构型模式可以分为类结构型模式和对象结构型模式:

1、类结构型模式关心类的组合,由多个类可以组合成一个更大的系统,在类结构型模式中一般只存在继承关系和实现关系。

2、对象结构型模式关心类与对象的组合,通过关联关系使得在一个类中定义另一个类的实例对象,然后通过该对象调用其方法。根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。

2.2 七大结构型设计模式

2.2.1 适配器模式

2.2.1.1 概念

  • 适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,使接口不兼容的类可以一起工作,别名包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
  • 适配器模式主要目的组合两个不相干类。常用有两种方法,第一种解决方案是修改各自类的接口,但如果没有源码,或不愿意为一个应用而修改各自的接口,则需要使用 Adapter 适配器,在两种接口之间创建一个混合接口。
  • 图解:
    【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第1张图片
  • 适用环境:

1、系统需要使用现有的类,而这些类的接口不符合系统的需要。

2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作

2.2.1.2 适配器模式代码案例:

/**
 * 定义客户端使用的接口,与业务相关
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/4
 */
public interface Target {

    /**
     * 客户端请求处理的方法
     */
    void request();
}

/**
 * 已经存在的接口,这个接口需要配置
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/4
 */
@Slf4j
public class Adaptee {

    /**
     * 原本存在的方法
     */
    public void specificRequest(){

        log.info("【已经存在的接口】");
    }
}

/**
 * 适配器
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/4
 */
public class Adapter implements Target{
    /**
     * 持有需要被适配的接口对象
     */
    private Adaptee adaptee;
    /**
     * 构造方法,传入需要被适配的对象
     * @param adaptee 需要被适配的对象
     */
    public Adapter(Adaptee adaptee){
        this.adaptee = adaptee;
    }
    @Override
    public void request() {
        adaptee.specificRequest();
    }

}
/**
 * 使用适配器的客户端
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/4
 */
public class Client {
    public static void main(String[] args){
        //创建需要被适配的对象
        Adaptee adaptee = new Adaptee();
        //创建客户端需要调用的接口对象
        Target target = new Adapter(adaptee);
        //请求处理
        target.request();
    }
}

2.2.1.3 JDK 中的适配器使用:

  • 使用适配器模式的类

java.util.Arrays#asList()
java.io.InputStreamReader(InputStream)
java.io.OutputStreamWriter(OutputStream)

  • Java I/O 库大量使用了适配器模式,如 ByteArrayInputStream 是一个适配器类,它继承了 InputStream 的接口,并且封装了一个 byte 数组。换言之,它将一个 byte 数组的接口适配成 InputStream 流处理器的接口。

  • 在 OutputStream 类型中,所有的原始流处理器都是适配器类。ByteArrayOutputStream 继承了 OutputStream 类型,同时持有一个对 byte 数组的引用。它将一个 byte 数组的接口适配成 OutputString 类型的接口,因此也是一个对象形式的适配器模式的应用。

  • FileOutputStream 继承了 OutputStream 类型,同时持有一个对 FileDiscriptor 对象的引用。这是一个将 FileDiscriptor 接口适配成 OutputStream 接口形式的对象型适配器模式。

  • Reader 类型的原始流处理器都是适配器模式的应用。StringReader 是一个适配器类,StringReader 类继承了 Reader 类型,持有一个对 String 对象的引用。它将 String 的接口适配成 Reader 类型的接口。

2.2.1.4 适配器模式总结:

1、适配器模式用于将一个接口转换成客户希望的另一个接口,使接口不兼容的类可以一起工作,别名包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。

2、适配器模式包含四个角色:1、目标抽象类定义客户要用的特定领域的接口;2、适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心;3、适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;4、在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。

3、在类适配器模式中,适配器类实现了目标抽象类接口并继承了适配者类,并在目标抽象类的实现方法中调用所继承的适配者类的方法;在对象适配器模式中,适配器类继承了目标抽象类并定义了一个适配者类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业务方法。

4、适配器模式的主要优点是将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“开闭原则”;类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类,对象适配器模式的缺点是很难置换适配者类的方法。

5、适配器模式适用情况包括:系统需要使用现有的类,而这些类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。

2.2.2 桥接模式

2.2.2.1 概念

  • 桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体(Handle and Body)模式或接口(Interface)模式。
  • 桥接模式是一种很实用的结构型设计模式,如果软件系统中某个类存在两个独立变化的维度,通过该模式可以将这两个维度分离出来,使两者可以独立扩展,让系统更加符合“单一职责原则。
  • 举例:绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,则需要4*3=12个类,这时候我们就可以用桥接模式,一个形状,一个颜色,将这两个维度分离开来,每一个维度都可以独立扩展。
  • 结构 :

1、Abstraction:抽象类(形状绘制抽象类)

2、RefinedAbstraction:具体类:具体形状绘制抽象类

3、Implementor:实现类接口,这里指颜色

4、ConcreteImplementor:具体实现类,具体颜色

2.2.2.2 桥接模式代码案例:

/**
 * 填充色
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
public interface Colour {

    /**
     * 填充
     */
    void fill();
}
/**
 * 红色填充色
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
@Slf4j
public class RedColour implements Colour {
    @Override
    public void fill() {

        log.info("填充红色!");
    }
}
/**
 * 蓝色填充色
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
@Slf4j
public class BlueColour implements Colour {
    @Override
    public void fill() {

        log.info("填充蓝色!");
    }
}
/**
 * 绘制图形抽象类
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
public abstract class AbstractionShape {

    public Colour colour;

    public void SetColour(Colour colour){
        this.colour = colour;
    }

    public  abstract  void Shape();
}

/**
 * 圆形
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
@Slf4j
public class Circle extends AbstractionShape {
    @Override
    public void Shape() {
        log.info("【圆形】");
    }
}

/**
 * 正方形
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
@Slf4j
public class Square extends AbstractionShape {
    @Override
    public void Shape() {
        log.info("【正方形】");
    }
}

/**
 * 桥接模式客户端测试
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/5
 */
public class Client {

    public static void main(String[] args) {
        AbstractionShape circle = new Circle();
        circle.SetColour(new RedColour());
        circle.Shape();
        circle.colour.fill();
        AbstractionShape square = new Square();
        square.SetColour(new BlueColour());
        square.Shape();
        square.colour.fill();
    }
}

2.2.2.3 适用环境:

1、如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。

2、抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响,在程序运行时可以动态将一个抽象化子类的对象和一个实现化子类的对象进行组合,即系统需要对抽象化角色和实现化角色进行动态耦合。

3、一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。虽然在系统中使用继承是没有问题的,但是由于抽象化角色和具体化角色需要独立变化,设计要求需要独立管理这两者。

4、对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用。

2.2.2.4 桥接模式总结:

1、桥接模式包含如下四个角色:1、抽象类中定义了一个实现类接口类型的对象并可以维护该对象;2、扩充抽象类扩充由抽象类定义的接口,它实现了在抽象类中定义的抽象业务方法,在扩充抽象类中可以调用在实现类接口中定义的业务方法;3、实现类接口定义了实现类的接口,实现类接口仅提供基本操作,而抽象类定义的接口可能会做更多更复杂的操作;4、具体实现类实现了实现类接口并且具体实现它,在不同的具体实现类中提供基本操作的不同实现,在程序运行时,具体实现类对象将替换其父类对象,提供给客户端具体的业务操作方法。

2、在桥接模式中,抽象化(Abstraction)与实现化(Implementation)脱耦,它们可以沿着各自的维度独立变化。

3、桥接模式的主要优点是分离抽象接口及其实现部分,是比多继承方案更好的解决方法,桥接模式还提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统,实现细节对客户透明,可以对用户隐藏实现细节;其主要缺点是增加系统的理解与设计难度,且识别出系统中两个独立变化的维度并不是一件容易的事情。

4、桥接模式适用情况包括:需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系;抽象化角色和实现化角色可以以继承的方式独立扩展而互不影响;一个类存在两个独立变化的维度,且这两个维度都需要进行扩展;设计要求需要独立管理抽象化角色和具体化角色;不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统。

2.2.3 组合模式

2.2.3.1 概念

  • 组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
  • 楞说有点不好理解,举个例子,树形大家应该见得多了,比如常见的文件系统:
    【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第2张图片
  • 图中有两种元素:文件(蓝色节点)和文件夹(白色节点),其中在文件夹中即可以包含文件,也可以包含子文件夹,但是在文件中就不能再包含子文件或者子文件夹了。如果用代码实现,不同的文件类型,如图片,文档加上文件夹类的设计和实现都会非常复杂,需要定义多个集合存储不同类型的成员,而且需要针对不同的成员提供增加、删除和获取等管理和访问成员的方法,存在大量的冗余代码,系统维护较为困难。
  • 而采用组合模式就不用这么麻烦,定义一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。
    【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第3张图片
    2.2.3.2 桥接模式代码案例:
/**
 * Component抽象类
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/6
 */
public abstract class Component {

    /**
     * 增加成员
     * @param c
     */
    public abstract void add(Component c);
    /**
     * 删除成员
     * @param c
     */
    public abstract void remove(Component c);
    /**
     * 获取成员
     * @param i
     * @return
     */
    public abstract Component getChild(int i);
    /**
     * 业务方法
     */
    public abstract void operation();
}
/**
 * 叶子继承Component
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/6
 */
@Slf4j
public class Leaf extends Component{
    @Override
    public void add(Component c) {
        log.info("我是叶子,没有添加功能");
    }

    @Override
    public void remove(Component c) {
        log.info("我是叶子,没有移除功能");
    }

    @Override
    public Component getChild(int i) {
        log.info("我是叶子,没有获取功能");
        return null;
    }

    @Override
    public void operation() {
        log.info("我是叶子,功能是巴拉巴拉。。。");
    }
}
/**
 * 容器继承Component
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/6
 */
public class Composite extends Component {
    private ArrayList<Component> list = new ArrayList<Component>();

    @Override
    public void add(Component c) {
        list.add(c);
    }

    @Override
    public void remove(Component c) {
        list.remove(c);
    }

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

    @Override
    public void operation() {
        //递归调用成员构件的业务方法
        for(Object obj:list) {
            ((Component)obj).operation();
        }
    }
}
/**
 * 测试
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/6
 */
public class Test {
    public static void main(String[] args) {
        Composite composite = new Composite();
        Leaf leaf = new Leaf();
        composite.add(leaf);
        Composite composite1 = new Composite();
        Leaf leaf1 = new Leaf();
        composite1.add(leaf1);
        composite.add(composite1);
        composite.operation();
    }
}

2.2.3.3 适用环境:

  • 当想表达对象的部分-整体的层次结构时。
  • 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时

2.2.3.4 组合模式总结:

  • 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。

  • 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待。

  • 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。

2.2.4 装饰者模式

2.2.4.1 概念

  • 装饰模式(Decorator Pattern) :动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”。
  • 适用环境

1、在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2、处理那些可以撤消的职责。

3、当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的 子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

2.2.4.2 装饰模式代码案例:

/**
 * 奶茶接口
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public interface MilkTea {

    /**
     * 奶茶名字
     * @return
     */
    String milkTeaName();
    /**
     * 奶茶价格
     * @return
     */
    int milkTeaPrice();
}

/**
 * 蜂蜜奶茶
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public class HoneyMilkTea implements MilkTea{

    @Override
    public String milkTeaName() {
        return "蜂蜜奶茶";
    }

    @Override
    public int milkTeaPrice() {
        return 15;
    }
}

/**
 * 珍珠奶茶
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public class PearlMilkTea implements MilkTea{

    @Override
    public String milkTeaName() {
        return "珍珠奶茶";
    }

    @Override
    public int milkTeaPrice() {
        return 10;
    }
}
/**
 * 口味
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public class Taste implements MilkTea{

    @Override
    public String milkTeaName() {
        return null;
    }

    @Override
    public int milkTeaPrice() {
        return 0;
    }
}

/**
 * 加冰
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public class AddIceTaste extends Taste{

    private int pri = 5;
    private String name = "加冰";
    private MilkTea tea = null;

    public AddIceTaste(MilkTea tea){
        this.tea = tea;
    }

    @Override
    public String milkTeaName() {
        return name + tea.milkTeaName();
    }

    @Override
    public int milkTeaPrice() {
        return tea.milkTeaPrice()+pri;
    }

}

/**
 * 装饰者测试
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class Test {

    public static void main(String[] args) {
        MilkTea tea = new HoneyMilkTea();
        tea = new AddIceTaste(tea);
        log.info("买了杯:{},价格是:{}",tea.milkTeaName(),tea.milkTeaPrice());
    }
}

  • 装饰模式包含如下角色:

Component: 抽象构件(奶茶接口)
ConcreteComponent: 具体构件(奶茶接口实现,具体奶茶)
Decorator: 抽象装饰类(奶茶接口实现,口味)
ConcreteDecorator: 具体装饰类(口味类子类,具体口味)

2.2.4.3 装饰模式总结:

1、装饰模式用于动态地给一个对象增加一些额外的职责,就增加对象功 能来说,装饰模式比生成子类实现更为灵活。。

2、装饰模式包含四个角色:1、抽象构件定义了对象的接口,可以给这些对 象动态增加职责(方法);2、具体构件定义了具体的构件对象,实现了 在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法); 3、抽象装饰类是抽象构件类的子类,用于给具体构件增加职责,但是具 体职责在其子类中实现;4、具体装饰类是抽象装饰类的子类,负责向构 件添加新的职责。

3、使用装饰模式来实现扩展比继承更加灵活,它以对客户透明的方式动 态地给一个对象附加更多的责任。装饰模式可以在不需要创造更多子 类的情况下,将对象的功能加以扩展。

4、装饰模式的主要优点在于可以提供比继承更多的灵活性,可以通过一种动态的 方式来扩展一个对象的功能,并通过使用不同的具体装饰类以及这些装饰类的 排列组合,可以创造出很多不同行为的组合,而且具体构件类与具体装饰类可 以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类;其主要缺 点在于使用装饰模式进行系统设计时将产生很多小对象,而且装饰模式比继承 更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需 要逐级排查,较为烦琐。

5、装饰模式适用情况包括:在不影响其他对象的情况下,以动态、透明的方式给 单个对象添加职责;需要动态地给一个对象增加功能,这些功能也可以动态地 被撤销;当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展 和维护时。

6、装饰模式可分为透明装饰模式和半透明装饰模式:在透明装饰模式中,要求客 户端完全针对抽象编程,装饰模式的透明性要求客户端程序不应该声明具体构 件类型和具体装饰类型,而应该全部声明为抽象构件类型;半透明装饰模式允 许用户在客户端声明具体装饰者类型的对象,调用在具体装饰者中新增的方法。

2.2.5 外观模式

2.2.5.1 概念

  • 外观模式(Facade),隐藏系统的复杂性,向客户端提供一个可以访问系统的接口。
    【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第4张图片
  • 外观模式中的角色:

1、门面角色:外观模式的核心。它被客户角色调用,它熟悉子系统的功能。内部根据客户角色的需求预定了几种功能的组合。

2、子系统角色:实现了子系统的功能。它对客户角色和Facade时未知的。它内部可以有系统内的相互交互,也可以由供外界调用的接口。

3、客户角色:通过调用Facede来完成要实现的功能。

  • 适用环境:

1、当要为一个复杂子系统提供一个简单接口时可以使用外观模式。该接口可以满足大多数用户的需求,而且用户也可以越过外观类直接访问子系统。

2、客户程序与多个子系统之间存在很大的依赖性。引入外观类将子系统与客户以及其他子系统解耦,可以提高子系统的独立性和可移植性。

3、在层次化结构中,可以使用外观模式定义系统中每一层的入口,层与层之间不直接产生联系,而通过外观类建立联系,降低层之间的耦合度。

2.2.5.2 外观模式代码案例:

/**
 * 电脑CPU
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class CPU {


     public void start() {
         log.info("cpu is start...");
     }

     public void shutDown() {
         log.info("CPU is shutDown...");
     }
}
/**
 * 电脑Disk
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class Disk {


     public void start() {
         log.info("Disk is start...");
     }

     public void shutDown() {
         log.info("Disk is shutDown...");
     }
}
/**
 * 电脑Memory
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class Memory {


     public void start() {
         log.info("Memory is start...");
     }

     public void shutDown() {
         log.info("Memory is shutDown...");
     }
}
/**
 * 门面类Facade
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class Computer {

    private CPU cpu;
    private Disk disk;
    private Memory memory;

    public Computer(){
        cpu = new CPU();
        memory = new Memory();
        disk = new Disk();
    }

    public void start(){
        log.info("Computer start begin");
        cpu.start();
        disk.start();
        memory.start();
        log.info("Computer start end");
    }

    public void shutDown(){
        log.info("Computer shutDown begin");
        cpu.shutDown();
        disk.shutDown();
        memory.shutDown();
        log.info("Computer shutDown end");
    }
}

2.2.5.3 外观模式总结:

1、在外观模式中,外部与一个子系统的通信必须通过一个统一的外观对象进行,为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。外观模式又称为门面模式。

2、外观模式包含两个角色:1、外观角色是在客户端直接调用的角色,在外观角色中可以知道相关的(一个或者多个)子系统的功能和责任,它将所有从客户端发来的请求委派到相应的子系统去,传递给相应的子系统对象处理;2、在软件系统中可以同时有一个或者多个子系统角色,每一个子系统可以不是一个单独的类,而是一个类的集合,它实现子系统的功能。

3、外观模式要求一个子系统的外部与其内部的通信通过一个统一的外观对象进行,外观类将客户端与子系统的内部复杂性分隔开,使得客户端只需要与外观对象打交道,而不需要与子系统内部的很多对象打交道。

4、外观模式主要优点在于对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易,它实现了子系统与客户之间的松耦合关系,并降低了大型软件系统中的编译依赖性,简化了系统在不同平台之间的移植过程;其缺点在于不能很好地限制客户使用子系统类,而且在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

5、外观模式适用情况包括:要为一个复杂子系统提供一个简单接口;客户程序与多个子系统之间存在很大的依赖性;在层次化结构中,需要定义系统中每一层的入口,使得层与层之间不直接产生联系。

2.2.6 享元模式

2.2.6.1 概念

  • 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式。
  • 常见的池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用。
  • 享元模式包含如下角色:

Flyweight: 抽象享元类
ConcreteFlyweight: 具体享元类
UnsharedConcreteFlyweight: 非共享具体享元类
FlyweightFactory: 享元工厂类

【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第5张图片

  • 适用环境:

1、一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费。

2、对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

3、使用享元模式需要维护一个存储享元对象的享元池,而这需要耗费资源,因此,应当在多次重复使用享元对象时才值得使用享元模式。

2.2.6.2 享元模式代码案例:

/**
 *
 *  享元接口:工具
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
public interface Tool {
    /**
     * 功能
     */
    void function();
}
/**
 * ToolImpl
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class ToolImpl implements Tool {

    private String name;

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

    @Override
    public void function() {

        log.info("这是{}",name);
    }
}
/**
 * 工具箱
 *
 * @author wangjie
 * @version V1.0
 * @date 2020/4/7
 */
@Slf4j
public class Tools {

    private static ConcurrentHashMap<String, Tool> tools = new ConcurrentHashMap<>();

    public Tool getCaeKey(String key){

        if(!tools.contains(key)){
            log.info("工具箱里没有这个,得先买一个");
            tools.put(key,new ToolImpl(key));
            log.info("买好了,放进工具箱");
        }

        return tools.get(key);
    }
}

2.2.6.3 享元模式总结:

1、享元模式运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。

2、享元模式包含四个角色:1、抽象享元类声明一个接口,通过它可以接受并作用于外部状态;2、具体享元类实现了抽象享元接口,其实例称为享元对象;3、非共享具体享元是不能被共享的抽象享元类的子类;4、享元工厂类用于创建并管理享元对象,它针对抽象享元类编程,将各种类型的具体享元对象存储在一个享元池中。

3、享元模式以共享的方式高效地支持大量的细粒度对象,享元对象能做到共享的关键是区分内部状态和外部状态。其中内部状态是存储在享元对象内部并且不会随环境改变而改变的状态,因此内部状态可以共享;外部状态是随环境改变而改变的、不可以共享的状态。

4、享元模式主要优点在于它可以极大减少内存中对象的数量,使得相同对象或相似对象在内存中只保存一份;其缺点是使得系统更加复杂,并且需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。

5、享元模式适用情况包括:一个系统有大量相同或者相似的对象,由于这类对象的大量使用,造成内存的大量耗费;对象的大部分状态都可以外部化,可以将这些外部状态传入对象中;多次重复使用享元对象。

2.2.7 代理模式

2.2.7.1 概念

  • 代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。
  • 代理模式包含如下角色:

Subject: 抽象主题角色
Proxy: 代理主题角色
RealSubject: 真实主题角色

【设计模式】设计模式之结构型模式(适配器、桥接、组合、装饰、外观、享元、代理)_第6张图片

  • 适用环境:

1、远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。

2、虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。

3、Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。

4、保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

5、缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。

6、防火墙(Firewall)代理:保护目标不让恶意用户接近。

7、同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。

8、智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

2.2.7.2 代理模式代码案例:

public interface Movie {
	void play();
}

public class RealMovie implements Movie {

	@Override
	public void play() {
		// TODO Auto-generated method stub
		System.out.println("您正在观看电影 《肖申克的救赎》");
	}

}
public class Cinema implements Movie {
	
	RealMovie movie;
	
	public Cinema(RealMovie movie) {
		super();
		this.movie = movie;
	}


	@Override
	public void play() {
		
		guanggao(true);
		
		movie.play();
		
		guanggao(false);
	}
	
	public void guanggao(boolean isStart){
		if ( isStart ) {
			System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
		} else {
			System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
		}
	}

}
public class ProxyTest {

	public static void main(String[] args) {
		
		RealMovie realmovie = new RealMovie();
		
		Movie movie = new Cinema(realmovie);
		
		movie.play();

	}

}

  • 上面是静态代理,还有动态代理可以自行了解。

2.2.6.3 代理模式总结:

  • 在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
  • 代理模式包含三个角色:1、抽象主题角色声明了真实主题和代理主题的共同接口;2、代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;3、真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。
  • 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。

你可能感兴趣的:(设计模式,java)