学习笔记-Java设计模式-结构型模式1

Java设计原则&&模式学习笔记

说明

近期扫地生决定整合一下年初学习的设计模式,一来用于复习巩固,二来也希望可以把自己的整合与有需要的同学共勉。

扫地生在学习的过程中主要参考的是公众号“一角钱技术”的相关推文,同时也参考了下列几篇文章。对这些作者扫地生表示衷心的感谢。

参考文章1-Java设计原则

参考文章2-Java设计模式总结

参考文章3-23种设计模式速记

4、结构型模式

4.1 结构型模式1——适配器模式(Adapter)

速记关键词:转换接口

简介

定义:将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

分类:类的适配器模式&&对象的适配器模式

在我们的应用程序中我们可能需要将两个不同接口的类来进行通信,在不修改这两个的前提下我们可能会需要某个中间件来完成这个衔接的过程。这个中间件就是适配器。所谓适配器模式就是将一个类的接口,转换成客户期望的另一个接口。它可以让原本两个不兼容的接口能够无缝完成对接。作为中间件的适配器将目标类和适配者解耦,增加了类的透明性和可复用性。

对象适配器

image-20211107201517784

  • 冲突:Target希望调用Operation方法,而Adaptee并没有(这就是所谓的不兼容)
  • 解决方法:为了使Target能够调用Adaptee类中的Specificoperation方法,提供一个中间环节Adapter(包装了一个Adapter实例),把Adaptee的API与Target的API衔接起来。其中Adapter与Adaptee之间是委派关系。
package top.saodisheng.designpattern.adapterpattern.v1;

/**
 * 1. 创建Target接口
 */
interface Target {
    // 创建源类Adaptee没有的方法
    void operation();
}

/**
 * 2. 创建源类
 */
class Adaptee {
    public void SpecificOperation(){
        System.out.println("我是源类");
    }
}

/**
 * 3. 创建适配器类,不是用继承,而是委派
 */
class Adapter implements Target{
    /** 直接关联源类 **/
    private Adaptee adaptee;

    /**
     * 通过构造函数传入具体需要适配的源类对象
     * @param adaptee
     */
    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    @Override
    public void operation() {
        // 使用委托的方式完成特殊功能
        this.adaptee.SpecificOperation();
        System.out.println("我是适配器类");
    }
}

/**
 * description:
 * 4.定义具体使用目标类,并通过Adapter类调用所需的方法从而实现目标
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class AdapterPattern {
    public static void main(String[] args) {
        Target mAdapter = new Adapter(new Adaptee());
        mAdapter.operation();
    }
}

类适配器(采用继承类的方式实现)

image-20211107201603025

  • 冲突所在:Target想要调用Operation方法,但原有的类Adaptee并不具备(不兼容之处)
  • 解决方法:为了使Target能够调用Adaptee类里的SpecificOperation方法,因此提供一个中间环节Adapter类(继承了Adaptee同时实现Target接口),把Adaptee的API与Target的API衔接起来(适配)
package top.saodisheng.designpattern.adapterpattern.v2;

/**
 * 1. 创建Target接口
 */
interface Target {
    // 创建源类Adaptee没有的方法
    void operation();
}

/**
 * 2. 创建源类
 */
class Adaptee {
    public void SpecificOperation(){
        System.out.println("我是源类");
    }
}

/**
 * 3. 创建适配器类,继承源类的同时实现目标接口
 */
class Adapter extends Adaptee implements Target{
    /**
     * 目标接口要求调用operation()这个方法名,但源类Adaptee没有对应的方法
     * 因此适配器补充上这个方法名,但实际上Operation()调用的是源类Adaptee中的SpecificOperation()方法的内容
     * 所以适配器只是将SpecificOperation()方法做了一层封装,封装成目标Target可以调用的Operation()方法而已
     */
    @Override
    public void operation() {
        this.SpecificOperation();
        System.out.println("我是适配器类");
    }
}

