23种设计模式学习笔记(4)

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 23种设计模式学习笔记(4),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

23种设计模式学习笔记(1)
https://blog.csdn.net/qq_51495235/article/details/115358623

23种设计模式学习笔记(2)
https://blog.csdn.net/qq_51495235/article/details/115358846

23种设计模式学习笔记(3)
https://blog.csdn.net/qq_51495235/article/details/115359128

13,外观模式

13.1 外观模式的定义和特点

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

外观(Facade)模式是“迪米特法则”的典型应用,它有以下主要优点。

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

外观(Facade)模式的主要缺点如下。

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

13.2 外观模式的结构与实现

13.2.1 外观模式的结构

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

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

13.2.2 代码实现

关系类图

23种设计模式学习笔记(4)

AirCondition

package com.zhuang.facade;

/**
 * @Classname AirCondition
 * @Description 空调类
 * @Date 2021/3/24 19:23
 * @Created by dell
 */

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

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

Light

package com.zhuang.facade;

/**
 * @Classname Light
 * @Description 电灯类
 * @Date 2021/3/24 19:23
 * @Created by dell
 */

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

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

TV

package com.zhuang.facade;

/**
 * @Classname TV
 * @Description 电视类
 * @Date 2021/3/24 19:23
 * @Created by dell
 */

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

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

SmartAppliancesFacade

package com.zhuang.facade;

/**
 * @Classname SmartAppliancesFacade
 * @Description 智能音箱类  外观类
 * @Date 2021/3/24 19:24
 * @Created by dell
 */

public class SmartAppliancesFacade {
    private Light light;
    private TV tv;
    private AirCondition airCondition;

    public SmartAppliancesFacade() {
        light = new Light();
        tv = new TV();
        airCondition = new AirCondition();
    }

    //私有打开方法 外界访问不了
    //一键打开
    private void on() {
        light.on();
        tv.on();
        airCondition.on();
    }

    //私有关闭方法 外界访问不了
    //一键关闭
    private void off() {
        light.off();
        tv.off();
        airCondition.off();
    }

    //判断方法
    public void say(String message) {
        if (message.contains("打开")) {
            on();
        } else if (message.contains("关闭")) {
            off();
        } else {
            System.out.println("你说的指令我听不懂!!!");
        }

    }
}

Client

package com.zhuang.facade;

/**
 * @Classname Client
 * @Description 外观模式测试类
 * @Date 2021/3/24 19:24
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        SmartAppliancesFacade smartAppliancesFacade = new SmartAppliancesFacade();
        smartAppliancesFacade.say("打开家电");
        System.out.println("=================");
        smartAppliancesFacade.say("关闭家电");
    }
}

23种设计模式学习笔记(4)

13.3 外观模式的应用场景

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

13.4 源码解析

使用tomcat作为web容器时,接收浏览器发送过来的请求,tomcat会将请求信息封装成ServletRequest对象,如下图①处对象。但是大家想想ServletRequest是一个接口,它还有一个子接口HttpServletRequest,而我们知道该request对象肯定是一个HttpServletRequest对象的子实现类对象,到底是哪个类的对象呢?可以通过输出request对象,我们就会发现是一个名为RequestFacade的类的对象。

23种设计模式学习笔记(4)

RequestFacade类就使用了外观模式

23种设计模式学习笔记(4)

为什么在此处使用外观模式呢?

定义 RequestFacade 类,分别实现 ServletRequest ,同时定义私有成员变量 Request ,并且方法的实现调用 Request 的实现。然后,将 RequestFacade上转为 ServletRequest 传给 servlet 的 service 方法,这样即使在 servlet 中被下转为 RequestFacade ,也不能访问私有成员变量对象中的方法。既用了 Request ,又能防止其中方法被不合理的访问。

14,享元模式

14.1 享元模式的定义和特点

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

享元模式的主要优点是:相同对象只要保存一份,这降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。

其主要缺点是:

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

14.2 享元模式的结构与实现

14.2.1 享元模式的结构

享元(Flyweight )模式中存在以下两种状态:

  1. 内部状态,即不会随着环境的改变而改变的可共享部分。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

享元模式的主要有以下角色:

  • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

14.2.1代码实现

关系类图

23种设计模式学习笔记(4)

IBox 定义不同的形状

package com.zhuang.flyweight;

/**
 * @Classname IBox
 * @Description I图形类(具体享元角色)
 * @Date 2021/3/25 15:33
 * @Created by dell
 */

