设计模式07-享元模式与组合模式详解

1.7.设计模式07-享元模式与组合模式详解

1.7.1.享元模式详解

 时长:1h12min

学习目标:

》掌握享元模式与组合模式的应用场景

》 了解享元模式的内部状态和外部状态

》掌握组合模式的透明写法和安全写法

》享元模式和组合模式的优缺点

7.1.享元模式的基本定义

定义

  Flyweight Pattern,享元模式。又称轻量级模式,是对象池的一种实现,类似于线程池,线程池可以避免不停

地创建和销毁多个对象,消耗性能。通过减少对象数量从而改善应用所需的对象结构的方式。

宗旨:

  共享细粒度对象,将多个对同一对象的访问集中起来。来达到资源的重复利用。

  属于结构型模式。   

享元模式在生产中的体现:

房产中介:各类房源共享,全国社保联网。

7.1.1.应用场景

常常应用于系统底层的开发,以便解决系统性能问题。

系统有大量相似对象,需要缓冲池的场景。

7.2.享元模式的code实现

7.2.1.通用写法
1.顶层享元接口定义

一般需要传入外部状态

package com.wf.flyweight.general;

/**
 * @ClassName IFlyweight
 * @Description 享元角色,顶层接口
 * @Author wf
 * @Date 2020/5/25 10:07
 * @Version 1.0
 */
public interface IFlyweight {
    void operation(String extrinsicState);
}
2.定义具体的享元实现
package com.wf.flyweight.general;

/**
 * @ClassName ConcreteFlyweight
 * @Description 享元实现,因为该类实例需要反复使用,所以,使用工厂类来创建实例
 * @Author wf
 * @Date 2020/5/25 10:10
 * @Version 1.0
 */
public class ConcreteFlyweight implements IFlyweight {
    private String intrinsicState;

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

    @Override
    public void operation(String extrinsicState) {
        System.out.println("Object address:"+System.identityHashCode(this));
        System.out.println("IntrinsicState:"+this.intrinsicState);
        System.out.println("ExtrinsicState:"+extrinsicState);
    }
}
3.享元工厂
package com.wf.flyweight.general;

import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName FlyweightFactory
 * @Description 享元工厂,因为需要把实例添加到缓存,会创建容器
 * @Author wf
 * @Date 2020/5/25 10:16
 * @Version 1.0
 */
public class FlyweightFactory {
    private static Map<String,IFlyweight> pool = new HashMap<String,IFlyweight>();

    //因为内部状态具备不变性,因此,作为缓存key
    public static IFlyweight getFlyweight(String intrinsicState){
        if(!pool.containsKey(intrinsicState)){
            IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
            pool.put(intrinsicState, flyweight);
        }
        return pool.get(intrinsicState);
    }
}

注意:

  享元对象是多例的

4.系统类图

7.2.2.享元模式的实际使用

  在外地工作的同学,每年过春节回家,为了购买火车票,都需要提前一个月进行抢票。如果人工抢票,一般官方放票一两小时,

车票就会抢光了,这时出现很多刷票软件

刷新软件的实现原理:

  会定时检查12306的余票数量【通过sdk或爬虫】,一旦发现有余票,就会把它放到共享池里面,就可以让大家一起来抢票了。

  在抢票之前,需要先缓存抢票人的个人信息,出发站,目的地,如果要求匹配,就立马下单,就不需要重复填写信息。就能加快抢票

速度,提高效率。

  下面用代码来模拟抢票的过程。

 7.2.2.1.创建顶层享元抽象接口
ackage com.wf.flyweight.ticket;

/**
 * @ClassName ITicket
 * @Description 火车票,享元接口
 * @Author wf
 * @Date 2020/5/25 10:45
 * @Version 1.0
 */
public interface ITicket {
    //根据席别区分车票
    void showInfo(String bunk);
}
7.2.2.2.享元具体实现
package com.wf.flyweight.ticket;

import java.util.Random;