/**
 * description:
 * 4.定义具体使用目标类,并通过Adapter类调用所需的方法从而实现目标
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class AdapterPattern {
    public static void main(String[] args) {
        Target mAdapter = new Adapter();
        mAdapter.operation();
    }
}

两种适配器比较

  • 对象适配器:使用组合的方式,不仅能适配一个被适配者的类,还可以适配它的任何一个子类;
  • 类适配器:只能适配一个特定的类,但是它不需要重新实现整个被适配者的功能,而且它还可以重写被适配者的行为;
  • 对象适配器:使用的是组合而不是继承,通过多写几行代码把事情委托给了被适配者,这样很灵活;
  • 类适配器:需要一个适配器和一个被适配器,只需要一个类就行;
  • 对象适配器:对适配器添加的任何行为对被适配者和它的子类都其作用;...

解决的问题

从模式的定义中,可以看到适配器模式就是用来转换接口、解决不兼容问题的。在我们日常生活中,最常用的适配器就是手机从电器了,也就是电源适配器,它把家用交流强电转换为手机用的直流弱电。其中交流电就是被适配者,充电器是适配器,手机是用典客户。

image-20211107103623190

模式组成


组成(角色) 作用
客户(Client) 只能调用目标接口功能,不能直接使用被适配器,但可以通过适配器的接口转换间接使用被适配器。
目标接口(Target) 客户看到的接口,适配器必须实现该接口才能被客户使用。
适配器(Adapter) 适配器把适配者接口转换成目标接口,提供给客户使用。
被适配者(Adaptee) 被适配者接口与目标接口不兼容,需要适配器转换成目标接口子类,才能被客户使用。

实例概况

  • 背景,楼上老刘买了一台进口的电视机
  • 冲突:进口电视机要求电压(110V)与国内标准输出电压(220V)不兼容
  • 解决方案:设置一个适配器将插头输出的220V转变成110V

即适配器模式中的类的适配器模式

package top.saodisheng.designpattern.adapterpattern.v3;

/**
 * 1. 创建Target接口
 */
interface Target {
    /** 将220V转换输出110V(原有插头(Adaptee)没有的) **/
    void convert_110v();
}

/**
 * 2. 创建源类
 */
class PowerPort220V_Adaptee {
    /** 原有插头只能输出220V **/
    public void output_220v(){
        System.out.println("原本只能输出220V");
    }
}

/**
 * 3. 创建适配器类,继承源类的同时实现目标接口
 */
class Adapter220V extends PowerPort220V_Adaptee implements Target{
    /**
     * 期待的插头要求调用convert_110v(),但原有插头没有
     * 因此适配器补充上这个方法名
     * 但实际上convert_110v()只是调用原有插头的output_220v()方法的内容
     * 所以适配器只是将output_220v()作了一层封装,封装成Target可以调用的convert_110v()而已
     */
    @Override
    public void convert_110v() {
        this.output_220v();
        System.out.println("经过适配器适配,现在可以输出110v");
    }
}

/**
 * 4 定义具体使用目标类,并通过Adapter类调用所需的方法从而实现目标(不需要通过原有插头)
 */
class ImportMachine{
    public void work() {
        System.out.println("进口电视正常运行");
    }
}