public class IBox extends AbstractBox {

    @Override
    public String getShape() {
        return "I";
    }

}

LBox 定义不同的形状

package com.zhuang.flyweight;

/**
 * @Classname IBox
 * @Description L图形类(具体享元角色)
 * @Date 2021/3/25 15:33
 * @Created by dell
 */

public class LBox extends AbstractBox {

    @Override
    public String getShape() {
        return "L";
    }

}

OBox 定义不同的形状

package com.zhuang.flyweight;

/**
 * @Classname IBox
 * @Description O图形类(具体享元角色)
 * @Date 2021/3/25 15:33
 * @Created by dell
 */

public class OBox extends AbstractBox {

    @Override
    public String getShape() {
        return "O";
    }

}

BoxFactory 提供了一个工厂类(BoxFactory),用来管理享元对象(也就是AbstractBox子类对象),该工厂类对象只需要一个,所以可以使用单例模式。并给工厂类提供一个获取形状的方法

package com.zhuang.flyweight;

import java.util.HashMap;

/**
 * @Classname BoxFactory
 * @Description 工厂类 将类设计为单例模式
 * @Date 2021/3/25 15:33
 * @Created by dell
 */

public class BoxFactory {
    private HashMap<String, AbstractBox> map;

    //在构造方法中初始化
    private BoxFactory() {
        map = new HashMap<String, AbstractBox>();
        IBox iBox = new IBox();
        LBox lBox = new LBox();
        OBox oBox = new OBox();
        map.put("I", iBox);
        map.put("L", lBox);
        map.put("O", oBox);
    }

    //声明一个方法获取工厂
    public static BoxFactory getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory();
    }

    //根据图形名称获取图形对象
    public AbstractBox getShape(String name) {
        return map.get(name);
    }
}

AbstractBox 对这些形状向上抽取出AbstractBox,用来定义共性的属性和行为

package com.zhuang.flyweight;

/**
 * @Classname AbstractBox
 * @Description 抽象享元角色 抽象类
 * @Date 2021/3/25 15:32
 * @Created by dell
 */

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

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

}

23种设计模式学习笔记(4)

14.3 享元模式的应用场景

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

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

前面分析了享元模式的结构与特点,下面分析它适用的应用场景。享元模式是通过减少内存中对象的数量来节省内存空间的,所以以下几种情形适合采用享元模式。

  1. 系统中存在大量相同或相似的对象,这些对象耗费大量的内存资源。
  2. 大部分的对象可以按照内部状态进行分组,且可将不同部分外部化,这样每一个组只需保存一个内部状态。
  3. 由于享元模式需要额外维护一个保存享元的数据结构,所以应当在有足够多的享元实例时才值得使用享元模式

14.4 JDK源码解析

Integer类使用了享元模式。我们先看下面的例子:

public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;

        System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));

        Integer i3 = 128;
        Integer i4 = 128;

        System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
    }
}

结果是 true false

为什么第一个输出语句输出的是true,第二个输出语句输出的是false?通过反编译软件进行反编译,代码如下:

public class Demo {
    public static void main(String[] args) {
        Integer i1 = Integer.valueOf((int)127);
        Integer i2 Integer.valueOf((int)127);
        System.out.println((String)new StringBuilder().append((String)"i1\u548ci2\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i1 == i2)).toString());
        Integer i3 = Integer.valueOf((int)128);
        Integer i4 = Integer.valueOf((int)128);
        System.out.println((String)new StringBuilder().append((String)"i3\u548ci4\u5bf9\u8c61\u662f\u5426\u662f\u540c\u4e00\u4e2a\u5bf9\u8c61\uff1f").append((boolean)(i3 == i4)).toString());
    }
}

上面代码可以看到,直接给Integer类型的变量赋值基本数据类型数据的操作底层使用的是 valueOf() ,所以只需要看该方法即可

public final class Integer extends Number implements Comparable<Integer> {
    
	public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                }
            }
            high = h;
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);
            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
}

可以看到 Integer 默认先创建并缓存 -128 ~ 127 之间数的 Integer 对象,当调用 valueOf 时如果参数在 -128 ~ 127 之间则计算下标并从缓存中返回,否则创建一个新的 Integer 对象。

15,模板方法模式

15.1 模板方法模式的定义和特点

模板方法(Template Method)模式的定义如下:**定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。**它是一种类行为型模式。