/**
 * @ClassName TrainTicket
 * @Description 火车票,具体享元实现
 * @Author wf
 * @Date 2020/5/25 10:47
 * @Version 1.0
 */
public class TrainTicket implements ITicket {
    //列车出发地
    private String from;
    //目的地
    private String to;
    //价格由后台设定,用户无需填写
    private int price;

    public TrainTicket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showInfo(String bunk) {
        this.price = new Random().nextInt(500);
        System.out.println(from+"->"+to+":"+ bunk + ",价格是:"+this.price);

    }
}
7.2.2.3.享元类创建工厂
package com.wf.flyweight.ticket;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ClassName TicketFactory
 * @Description 车票创建工厂
 * @Author wf
 * @Date 2020/5/25 10:55
 * @Version 1.0
 */
public class TicketFactory {
    private static Map<String,ITicket> pool = new ConcurrentHashMap<String,ITicket>();
    public static ITicket queryTicket(String from, String to){
        String key = from +"->"+ to;
        if(!pool.containsKey(key)){
            ITicket ticket = new TrainTicket(from,to);
            System.out.println("首次查询,创建对象:"+key);
            pool.put(key,ticket);
        }

        return pool.get(key);
    }
}
7.2.2.4.客户调用
package com.wf.flyweight.ticket;

/**
 * @ClassName Test
 * @Description 客户端调用
 * @Author wf
 * @Date 2020/5/26 16:23
 * @Version 1.0
 */
public class Test {
    public static void main(String[] args) {
        ITicket ticket = TicketFactory.queryTicket("杭州","遵义");
        ticket.showInfo("硬座");
    }

}

测试结果如下:

 

类图如下所示:

说明:

  享元模式,看起来和注册工单例是很像的。然而享元模式的重点,在于系统结构上,而不关心对象创建,是不是单例的。

7.2.3.享元模式应用之数据库连接池
7.2.3.1.顶层享元抽象接口

  这个接口已经存在了,可以直接使用jdk提供的Connection接口。

7.2.3.1.享元工厂
package com.wf.flyweight.pool;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Vector;

/**
 * @ClassName ConnectionPool
 * @Description TODO
 * @Author wf
 * @Date 2020/5/26 16:54
 * @Version 1.0
 */
public class ConnectionPool {
    private Vector<Connection> pool;
    private int poolSize = 100;
    private String url = "jdbc:mysql://localhost:3306/test";
    private String username = "root";
    private String password = "root";
    private String driverClassName = "com.mysql.jdbc.Driver";