/**
 * description:
 * 通过Adapter类从而调用所需要的方法
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class AdapterPattern {
    public static void main(String[] args) {
        Target mAdapter220V = new Adapter220V();
        ImportMachine importMachine = new ImportMachine();

        // 用户拿着进口电视插上适配器(调用Convert_110v()方法)
        // 再将适配器插上原有插头(Convert_110v()方法内部调用Output_220v()方法输出220V)
        // 适配器只是个外壳,对外提供110V,但本质还是220V进行供电
        mAdapter220V.convert_110v();
        importMachine.work();
    }
}

image-20211107105517444

优缺点

优点:

  • 转换接口,适配器让不兼容的接口变成兼容。
  • 让客户和实现的接口解耦。有了适配器,客户端每次调用不兼容的接口时,不用修改自己的代码,只要调用适合的适配器就可以了。
  • 使用了对象组合设计原则。以组合的方式包装被适配者,被适配者的任何子类都可以搭配着同一个适配器使用。
  • 体现了“开闭原则”,适配器模式把客户和接口绑定起来,而不是和具体的实现绑定,我们可以使用多个适配器来转换多个后台类,也可以很容易地增加新的适配器。

缺点:

  • 每个被适配者都需要一个适配器,当适配器过多时会增加系统的复杂度,降低运行时的性能。
  • 实现一个适配器可能需要下一番功夫,增加开发难度。

应用场景

  1. 当希望使用某些现有类,但其接口与已有的其他代码不兼容时,使用适配器类。
  2. 当希望重用结果现有子类,这些子类缺少一些不能添加到超类中的公共功能是,使用该模式。

源码中的应用

#JDK
java.util.Arrays#asList()
java.util.Collections#list()
java.util.Collections#enumeration()
java.io.InputStreamReader(InputStream) (returns a Reader)
java.io.OutputStreamWriter(OutputStream) (returns a Writer)
java.util.collections#enumeration(),从Iterator到Enumeration的适配。

#Spring
org.springframework.context.event.GenericApplicationListenerAdapter

4.2 结构型模式2——组合模式(Composite)

速记关键词:树形目录结构

简介

定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。它定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。

虽然组合模式能够清晰地定义分层次的复杂对象,也使得增加新构件也更容易,但是这样就导致了系统的设计变得更加抽象,如果系统的业务规则比较复杂的话,使用组合模式就有一定的挑战了。

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

图片

透明模式:在这种方式下,抽象构件声明了所有子类中的全部方法。所以客户端无需区别树叶对象和树枝对象,对客户端来说是透明的。但其缺点是:树叶构没有抽象构件中的一些方法,但却要实现他们(空实现或抛异常),这样会带来一些安全性问题。

图片

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

解决的问题

它在我们树形结构的问题中就,模糊了简单元素和复杂元素的概念,客户端程序可以像处理简单元素一样处理复杂元素,从而使得客户程序与复杂的内部结构解耦。

模式组成

组成(角色) 作用
抽象构件(Component)角色 它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模型中抽象构件还声明访问和管理子类的接口;在安全模式的组合模式中不声明访问和管理子类的接口,管理工作有树枝构件完成。
树叶构件(Leaf)角色 是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
树枝构件(Composite)角色 是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含Add()、Remove()、GetChild()等方法。

模式实现

假设需要访问的集合 c0 = {leaf1, {leaf2, leaf3}}中的元素,其对应的树状图如下:

image-20211107153855664

package top.saodisheng.designpattern.composite.v1;

import java.util.ArrayList;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class CompositePattern {
    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. 抽象构件接口
 */
interface Component {
    void add(Component e);
    void remove(Component e);
    Component getChild(int i);
    void operation();
}

/**
 * 2. 树叶构件
 */
class Leaf implements Component {
    private String name;

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

    }
    @Override
    public void remove(Component e) {

    }
    @Override
    public Component getChild(int i) {
        return null;
    }
    @Override
    public void operation() {
        System.out.println("树叶" + name + ":被访问。");
    }
}

/**
 * 3. 树枝构件
 */
class Composite implements Component{
    private ArrayList<Component> children = new ArrayList<Component>();

    @Override
    public void add(Component e) {
        children.add(e);
    }
    @Override
    public void remove(Component e) {
        children.remove(e);
    }
    @Override
    public Component getChild(int i) {
        return children.get(i);
    }
    @Override
    public void operation() {
        for (Object obj : children) {
            ((Component)obj).operation();
        }
    }
}

image-20211107154334657

实例概况