该模式的主要优点如下。

  1. 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  2. 它在父类中提取了公共的部分代码,便于代码复用。
  3. 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下。

  1. 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。
  2. 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。
  3. 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

15.2 模板方法模式的结构与实现

15.2.1 模板方法模式的结构

模板方法(Template Method)模式包含以下主要角色:

  • 抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

    • 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:

      • 抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。

      • 具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。

      • 钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

        一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。

  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。

23种设计模式学习笔记(4)

15.2.2 代码实现

AbstractClass

package com.zhuang.template;

/**
 * @Classname AbstractClass
 * @Description 抽象类
 * @Date 2021/3/26 20:06
 * @Created by dell
 */

public abstract class AbstractClass {
    public final void work() {
        //起床
        this.wake();
        //刷牙
        this.brush();
        //吃早饭
        this.breakfast();
        //交通工具
        this.transport();
        //睡觉
        this.sleep();
    }

    //步骤一样 直接实现
    public void wake() {
        System.out.println("起床...");
    }

    //步骤一样 直接实现
    public void brush() {
        System.out.println("刷牙...");
    }

    // 步骤不一样 (一个是吃面包 一个是喝牛奶)
    public abstract void breakfast();

    // 步骤不一样 (一个是开车 一个是坐地铁)
    public abstract void transport();

    // 步骤一样 直接实现
    public void sleep() {
        System.out.println("睡觉...");
    }

}

ConcreteClass_BreakFast

package com.zhuang.template;

/**
 * @Classname ConcreteClass_BreakFast
 * @Description 具体类 早饭类 继承
 * @Date 2021/3/26 20:13
 * @Created by dell
 */

public class ConcreteClass_BreakFast extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("吃面包...");
    }

    @Override
    public void transport() {
        System.out.println("坐公交...");
    }
}

ConcreteClass_Transport

package com.zhuang.template;

/**
 * @Classname ConcreteClass_Transport
 * @Description 交通工具类 继承
 * @Date 2021/3/26 20:14
 * @Created by dell
 */

public class ConcreteClass_Transport extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("喝牛奶...");
    }

    @Override
    public void transport() {
        System.out.println("乘地铁...");
    }
}

Client

package com.zhuang.template;

/**
 * @Classname Client
 * @Description 模板方法模式 测试类
 * @Date 2021/3/26 20:16
 * @Created by dell
 */

public class Client {
    public static void main(String[] args) {
        //吃面包 坐公交
        System.out.println("周一");
        AbstractClass breakFast = new ConcreteClass_BreakFast();
        breakFast.work();

        System.out.println("========================");
        System.out.println("周五");
        AbstractClass transport = new ConcreteClass_Transport();
        transport.work();
    }
}

23种设计模式学习笔记(4)

钩子方法

AbstractClass

package com.zhuang.template.hook_method;

/**
 * @Classname AbstractClass
 * @Description 抽象类
 * @Date 2021/3/26 20:06
 * @Created by dell
 */

public abstract class AbstractClass {
    public final void work() {
        //起床
        this.wake();
        //刷牙
        this.brush();
        //吃早饭
        this.breakfast();
        //交通工具
        if (isSunday()) {
            this.transport();
        }
        //睡觉
        this.sleep();
    }

    //步骤一样 直接实现
    public void wake() {
        System.out.println("起床...");
    }

    //步骤一样 直接实现
    public void brush() {
        System.out.println("刷牙...");
    }

    // 步骤不一样 (一个是吃面包 一个是喝牛奶)
    public abstract void breakfast();

    // 步骤不一样 (一个是开车 一个是坐地铁)
    public abstract void transport();

    // 步骤一样 直接实现
    public void sleep() {
        System.out.println("睡觉...");
    }

    //钩子方法 是否为周末 周末不用交通工具
    boolean isSunday() {
        return false;
    }

}

ConcreteClass_BreakFast

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_BreakFast
 * @Description 具体类 早饭类 继承
 * @Date 2021/3/26 20:13
 * @Created by dell
 */

public class ConcreteClass_BreakFast extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("吃面包...");
    }

    @Override
    public void transport() {
        System.out.println("坐公交...");
    }
}

ConcreteClass_Transport

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_Transport
 * @Description 交通工具类 继承
 * @Date 2021/3/26 20:14
 * @Created by dell
 */

public class ConcreteClass_Transport extends AbstractClass {
    @Override
    public void breakfast() {
        System.out.println("喝牛奶...");
    }