    public ConnectionPool() {
        this.pool = new Vector<Connection>(poolSize);
        try{
            Class.forName(driverClassName);
            for(int i=0;i< poolSize;i++) {
                Connection conn = DriverManager.getConnection(url, username, password);
                pool.add(conn);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public synchronized Connection getConnection(){
        if(poolSize > 0){
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        }
        return null;
    }

    public synchronized void release(Connection conn){
        pool.add(conn);
    }
}
 7.2.3.2.测试类
package com.wf.flyweight.pool;


import java.sql.Connection;

/**
 * @ClassName Test
 * @Description 测试类
 * @Author wf
 * @Date 2020/5/26 17:12
 * @Version 1.0
 */
public class Test {
    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool();
        Connection conn = pool.getConnection();
        System.out.println(conn);

    }
}

 测试要想成功,需要本地安装mysql服务器,并且存在test数据库。

还需要添加mysql pom依赖:

 <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>

测试结果如下:

 7.3.享元模式在源码中的应用

7.3.1.jdk String使用享元模式
package com.wf.flyweight.jdk;

/**
 * @ClassName StringTest
 * @Description String源码研究测试
 * @Author wf
 * @Date 2020/5/26 17:37
 * @Version 1.0
 */
public class StringTest {
    public static void main(String[] args) {
        //这句声明,创建了几个对象?
        //s1是在运行阶段声明的,“hello”字符串常量,在编译期就已经完成创建
        //常量存储在常量池
        String s1= "hello";

        String s2= "hello";
        System.out.println(s1 == s2);//true,两个变量都从常量池取值

        //测试:反例
        //两个常量,拼接,在编译阶段就会,得到最终常量,jdk的底层做了优化处理
        String s3 = "he" + "llo";
        System.out.println(s1 == s3);//true

        String s4 ="hel" + new String("lo");
        System.out.println(s1 == s4);//false

        String s5 = new String("hello");
        System.out.println(s1 == s5);//false
        System.out.println(s4 == s5);//false

        String s6 = s5.intern();//从缓存中取值
        System.out.println(s1 == s6);//true

        String s7 = "h";
        String s8 = "ello";
        String s9 = s7 + s8;
        System.out.println(s1 == s9);//false


    }
}
7.3.2.Integer使用享元模式
package com.wf.flyweight.jdk;

/**
 * @ClassName IntegerTest
 * @Description 测试享元模式应用
 * @Author wf
 * @Date 2020/5/26 18:43
 * @Version 1.0
 */
public class IntegerTest {
    public static void main(String[] args) {
        Integer a = Integer.valueOf(127);
        Integer b = 127;
        System.out.println(a == b);//true

        Integer c = Integer.valueOf(128);
        Integer d = 128;
        System.out.println(c == d);//false
    }
}
注意:
  这里根据经验值,i在【-128,127】之间时,才会缓存值。

  特殊处理是在valueOf中进行的。

7.3.3.Long使用享元模式

ValueOf中进行处理。

7.4.享元模式扩展知识

7.4.1.内部状态和外部状态

  享元模式,提出两个要求。一是细粒度,一是共享。

  细粒度对象,不可避免就会产生很多对象。

  内部状态:是享元状态共享出来的东西,它存在享元对象内部,不会跟随环境改变而改变。

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

7.4.2.享元模式的优点和缺点

优点:

  减少对象的创建,降低内存中对象的数量,降低系统内存消耗,提高效率。

  减少内存之外的其他资源占用。

缺点:

  关注内部 ,外部状态,关注线程安全问题

  使系统,程序的逻辑复杂化

7.4.3.享元模式与其他设计模式的关联
7.4.3.1.享元模式与代理模式

静态代理:持有目标对象的引用,是一对一的关系。达到功能增强的目的。

享元模式:也会持有目标对象的引用,是一对多的关系。提高效率,达到资源复用的目的。

7.4.3.2.享元模式与单例模式

单例模式:单例强调对象的创建是否唯一。

享元模式:通常结合工厂模式使用,一般会把享元类设计为单例类。它强调的是系统结构。

 1.7.2.组合模式详解

时长:60min

7.2.1.组合模式的定义

定义

  Composite Pattern,也称整体-部分模式,它的宗旨是通过将单个对象(叶子节点)和组合对象(树枝节点)

用相同的接口进行表示。属于结构型模式。

作用:

  使客户端对单个对象和组合对象保持一致的方式处理。

  

组合/聚合区分:

  心在一起,称为团队,也可称组合。如:人的组合,身体和头,相互依存,不能分割,这称为组合。

  只是人在一起,称团伙,也可称聚合。而像选择授课老师,这种可以灵活选择,称为聚合。U盘和电脑,也是一种聚合关系。

7.2.1.1.生活中组合模式的体现

  》公司组织架构

  》操作系统的文件管理系统

7.2.1.2.组合模式的适用场景

1.希望客户端可以忽略组合对象与单个对象的差异时

2.对象层次具备整体与部分,呈树形结构(如:树形菜单,操作系统目录,公司组织架构等)

7.2.2.组合模式的代码示例

7.2.2.1.通用写法

组合模式的通常实现,又可分为两种写法:透明写法,和安全写法

1.透明写法

A.抽象组件

  也是一个根节点,代码如下:

package com.wf.composite.transparent;

/**
 * @ClassName Component
 * @Description 组合对象,顶层抽象组件,根节点
 * @Author wf
 * @Date 2020/5/27 10:00
 * @Version 1.0
 */
public abstract class Component {
    protected String name;

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

    public abstract String operation();

    public boolean addChild(Component component){
        throw new UnsupportedOperationException("addChild not supported!");
    }
    public boolean removeChild(Component component){
        throw new UnsupportedOperationException("removeChild not supported!");
    }

    public Component getChild(int index){
        throw new UnsupportedOperationException("getChild not supported!");
    }
}

B.树枝节点,抽象组件实现

package com.wf.composite.transparent;

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

/**
 * @ClassName Composite
 * @Description 抽象组合,透明实现,实现的父类的空方法,树节点
 * @Author wf
 * @Date 2020/5/27 10:05
 * @Version 1.0
 */
public class Composite  extends Component{
    List<Component> mComponents;
    public Composite(String name) {
        super(name);
        mComponents = new ArrayList<Component>();
    }

    @Override
    public String operation() {
        StringBuilder builder = new StringBuilder(this.name);
        for(Component component: this.mComponents){
            builder.append("
");
            builder.append(component.operation());
        }
        return builder.toString();
    }
    @Override
    public boolean addChild(Component component) {
        return this.mComponents.add(component);
    }

    @Override
    public boolean removeChild(Component component) {
        return this.mComponents.remove(component);
    }

    @Override
    public Component getChild(int index) {
        return this.mComponents.get(index);
    }
}

C.叶子节点

package com.wf.composite.transparent;

/**
 * @ClassName Leaf
 * @Description 组合对象,叶子节点
 * @Author wf
 * @Date 2020/5/27 10:12
 * @Version 1.0
 */
public class Leaf extends Component {

    public Leaf(String name) {
        super(name);
    }

    @Override
    public String operation() {
        return this.name;
    }
}

D,测试代码

package com.wf.composite.transparent;

public class Test {
    public static void main(String[] args) {
        // 来一个根节点
        Component root = new Composite("root");
        // 来一个树枝节点
        Component branchA = new Composite("---branchA");
        Component branchB = new Composite("------branchB");
        // 来一个叶子节点
        Component leafA = new Leaf("------leafA");
        Component leafB = new Leaf("---------leafB");
        Component leafC = new Leaf("---leafC");

        root.addChild(branchA);
        root.addChild(leafC);
        branchA.addChild(leafA);
        branchA.addChild(branchB);
        branchB.addChild(leafB);

        String result = root.operation();
        System.out.println(result);

    }
}

测试结果如下:

E.系统类图

之所以,称为透明写法时,Component抽象组件中定义了3个空方法。

 2.安全写法

  由于透明写法中,抽象组件中定义几个空方法,作为公共组件,Leaf实现类,不需要使用到这几个空方法。

所以,父类中提供的几个功能,并不是公共的。并不是子类中都能用到,所以,现在,去掉这几个方法。

  Composite实现中,如果需要自己进行扩展。

A.顶层抽象组件

package com.wf.composite.safe;

/**
 * @ClassName Component
 * @Description 组合对象,顶层抽象组件,根节点
 * @Author wf
 * @Date 2020/5/27 10:00
 * @Version 1.0
 */
public abstract class Component {
    protected String name;

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

    public abstract String operation();
}

B.子节点实现

package com.wf.composite.safe;

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

/**
 * @ClassName Composite
 * @Description 抽象组合,透明实现,实现的父类的空方法,树节点
 * @Author wf
 * @Date 2020/5/27 10:05
 * @Version 1.0
 */
public class Composite  extends Component {
    List<Component> mComponents;
    public Composite(String name) {
        super(name);
        mComponents = new ArrayList<Component>();
    }

    @Override
    public String operation() {
        StringBuilder builder = new StringBuilder(this.name);
        for(Component component: this.mComponents){
            builder.append("
");
            builder.append(component.operation());
        }
        return builder.toString();
    }

    public boolean addChild(Component component) {
        return this.mComponents.add(component);
    }


    public boolean removeChild(Component component) {
        return this.mComponents.remove(component);
    }


    public Component getChild(int index) {
        return this.mComponents.get(index);
    }
}

C.叶子节点实现

package com.wf.composite.safe;

/**
 * @ClassName Leaf
 * @Description 组合对象,叶子节点
 * @Author wf
 * @Date 2020/5/27 10:12
 * @Version 1.0
 */
public class Leaf extends Component {

    public Leaf(String name) {
        super(name);
    }

    @Override
    public String operation() {
        return this.name;
    }
}

D.测试代码

package com.wf.composite.safe;

public class Test {
    public static void main(String[] args) {
        //由于Composite对抽象组件Component的功能,进行扩展
        //就不能使用多态的方式,调用子类的方法

        // 来一个根节点
        Composite root = new Composite("root");
        // 来一个树枝节点
        Composite branchA = new Composite("---branchA");
        Composite branchB = new Composite("------branchB");
        // 来一个叶子节点
        Component leafA = new Leaf("------leafA");
        Component leafB = new Leaf("---------leafB");
        Component leafC = new Leaf("---leafC");

        root.addChild(branchA);
        root.addChild(leafC);
        branchA.addChild(leafA);
        branchA.addChild(branchB);
        branchB.addChild(leafB);

        String result = root.operation();
        System.out.println(result);

    }
}

E.类图查看

 

3.组合模式的具体实现示例

【1】基于课程需求,透明实现

A.系统需求分析

假设希望输出课程目录信息,如下的结构。以此需求,利用组合模式设计系统,并开发。

初步分析:

  上面的结构,有3层目录,可以设计3层嵌套结构。【但是,类太多,不推荐】

  可以巧妙用使用两层结构来处理3层目录。第一级目录,设计成课程包类【作为子节点】

  具体的课程作为叶子节点

  如:java设计模式,作为java架构师课程的叶子节点。

    而java入门课程,也得做为叶子节点。

    关于前后两级目录,使用参数进行优先级区分,如果优先级为1,表示第一层根目录。优先级为2,

表示2级目录。

 B.测试代码

  这里以TDD的开发方式,先编写测试代码,再编写业务逻辑代码。

具体的代码如下:

package com.wf.composite.demo.transparent;

public class Test {
    public static void main(String[] args) {

        System.out.println("============透明组合模式===========");

        CourseComponent javaBase = new Course("Java入门课程",8280);
        CourseComponent ai = new Course("人工智能",5000);

        CourseComponent packageCourse = new CoursePackage("Java架构师课程",2);

        CourseComponent design = new Course("Java设计模式",1500);
        CourseComponent source = new Course("源码分析",2000);
        CourseComponent softSkill = new Course("软技能",3000);

        packageCourse.addChild(design);
        packageCourse.addChild(source);
        packageCourse.addChild(softSkill);

        CourseComponent catalog = new CoursePackage("课程主目录",1);
        catalog.addChild(javaBase);
        catalog.addChild(ai);
        catalog.addChild(packageCourse);

        catalog.print();


    }
}

先编写测试代码,以编译报错,驱动编写业务代码。

C.定义课程的抽象组件

package com.wf.composite.demo.transparent;

/**
 * @ClassName CourseComponent
 * @Description 课程抽象组件
 * @Author wf
 * @Date 2020/5/27 15:22
 * @Version 1.0
 */
public abstract class CourseComponent {
}

为了解决报错,暂时得到一个空类。后面再进行代码填充。

D.创建课程类【叶子节点】

package com.wf.composite.demo.transparent;

/**
 * @ClassName Course
 * @Description 具体组件实现叶子节点,课程类
 * @Author wf
 * @Date 2020/5/27 15:25
 * @Version 1.0
 */
public class Course extends CourseComponent {
    private String name;
    private int price;
    public Course(String name, int price) {
        this.name = name;
        this.price = price;
    }
}

根据测试提示,需要新增两个成员,课程名称name,和购买价格price.

其它代码,需要进一步填充。

E.创建组件实现的叶子节点,课程归属类

初步实现,会生成构造器,并手动添加成员变量。

再来看,测试类中报错情况:

 显然,还有两个方法,未实现引起报错,解决报错,实现方法。

 根据提示,有两种实现方式,一种是定义抽象方法,一种是定义普通方法。

结合组合模式透明写法,叶子节点不需要此方法,只能定义为普通方法。并且抽象类中方法不作实现

这个方法是子节点中必须要实现的方法,所以在子节点类中手动实现。

方法的功能是:

  添加1个或多个叶子节点,所以,是一对多的数据关系,定义一个List类型成员来存储多个节点。

再来解决测试类中最后一个报错,print方法未定义的问题。由于这个方法是公用的,提取到父类中,作为抽象方法定义。

抽象组件中定义抽象方法。抽象方法必须在子类中重写,否则子类引起报错。如下:

package com.wf.composite.demo.transparent;

/**
 * @ClassName Course
 * @Description 具体组件叶子节点,课程类
 * @Author wf
 * @Date 2020/5/27 15:25
 * @Version 1.0
 */
public class Course extends CourseComponent {
    /** 课程名称*/
    private String name;
    private double price;
    public Course(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public void print() {
        System.out.println(name + " (¥" + price + "元)");
    }
}

子节点类,也需要重写抽象方法:

package com.wf.composite.demo.transparent;

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

/**
 * @ClassName CoursePackage
 * @Description 组合对象抽象组合,子节点,课程归属类
 * @Author wf
 * @Date 2020/5/27 15:32
 * @Version 1.0
 */
public class CoursePackage extends CourseComponent {
    /** 课程归属名称*/
    private String name;
    /** 显示结构优先级*/
    private Integer level;
    private List<CourseComponent> items = new ArrayList<CourseComponent>();

    public CoursePackage(String name, Integer level) {
        this.name = name;
        this.level = level;
    }

    @Override
    public void addChild(CourseComponent catalogComponent) {
        items.add(catalogComponent);
    }

    @Override
    public void print() {
        //需要根据需求设置指定的输出格式
        System.out.println(this.name);

        for(CourseComponent catalogComponent : items){
            //控制显示格式
            if(this.level != null){
                for(int  i = 0; i < this.level; i ++){
                    //打印空格控制格式
                    System.out.print("  ");
                }
                for(int  i = 0; i < this.level; i ++){
                    //每一行开始打印一个+号
                    if(i == 0){ System.out.print("+"); }
                    System.out.print("-");
                }
            }
            //打印标题
            catalogComponent.print();
        }

    }
}

F.抽象组件的完整代码

package com.wf.composite.demo.transparent;

/**
 * @ClassName CourseComponent
 * @Description 课程抽象组件
 * @Author wf
 * @Date 2020/5/27 15:22
 * @Version 1.0
 */
public abstract class CourseComponent {
    public void addChild(CourseComponent catalogComponent) {
        throw new UnsupportedOperationException("不支持添加操作");
    }

    public abstract void print();
}

G.运行测试  

  整个系统开发完成,进行最后的测试验证。输出结果如下:

H.系统类图

 【2】基于系统文件目录需求,安全实现

A.需求分析

 展示结果分析:

  上面系统文件目录,存在4级展示目录。还是可以基于展示层次优先级作区分。

B.编写测试代码

具体的测试代码如下:

package com.wf.composite.demo.safe;


public class Test {
    public static void main(String[] args) {

        System.out.println("============安全组合模式===========");

        File qq = new File("QQ.exe");
        File wx = new File("微信.exe");

        Folder office = new Folder("办公软件",2);

        File word = new File("Word.exe");
        File ppt = new File("PowerPoint.exe");
        File excel = new File("Excel.exe");

        office.add(word);
        office.add(ppt);
        office.add(excel);

        Folder wps = new Folder("金山软件",3);
        wps.add(new File("WPS.exe"));
        office.add(wps);

        Folder root = new Folder("根目录",1);
        root.add(qq);
        root.add(wx);
        root.add(office);

        System.out.println("----------show()方法效果-----------");
        root.show();

        System.out.println("----------list()方法效果-----------");
        root.list();


    }
}

注意:由于这里是基于组合模式的安全写法实现,不能使用多态,没有出现顶层抽象组件依赖。 

C.顶层抽象组件定义

  基于TDD开发,根据测试代码中报错提示,驱动开发业务代码。

package com.wf.composite.demo.safe;

/**
 * 顶层抽象组件,目录类
 */
public abstract class Directory {

    protected String name;

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

    public abstract void show();

}

D.定义叶子节点File文件类

package com.wf.composite.demo.safe;

/**
 * @ClassName File
 * @Description 叶子节点,文件类
 * @Author wf
 * @Date 2020/5/27 16:36
 * @Version 1.0
 */
public class File extends Directory {
    //private String name;  //由于是公共成员,提取到父类中,声明为protected
    public File(String name) {
        super(name);
    }

    @Override
    public void show() {
        System.out.println(this.name);
    }
}

需要手动实现抽象组件,并重写方法。只需要打印名称即可。

E.定义子节点文件夹Folder类

package com.wf.composite.demo.safe;

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

/**
 * @ClassName Folder
 * @Description 组合对象抽象组件,子节点
 * @Author wf
 * @Date 2020/5/27 16:41
 * @Version 1.0
 */
public class Folder extends Directory {
    private Integer level;
    private List<Directory> dirs = new ArrayList<Directory>();

    public Folder(String name, Integer level) {
        super(name);
        this.name = name;
        this.level = level;
    }

    public void add(Directory dir) {
        dirs.add(dir);
    }

    @Override
    public void show() {
        //抽象方法,必须重写,设置输出格式
        System.out.println(this.name);
        for (Directory dir : this.dirs) {
            //控制显示格式
            if(this.level != null){
                for(int  i = 0; i < this.level; i ++){
                    //打印空格控制格式
                    System.out.print("  ");
                }
                for(int  i = 0; i < this.level; i ++){
                    //每一行开始打印一个+号
                    if(i == 0){ System.out.print("+"); }
                    System.out.print("-");
                }
            }
            //打印名称
            dir.show();
        }

    }

    /**
     * 扩展方法,不能使用多态调用
     */
    public void list() {
        for (Directory dir : this.dirs) {
            System.out.println(dir.name);
        }
    }
}

F.运行测试

 G.系统类图查看

7.2.3.组合模式在源码中的应用

7.2.3.1.jdk中hashMap应用组合模式
 public void putAll(Map<? extends K, ? extends V> m) {
        putMapEntries(m, true);
    }

说明:

  putAll方法,传参抽象组件Map类型。就是组合模式的应用。

7.2.3.2.jdk中ArrayList中addAll方法
public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
 说明:

  addAll,传参Collection,组合模式应用。

7.2.4.组合模式的问题总结

1.透明写法与安全写法的区别?

透明写法

  顶层抽象组件会定义所有的方法,包括叶子节点不需要的方法。只是作空方法声明。

对于叶子节点,很容易错误调用,所以,违背最少知道原则。

安全写法

  顶层抽象组件,只定义公共方法,调用时就不能使用多态的方式,只能子类自己new自己。

  避免叶子节点类实例,错误调用。

组合模式一定是树形结构吗?

  答:不一定。只是强调整体部分的关系。它是针对整体与部分,寻找共同点,进行公共逻辑抽取的思维方式。

7.2.4.1.组合模式的优缺点

优点:

  1.清楚地定义层次分明的复杂对象,表示对象的全部或部分层次

  2.让客户端忽略层次的差异,方便对整个层次结构进行控制

  3.简化客户端代码

  4.符合开闭原则

缺点:

  1.限制类型时会较为复杂

  2.使设计变得更加复杂

附录:

  tom老师的刷票软件:https://github.com/gupaoedu-tom/3party-tools/tree/master/py12306

原文地址:https://www.cnblogs.com/wfdespace/p/12957670.html