用组合模式实现当用户在商店购物后,显示其所选商品信息,并计算所选商品总价的功能。

说明:楼上老刘到光之国游玩,在奥特超市里购物用1个红色小袋子装了2包光之国特产(单价约合7.9元RMB)、1张光之国地图(单价约合9.9RMB);用一个白色小袋子装了2包香菇(单价约合68RMB)和3包红茶(单价约合180RMB);用一个中袋子装了前面的红色小袋子和1个雨花石吊坠(单价约合380RMB);用一个大袋子装了前面的中袋子、白色小袋子和1双奥特战靴(单价约合198RMB)、

最后大袋子中的内容如下,现在要求编写程序用于显示楼上老刘放在大袋子中的所有商品信息并计算要支付的总价。

image-20211107155526999

使用步骤

可按安全组合模式设计,其结构图如下:

图片

package top.saodisheng.designpattern.composite.v2;

import java.util.ArrayList;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class CompositePattern {
    public static void main(String[] args) {
        float sum = 0;
        Bag bigBag, mediumBag, smallRedBag, smallWhiteBag;
        Good sp;
        bigBag = new Bag("大袋子");
        mediumBag = new Bag("中袋子");
        smallRedBag = new Bag("红色小袋子");
        smallWhiteBag = new Bag("白色小袋子");

        sp = new Good("光之国特产", 2, 7.9f);
        smallRedBag.add(sp);
        sp = new Good("光之国地图", 1, 9.9f);
        smallRedBag.add(sp);

        sp = new Good("香菇", 2, 68);
        smallWhiteBag.add(sp);
        sp = new Good("红茶", 3, 180);
        smallWhiteBag.add(sp);
        sp = new Good("雨花石吊坠", 1, 380);
        mediumBag.add(sp);
        mediumBag.add(smallRedBag);
        sp = new Good("奥特战靴", 1, 198);
        bigBag.add(sp);
        bigBag.add(smallWhiteBag);
        bigBag.add(mediumBag);

        System.out.println("楼上老刘选购的商品有:");
        bigBag.show();
        sum = bigBag.calculation();
        System.out.println("要支付的总价是:" + sum + "元");
    }
}

/**
 * 1. 抽象构件接口(Component)角色:物品
 */
interface Articles {
    /** 计算 **/
    float calculation();
    /** 显示信息 **/
    void show();
}

/**
 * 2. 定义树叶构件(Leaf)角色:商品
 */
class Good implements Articles {
    /** 名字 **/
    private String name;
    /** 数量 **/
    private int quantity;
    /** 单价 **/
    private float unitPrice;

    public Good(String name, int quantity, float unitPrice) {
        this.name = name;
        this.quantity = quantity;
        this.unitPrice = unitPrice;
    }


    @Override
    public float calculation() {
        return quantity * unitPrice;
    }

    @Override
    public void show() {
        System.out.println(name + "(数量" + quantity + ", 单价:" + unitPrice + "元)");
    }
}

/**
 * 3. 定义树枝构件(Composite)角色:袋子
 */
class Bag implements Articles {
    /** 名字 **/
    private String name;
    private ArrayList<Articles> bags = new ArrayList<Articles>();

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

    public void add(Articles c) {
        bags.add(c);
    }

    public Articles getChild(int i) {
        return bags.get(i);
    }


    @Override
    public float calculation() {
        float sum = 0;
        for (Object obj : bags) {
            sum += ((Articles) obj).calculation();
        }
        return sum;
    }

    @Override
    public void show() {
        for (Object obj : bags) {
            ((Articles) obj).show();
        }
    }
}

image-20211107161646604

优缺点

优点:

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

缺点

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

应用场景

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

部分、整体场景,如树形菜单、文件、文件夹管理

源码中的应用

java.awt中的组合模式
java集合中的组合模式
Mybatis SqlNode中的组合模式

4.3 结构型模式3——装饰器模式(Decorator)