    @Override
    public void transport() {
        System.out.println("乘地铁...");
    }
}

ConcreteClass_Sunday

package com.zhuang.template.hook_method;

/**
 * @Classname ConcreteClass_Sunday
 * @Description 周末 不用上班 空实现交通方法
 * @Date 2021/3/26 20:28
 * @Created by dell
 */

public class ConcreteClass_Sunday extends AbstractClass{

    @Override
    public void breakfast() {
        System.out.println("吃面包或者喝牛奶...");
    }

    @Override
    public void transport() {
        //空实现
    }

    @Override
    boolean isSunday() {
        System.out.println("今天周末,休息...");
        return true;
    }
}

Client

package com.zhuang.template.hook_method;

/**
 * @Classname Client
 * @Description 模板方法 测试钩子方法
 * @Date 2021/3/26 20:26
 * @Created by dell
 */

public class Client {

    public static void main(String[] args) {
        AbstractClass sunday = new ConcreteClass_Sunday();
        sunday.work();
    }
}

15.3 模板方法模式的应用场景

  1. 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
  2. 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  3. 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。

15.4 JDK源码解析

InputStream类就使用了模板方法模式。在InputStream类中定义了多个 read() 方法,如下:

public abstract class InputStream implements Closeable {
    //抽象方法,要求子类必须重写
    public abstract int read() throws IOException;

    public int read(byte b[]) throws IOException {
        return read(b, 0, b.length);
    }

    public int read(byte b[], int off, int len) throws IOException {
        if (b == null) {
            throw new NullPointerException();
        } else if (off < 0 || len < 0 || len > b.length - off) {
            throw new IndexOutOfBoundsException();
        } else if (len == 0) {
            return 0;
        }

        int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
        if (c == -1) {
            return -1;
        }
        b[off] = (byte)c;

        int i = 1;
        try {
            for (; i < len ; i++) {
                c = read();
                if (c == -1) {
                    break;
                }
                b[off + i] = (byte)c;
            }
        } catch (IOException ee) {
        }
        return i;
    }
}

从上面代码可以看到,无参的 read() 方法是抽象方法,要求子类必须实现。而 read(byte b[]) 方法调用了 read(byte b[], int off, int len) 方法,所以在此处重点看的方法是带三个参数的方法。

在该方法中第18行、27行,可以看到调用了无参的抽象的 read() 方法。

总结如下: 在InputStream父类中已经定义好了读取一个字节数组数据的方法是每次读取一个字节,并将其存储到数组的第一个索引位置,读取len个字节数据。具体如何读取一个字节数据呢?由子类实现

16,组合模式

16.1 组合模式的定义和特点

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

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

23种设计模式学习笔记(4)

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

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

组合模式的主要优点有:

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

其主要缺点是:

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

16.2 组合模式的结构与实现

16.2.1 组合模式的结构

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

16.2.2 代码实现

如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。

23种设计模式学习笔记(4)

23种设计模式学习笔记(4)

MenuComponent MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。

package com.zhuang.combination;

/**
 * @Classname MenuComponent
 * @Description 菜单组件 不管菜单还是菜单项,都应该继承该类  抽象类
 * @Date 2021/3/24 17:02
 * @Created by dell
 */

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 i) {
        throw new UnsupportedOperationException();
    }

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

    //打印方法
    public void print() {
        throw new UnsupportedOperationException();
    }
}

Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能

package com.zhuang.combination;

import java.util.ArrayList;
import java.util.List;

/**
 * @Classname Menu
 * @Description 菜单类 继承菜单组件
 * @Date 2021/3/24 17:05
 * @Created by dell
 */

public class Menu extends MenuComponent {

    private List<MenuComponent> menuComponentList;

    public Menu(String name, int level) {
        this.name = name;
        this.level = level;
        menuComponentList = new ArrayList<MenuComponent>();
    }

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

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

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

    @Override
    public void print() {
        for (int i = 0; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}

MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。

package com.zhuang.combination;

/**
 * @Classname MenuItem
 * @Description 菜单选项 继承菜单组件
 * @Date 2021/3/24 17:10
 * @Created by dell
 */

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);
    }
}

23种设计模式学习笔记(4)

在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。

  • 透明组合模式

    透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 addremovegetChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

    透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

  • 安全组合模式

    在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

23种设计模式学习笔记(4)

16.3 组合模式的应用场景

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/140738.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!