速记关键词:动态附加职责

简介

定义:在不改变原有对象的基础上,将功能附加到对象上。动态地给对象添加新的功能。

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

image-20211107162305482

解决的问题

扩展一个类的功能或给一个类添加附加职责。

模式组成

组成(角色) 作用
抽象构件(Component) 给出一个抽象接口,以规范准备接受附加责任的对象
具体构件(ConcreteComponent) 定义一个将接收附加责任的类
装饰角色(Decorator) 持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口
具体装饰角色(ConcreteDecorator) 负责给构件对象贴上“附加的责任”

实例说明

  • b背景:楼上老刘准备开发一款相机软件
  • 冲突:调研市场发现有的用户需要自动添加美颜,有的需要自动添加滤镜
  • 解决方案:通过使用装饰器在相机的基础上,根据不同的用户添加相应的功能
package top.saodisheng.designpattern.decorator;

/**
 * description:
 *
 * @author 扫地生_saodisheng
 * @date 2021-02-05
 */
public class DecoratorPattern {
    public static void main(String[] args) {
        // 添加美颜
        Component componentA = new ConcreteDecoratorA(new ConcreteComponent());
        componentA.operation();
        System.out.println();

        // 添加滤镜
        Component componentB = new ConcreteDecoratorB(new ConcreteComponent());
        componentB.operation();
        System.out.println();

        // 添加美颜和滤镜
        Component component = new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent()));
        component.operation();
    }
}

/**
 * 1. 定义原始抽象构件
 */
abstract class Component {
    /**
     * 抽象方法
     */
    public abstract void operation();
}

/**
 * 2. 定义具体的构件,实现相机的基本功能
 * ConcreteComponent是最核心,最原始、最基本的接口或抽象类的实现,是我们要装饰的对象。
 */
class ConcreteComponent extends Component {

    @Override
    public void operation() {
        System.out.println("相机拍照功能!");
    }
}

/**
 * 3. 定义装饰者抽象类,聚合原始抽象接口
 */
abstract class Decorator extends Component {
    private Component component = null;

    /**
     * 通过构造函数传入被修饰者
     * @param component
     */
    public Decorator(Component component) {
        this.component = component;
    }

    /**
     * 委托给被修饰者执行
     */
    @Override
    public void operation() {
        this.component.operation();
    }
}

/**
 * 4. 具体修饰类,添加具体的功能
 */
class ConcreteDecoratorA extends Decorator {
    /**
     * 定义被修饰者
     * @param component
     */
    public ConcreteDecoratorA(Component component) {
        super(component);
    }

    /**
     * 定义自己的修饰方法
     */
    private void decorator() {
        System.out.println("新增美颜功能");
    }
    @Override
    public void operation() {
        this.decorator();
        super.operation();
    }
}
class ConcreteDecoratorB extends Decorator {
    /**
     * 定义被修饰者
     * @param component
     */
    public ConcreteDecoratorB(Component component) {
        super(component);
    }

    /**
     * 定义自己的修饰方法
     */
    private void decorator() {
        System.out.println("新增滤镜功能");
    }
    @Override
    public void operation() {
        this.decorator();
        super.operation();
    }
}

image-20211107163546907

优缺点

优点:

  • 不改变原有对象的情况下给一个对象扩展功能
  • 使用不同的组合可以实现不同的效果
  • 符合开闭原则

缺点:

  • 多层的装饰会使系统比较复杂

即一个实现类的功能若多个装饰类进行装饰,则会增加该实现类的耦合度

应用场景

扩展一个类的功能或给一个类添加附加职责

源码中的应用

# JDK
FilterInputStream

#Servlet Api:
javax.servlet.http.HttpServletRequestWrapper
javax.servlet.http.HttpServletResponseWrapper
posted @ 2021-11-07 20:27  技术扫地生—楼上老刘  阅读(72)  评论(0编辑  收藏  举报