设计模式(四)~结构型模式(2)

装饰模式(不改变类结构情况下对类功能扩展)

通常情况下,扩展一个类的功能会使用继承方式来实现,但是继承具有静态特征,耦合度高,随着扩展功能的增加,导致子类过于膨胀。而通过装饰者模式来创建一个包装对象(即装饰对象)来包裹真实对象,在真实对象类结构不变的情况下,为其提供额外的功能。

定义

装饰模式:在不改变当前对象结构的前提下,对该对象进行动态的增加一些功能,它属于对象结构型模式。

特点

优点

  • 灵活:装饰模式扩展对象功能比采用继承方式更加灵活。

    继承是在编译时期静态引入,耦合度高,而且子类拥有父类的资源,如果扩展功能增多,子类会很膨胀。

  • 多种组合:可以设计出多个不同的具体装饰类,创造出多个不同的行为组合。

    为每一个扩展的功能设计一个具体装饰类,来进行对原对象功能扩展(但是是增加许多子类)。

缺点

  • 子类过多,增加程序复杂性

    装饰模式由于通过具体装饰类对具体构建者(原对象)进行功能扩展,因此会增加许多子类,如果过度使用会使程序变得复杂。

结构

装饰模式主要包含以下角色

  • 抽象构建者:是一个抽象接口,规范待扩展功能的对象(具体构建者)。
  • 具体构建者(待扩展类):实现抽象构建者接口内的方法,通过装饰者为其添加一些扩展功能。
  • 抽象装饰者:实现抽象构建者接口,内部包含一个具体构建者的引用,通过子类对具体构建者进行功能扩展。
  • 具体装饰者:继承抽象装饰者实现相关抽象方法,并给具体构建者添加扩展功能。
    在这里插入图片描述
实现

案例:比如在具体构建者Hello类输出“Hello”语句前后各扩展一条输出语句。

/**
 *1、定义具体构建者接口(规范待扩展功能的对象)
 */
public interface IHello {
    void say();
}
/**
 *2、定义具体构建者(实现抽象构建者接口方法)
 */
public class HelloImpl implements IHello {
    @Override
    public void say() {
        System.out.println("Hello...");
    }
}
/**
 *3、定义抽象装饰者(实现抽象构建者,内部包含对具体构建者的引用)
 */
public abstract class AbsHello implements IHello {
    //内部持有一个具体构建者的引用
    IHello iHello;
    public AbsHello(IHello iHello){
        this.iHello = iHello;
    }

    @Override
    public void say() {
        iHello.say();
    }
}
/**
 *4、定义具体装饰者(继承抽象装饰者,实现对具体构建者增强)
 */
public class BeforeSayHello extends AbsHello {
    public BeforeSayHello(IHello iHello) {
        super(iHello);
    }
    //对say()方法增强
    @Override
    public void say() {
        before();
        iHello.say();
    }

    void before() {
        System.out.println("before say hello。。。");
    }
}
//测试
public class Test {
    public static void main(String[] args) {
        
        //装饰者模式,通过组合方式对具体构建者进行不同行为扩展
        IHello iHello = new HelloImpl();
        iHello = new AfterSayHello(iHello);
        iHello = new BeforeSayHello(iHello);
        iHello.say();
        //或者
        IHello iHello = new BeforeSayHello(new AfterRunHello(new HelloImpl()));
        iHello.say();
    }
}

//输出
before 。。。
Hello...
after。。。
应用场景
  • 当需要给一个现有类扩展功能,而有不能采用继承生成子类方式进行扩展时候。
  • 当需要通过对现有的一组基本功能进行排列组合而产生非常多的功能时,采用继承方式很难实现,而采用装饰模式很好实现。
  • 当对象的功能要求可以动态扩展,也可以动态撤销时。

在java中最著名的装饰者模式应用莫过于Java/IO标准库的设计了,例如:InputStream的子类FilterInPutStream,OutPutStream的子类FilterOutPutStream,Reader的子类BufferedReader以及FilterReader,还有Writer的子类BufferedWriter、FilterWriter、PrintWriter等他们都是抽象装饰类。

扩展
1. 省略抽象构建者(仅有一个具体构建者时候)
/**
 *1、创建具体构建者(要被扩展功能的对象)
 */
public class Hello1 {
    public void say(){
        System.out.println("hello");
    }
    
    public void run(){
        System.out.println("run");
    }
}


/**
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *2、创建抽象装饰者(继承自具体构建者,内部包含一个对具体构建者引用)
 *++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
public class AbsHello1 extends Hello1 {
    //内部持有具体构建者引用
    Hello1 hello1;
    public AbsHello1(Hello1 hello1) {
        this.hello1 = hello1;
    }

    @Override
    public void say() {
        hello1.say();
    }

    @Override
    public void run() {
        hello1.run();
    }
}

/**
 *3、创建具体装饰者-继承自抽象构建者(实现对具体构建者功能扩展)
 */
//具体装饰者1-对run()方法增强
public class BeforeRunHello1 extends AbsHello1 {
    public BeforeRunHello1(Hello1 hello1) {
        super(hello1);
    }

    @Override
    public void run() {
        before();
        super.run();
    }

    void before() {
        System.out.println("before.。。。");
    }
}

//具体装饰者2-对run方法进行增强
public class AfterRunHello1 extends AbsHello1 {
    public AfterRunHello1(Hello1 hello1) {
        super(hello1);
    }

    @Override
    public void run() {
        super.run();
        after();
    }

    void after(){
        System.out.println("after.。。。");
    }
}

//测试
public class Test {
    public static void main(String[] args) {
        AbsHello1 absHello = new BeforeRunHello1(new AfterRunHello1(new Hello1()));
        absHello.run();
    }
}
//输出
befor.。。。
run
after.。。。
2. 省略抽象装饰者(仅有一个具体装饰者时候)
/**
 *1、定义抽象构建者
 */
 public interface IHello2 {
    void say();
}
/**
 *2、定义具体构建者(实现抽象构建者)
 */
public class Hello2Impl implements IHello2 {
    @Override
    public void say() {
        System.out.println("Hello1");
    }
}
/**
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *3、定义具体装饰者-实现抽象构建者(仅有一个具体装饰者可以省略抽象装饰者)
 *+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 */
public class BeforHello2 implements IHello2 {
    //内部持有具体构建者引用
    IHello2 hello;
    public BeforHello2(IHello2 iHello2) {
        this.hello = iHello2;
    }

    @Override
    public void say() {
        befor();
        hello.say();
    }

    void befor() {
        System.out.println("befor。。。");
    }
}

//测试
public class Test {
    public static void main(String[] args) {
        IHello2 iHello2 = new BeforSayHello2(new Hello2Impl());
        iHello2.say();
    }
}

//输出
befor。。。
Hello1
3. 如果存在多个具体装饰者,是否可以省略抽象装饰者呢?

在学习的过程中,发现当仅有一个具体装饰者的时候是可以省略抽象装饰者的,即具体装饰者直接实现抽象构建者并持有一个具体构建者的引用。那么如果存在多个具体装饰者不是也可以省略抽象装饰者而直接实现抽象构建者吗?

下面来看一下在有多个具体装饰者情况下使用抽象装饰者和不使用抽象装饰者的区别

/**
 *1、定义抽象构建者接口
 */
public class IHello{
    void say();
    void run();
}

/**
 *2、定义具体构建者(实现抽象构建者接口方法)
 */
public class HelloImpl implements IHello{
    @Override
    public void say(){
        System.out.println("hello");
    }
    
     @Override
    public void run(){
        System.out.println("run");
    } 
}
  • 使用抽象装饰者

    /**
     *3、定义抽象构建者(实现抽象构建者接口,且内部持有一个具体构建者引用)
     */
    public abstract AbsHello implements IHello{
        
        IHello hello;
        public AbsHello(IHello hello){
            this.hello = hello;
        }
        
        @Override
        public void say() {
            hello.say();
        }  
        
        @Override
        public void run() {
            hello.run();
        } 
    }
    
    /**
     *4、定义多个具体装饰者继承自抽象构建者
     */
    //具体构建者1-增强say方法。
    public class BeforeSayHello extends AbsHello {
        public BeforeSayHello(IHello iHello) {
            super(iHello);
        }
    
        //这个类只想增强一下say()方法,不想变动其他的run方法,所以这个类只需要重写这个类即可
        @Override
        public void say() {
            before();
            iHello.say();
        }
    
        void before() {
            System.out.println("before say hello。。。");
        }   
    }
    
    //具体装饰者2-增强run方法
    public class AfterRunHello extends AbsHello {
        public AfterRunHello(IHello iHello) {
            super(iHello);
        }
    
        //这个类只想增强一下run()方法,不想变动其他的say方法,所以这个类只需要重写这个类即可
        @Override
        public void run() {
            iHello.run();
            after();
        }
    
        void after() {
            System.out.println("after run。。。。");
        }
    }
    
  • 不使用抽象装饰者

    /**
     *3、具体装饰者1-直接实现抽象构建者接口
     */
    //这个具体装饰者类只想增强一个say方法。
    public class BeforeSayHello2 implements IHello {
        IHello iHello;
        public BeforeSayHello2(IHello iHello) {
            this.iHello = iHello;
        }
    
        //增强say()方法
        @Override
        public void say() {
            before();
            iHello.say();
        }
        //这个类只想增强一下say()方法,但是实现了IHello接口,因此必须要重写run()方法。
        @Override
        public void run() {
            iHello.run();
        }
    
        void before(){
            System.out.println("before say hello。。。");
        }
    }
    
    //同样:具体装饰者2也直接实现抽象构建者,只想增强一下run()方法
    public class AfterRunHello2 implements IHello {
    
        IHello iHello;
        public AfterRunHello2(IHello iHello){
            this.iHello = iHello;
        }
        
        //这个类只想增强一下run()方法,但是实现了IHello接口,因此必须要重写say()方法。
        @Override
        public void say() {
            iHello.say();
        }
    
        //增强run()方法
        @Override
        public void run() {
            iHello.run();
            after();
        }
    
        void after() {
            System.out.println("after run。。。。");
        }
    }
    

总结

通过以上对是否使用抽象装饰者的对比,发现存在多个具体构建者的时候,使用抽象装饰者可以指定的对具体构建者的功能进行扩展,而无需覆写冗余的功能方法。如果不使用抽象构建者则需要具体构建者覆写所实现的抽象构建者接口内的所有方法。

存在多个具体装饰者的时候使用抽象构建者使程序逻辑更加清晰。

外观模式(多个子系统对外提供统一接口)

定义

显示生活中,常常存在办事较复杂的例子,比如办理产证或者注册一家公司,有时要多个部门同时联系,这时要是有一个综合部门能解决一切手续问题就好了。

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

特点

优点

  • 降低系统耦合度

    降低了子系统与客户端的耦合度,使得子系统的变化不会影响它的调用者。

  • 子系统使用更加容易

    对调用者屏蔽子系统组件,减少客户端处理对象数目。并使得子系统使用起来更加容易。

  • 降低大型系统中的编译依赖性

    简化了系统在不同平台之间的移植过程,因为编译一个子系统不会影响其他的子系统,也不会影响外观对象。

缺点

  • 不符合开闭原则

    增加新的子系统可能需要修改外观类或客户端的源代码,违背了开闭原则

结构

外观模式比较简单,主要定义一个高层接口,包含了对各个子系统的引用,客户端通过它访问各个子系统的功能。

外观模式主要有以下角色

  • 外观角色:为多个子系统提供共同的接口。
  • 子系统角色:实现系统的部分功能,客户端可以通过外观角色访问它。
  • 客户端角色:通过一个外观角色访问各个子系统功能。
    在这里插入图片描述
实现

案例:比如打开电脑和关闭电脑需要分别对电源、CPU、硬盘的启动和关闭,这里电脑就相当于外观角色,而电源、CPU、硬盘相当于子系统,因此我们将电源、CPU、硬盘的启动和关闭通过电脑提供统一的接口实现。

/**
 *1、定义外观类-电脑(包装多个子系统)
 */
public class Computer {

    //子系统
    ISubSystem power;
    ISubSystem cpu;
    ISubSystem hardDisk;

    public Computer() {
        power = new Power();
        cpu = new CPU();
        hardDisk = new HardDisk();
    }

    /**
     * 开机
     */
    public void open() {
        power.open();
        cpu.open();
        hardDisk.open();
    }

    //关机
    public void close() {
        hardDisk.close();
        cpu.close();
        power.close();
    }
}
/**
 *2、抽象子系统
 */
public interface ISubSystem {
    void open();
    void close();
}
//具体子系统实现抽象子系统接口,完成具体方法实现
//子系统-电源
public class Power implements ISubSystem {
    @Override
    public void open() {
        System.out.println("启动电源");
    }

    @Override
    public void close() {
        System.out.println("关闭电源");
    }
}

//子系统-CPU
public class CPU implements ISubSystem{

    public void open(){
        System.out.println("启动CPU...");
    }

    public void close(){
        System.out.println("关闭CPU...");
    }
}

//子系统-硬盘
public class HardDisk implements ISubSystem{
    @Override
    public void open() {
        System.out.println("启动硬盘...");
    }

    @Override
    public void close() {
        System.out.println("关闭硬盘...");
    }
}
//测试-客户端通过外观角色访问具体子系统
public class Test {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.open();
        System.out.println("-----------------");
        computer.close();   
    }
}

//输出
启动电源...
启动CPU...
启动硬盘...
--------------------------------
关闭硬盘...
关闭CPU...
关闭电源...
应用场景
  • 对分层结构系统构建时,使用外观模式定义子系统中每层的入口点可以简化子系统之间的依赖关系。
  • 当一个复杂系统的子系统很多时,外观模式可以为系统设计一个简单的接口供外界访问。
  • 当客户端与多个子系统之间存在很大的联系时,引入外观模式可将他们分离,从而提高子系统的独立性和可移植性。
扩展
1. 增加抽象外观者角色

加入现在需要新增一个显示子系统启动,或者删除一个硬盘子系统,则需要对原有的外观角色进行修改,这违背了开闭原则,通过引入抽象外观角色即可解决。

在这里插入图片描述

/**
 *1、定义抽象外观角色
 */
 public abstract class AbsFacade {
    abstract void open();
    abstract void close();
}

/**
 *2、新增子系统-显示器
 */
public class Monitor implements ISubSystem {
    @Override
    public void open() {
        System.out.println("启动显示器...");
    }

    @Override
    public void close() {
        System.out.println("关闭显示器...");
    }
}

/**
 *3、定义外观角色,继承抽象外观角色,新增子系统无需修改原来外观角色
 */
public class Computer2 extends AbsFacade {

    ISubSystem power, monitor, cpu, hardDisk;

    public Computer2() {
        power = new Power();
        monitor = new Monitor();
        cpu = new CPU();
        hardDisk = new HardDisk();
    }

    @Override
    public void open() {
        power.open();
        monitor.open();
        cpu.open();
        hardDisk.open();
    }

    @Override
    public void close() {
        hardDisk.close();
        cpu.close();
        monitor.close();
        power.close();
    }
}

/**
 *4、测试
 */
public class Test {
    public static void main(String[] args) {
        
        Computer computer1 = new Computer();
        computer1.open();
        System.out.println();
        computer1.close();

        System.out.println("------------外观模式扩展--------------------");

        //测试-扩展外观者模式(增加抽象外观者角色)
        AbsFacade computer2 = new Computer2();
        computer2.open();
        System.out.println();
        computer2.close();
    }
}

//输出
启动电源...
启动CPU...
启动硬盘...
    
关闭硬盘...
关闭CPU...
关闭电源...
------------外观模式扩展--------------------
启动电源...
启动显示器...
启动CPU...
启动硬盘...

关闭硬盘...
关闭CPU...
关闭显示器...
关闭电源...

上面通过引入抽象外观角色增加或删除子系统,在不修改原来具体外观模式角色情况下,通过抽象外观角色来扩展具体外观角色解决,满足开闭原则。

享元模式(大量细粒度对象复用)

在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。

定义

共享细粒度对象(运用共享技术来有效的支持大量细粒度对象的复用)。

特点

优点

  • 减少对象创建,降低系统内存。

    相同的对象只需要保存一份,降低系统中对象数量,从而降低了系统中细粒度对象给内存带来的压力。

缺点

  • 增加程序复杂性。

    为了使对象可以共享,需要将一些不能共享的状态(属性)外部化,这将增加程序的复杂性。

  • 读取享元模式的外部状态会使运行时间稍微变长。

状态

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

  • 内部状态:不会随着环境的改变而改变的可共享部分;
  • 外部状态,随着环境的改变而改变的不可共享部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部对象外部化。

内部状态是不变的,外部状态是变化的,然后通过共享不变的部分,达到减少对象数据并节约内存的目的。

结构

享元模式的主要角色如下:

  • 抽象享元角色:所有具体享元类的基类,同时定义对象的外部状态和内部状态的接口或实现。
  • 具体享元角色:实现抽象享元角色中所规定的接口(非享元的外部状态以参数形式通过方法传入)
  • 非享元角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
  • 享元工厂角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

在这里插入图片描述

实现

案例:过年回家的时候抢购火车票,抢票之前我们需要先查询车票信息,这里假设一张火车票包含:出发站、终点站、价格、座位类型。现在要求编写一个查询火车票的伪代码,可以根据:出发站、终点站查询到相关的车票信息。

/**
 *1、定义抽象享元角色-火车票
 */
public interface ITrainTicket {
    void showTicket(String type);
}

/**
 *2、定义具体享元角色
 */
public class TrainTicket implements ITrainTicket {

    //外部状态-通过方法传入
    String from;
    String to;

    public void fromStation(String from) {
        this.from = from;
    }

    public void toStation(String to) {
        this.to = to;
    }

    @Override
    public void showTicket(String type) {
        SecureRandom secureRandom = new SecureRandom();
        int price = secureRandom.nextInt(500);
        System.out.println("从" + from + "开往" + to + "的" + type + "票价" + price + "元");

    }
}

/**
 *3.1 不使用享元模式
 */
public class TrainTicketFactory {
    public static ITrainTicket getTrain(String from, String to) {
           /**
            *每次都创建新的对象,当某个瞬间如果有大量的用户请求同一张票的信息时,系统就会创建出大量该火车
            *票对象,系统内存压力骤增。
            */
           return new TrainTicket(from, to);
    }
}

/**
 *3、使用享元模式-定义享元工厂(管理和创建享元角色)
 */
public class TrainTicketFactory {
    //定义享元对象缓存池
    static HashMap<String, ITrainTicket> pool = new HashMap<>();

    public static ITrainTicket getTrain(String from, String to) {
        String key = from + "->" + to;
        ITrainTicket iTrainTicket = null;
        //当一个享元对像存在缓存中,则直接返回该对象,无需再次创建。
        if (!pool.containsKey(key)) {
            System.out.println("创建新对象了....");
            iTrainTicket = new TrainTicket();
            ((TrainTicket) iTrainTicket).fromStation(from);
            ((TrainTicket) iTrainTicket).toStation(to);
            pool.put(key, iTrainTicket);
        } else {
            System.out.println("使用已有对象...");
            iTrainTicket = (TrainTicket) pool.get(key);
        }
        return iTrainTicket;
    }
}
/**
 *4、测试
 */
public class Test {
    public static void main(String[] args) {
        ITrainTicket iTrainTicket = TrainTicketFactory.getTrain("上海", "北京");
        iTrainTicket.showTicket("软座");

        System.out.println("---------------------");

        ITrainTicket iTrainTicket2 = TrainTicketFactory.getTrain("上海", "南京");
        iTrainTicket2.showTicket("硬座");

        System.out.println("---------------------");

        ITrainTicket iTrainTicket3 = TrainTicketFactory.getTrain("徐州", "上海");
        iTrainTicket3.showTicket("硬座");


        System.out.println("---------------------");

        ITrainTicket iTrainTicket4 = TrainTicketFactory.getTrain("北京", "成都");
        iTrainTicket4.showTicket("软座");

        System.out.println("---------------------");

        ITrainTicket iTrainTicket5 = TrainTicketFactory.getTrain("上海", "南京");
        iTrainTicket5.showTicket("软座");

        ITrainTicket iTrainTicket6 = TrainTicketFactory.getTrain("徐州", "上海");
        iTrainTicket6.showTicket("硬座");
    }
}

//输出
创建新对象了....
从上海开往北京的软座票价219---------------------
创建新对象了....
从上海开往南京的硬座票价396---------------------
创建新对象了....
从徐州开往上海的硬座票价419---------------------
创建新对象了....
从北京开往成都的软座票价388---------------------

使用已有对象...
从上海开往南京的软座票价224---------------------
使用已有对象...
从徐州开往上海的硬座票价28//当key已经存在的情况下,享元角色不会重新创建,而是通过享元工厂内部维护的对象返回,通过更改外部状态(属性)复用已经存在的对象。
应用场景
  • 系统中存在大量相同或相似色对象,且这些对象耗费大量的内存资源。
  • 大部分对象可以按照内部状态进行分组,且可将不同的部分外部化,这样每个组只需保存一个内部状态。
  • 由于享元模式需要额外维护一个保存享元的数据结构(缓存池),所以应当在有足够多的享元实例时才值得使用享元模式。
扩展
1. 单纯享元模式

所有的具体享元类都是可以共享的,不存在非共享的具体享元类,结构如下

在这里插入图片描述

2. 复合享元模式

有些享元对象是由一些单纯享元对象组合而成的,它们就是复合享元对象,虽热复合享元对象本身不能共享,但它们可以分解成单纯享元怼系那个再被共享,结构如下:

在这里插入图片描述

组合模式(单个对象组合成复合对象)

生活中存在很多部分-整体的关系,例如:学习用品中的书与书包,生活用品中的衣服与衣柜。软件开发中也是这样,例如:文件系统中的文件与文件夹。这些简单对象与复合对象的处理,如果用组合模式来处理会很方便。

定义

组合模式:是一种将对象组合成树状的层次结构模式,用来表示部分-整体的关系,使用户对单个对象和组合对象具有一致的访问性。

特点

优点

  • 简化客户端代码

    组合模式可以使得客户端代码可以一致的处理单个对象和组合对象,无需关心是处理单个对象还是组合对象。

  • 满足开闭原则

    组合模式更容易在组合体内加入新的对象,客户端不会因为加入新的对象二更改源代码。

缺点

  • 设计复杂:客户端需要花费更多时间清理类之间层次关系;
  • 不容易限制容器中的构建;
  • 不容易用继承的方法来增加构建的新功能。
结构

组合模式主要包含一下角色

  • 抽象构件(Component)角色:主要作用是为树叶构件和树枝构建声明公共接口,并实行他们的默认行为,
  • 树叶构件(Leaf)角色:是组合中的叶节点对象,他没有子节点,用于实现抽象构件角色中声明的接口。
  • 树枝构件(Composite)角色:是组合模式中的分支节点对象,它有子节点。实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包括Add()、Remove()、GetChild()等方法。

透明式组合

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

在这里插入图片描述

安全式组合

该方式中,将管理子构件的方法移动到树枝构件中,抽象构件没有对子对象的管理方法,这样就避免了透明式组合的安全性问题,但由于叶子和树枝有不同的接口,客户端在调用时要知道树叶对象和树枝对象的存在,因此失去透明性。结构如下:

在这里插入图片描述

实现
  • 透明式组合实现

    案例:文件夹结构由文件夹和具体文件组成,这里通过透明式组合实现文件夹创建及子文件添加和展示

    /**
     * 1、创建抽象构建对象(因为透明式组合因此声明子对象的所有方法)
     */
    public abstract class AbsFile {
    
        String name;
        public AbsFile(String name) {this.name = name;}
    
        public String getName() {return name;}
    
        public void setName(String name) {this.name = name;}
        //存储文件夹下所有文件
        protected List<AbsFile> fileList = new ArrayList<>();
        //添加文件或者文件夹
        public abstract void add(AbsFile file);
        //删除文件或者文件夹
        public abstract void remove(AbsFile absFile);
        //输出文件结构
        public abstract void show();
    }
    
    /**
     *2.创建树枝构件-文件夹
     */
    public class Folder extends AbsFile {
    
        public Folder1(String name) {super(name);}
    
        @Override
        public void add(AbsFile file) {fileList.add(file);}
    
        @Override
        public void remove(AbsFile file) {fileList.remove(file);}
    
        @Override
        public void show() {
            System.out.println("=文件夹:" + this.getName());
            for (AbsFile file : fileList) {
                file.show();
            }
        }
    }
    
    /**
     *3、创建树叶构件-具体文件
     */
    public class File extends AbsFile {
        public File1(String name) {super(name);}
    
        @Override
        public void add(AbsFile file) {//TODO ignore ...}
    
        @Override
        public void remove(AbsFile absFile) {//TODO ignore ...}
        
        @Override
        public void show() {
            System.out.println("\t\t文件:" + this.getName());
        }
    }
    

    测试

    /**
     * 组合模式-透明式,客户端调用无需关心是叶子还是树枝构件
     */
    AbsFile rootFolder1 = new Folder1("根目录");
    
    AbsFile picFolder1 = new Folder1("图片文件夹");
    AbsFile pic1File = new File1("盾山-海报.png");
    AbsFile pic2File = new File1("曹操-海报.png");
    AbsFile pic3File = new File1("关羽-海报.jpg");
    picFolder1.add(pic1File);
    picFolder1.add(pic2File);
    picFolder1.add(pic3File);
    
    AbsFile audioFolder1 = new Folder1("音频文件夹");
    AbsFile audio1File = new File1("鲁班七号-配音.mp3");
    AbsFile audio2File = new File1("狄仁杰-配音.mp3");
    AbsFile audio3File = new File1("孙尚香-配音.mpd");
    audioFolder1.add(audio1File);
    audioFolder1.add(audio2File);
    audioFolder1.add(audio3File);
    
    AbsFile videoFolder1 = new Folder1("视频文件夹");
    AbsFile video1File = new File1("芈月-使用攻略.mp4");
    AbsFile video2File = new File1("复活甲-属性介绍.mp4");
    AbsFile video3File = new File1("扁鹊-使用攻略.mp4");
    videoFolder1.add(video1File);
    videoFolder1.add(video2File);
    videoFolder1.add(video3File);
    
    rootFolder1.add(picFolder1);
    rootFolder1.add(audioFolder1);
    rootFolder1.add(videoFolder1);
    
    rootFolder1.show();
    
    //输出
    =======================================
                  组合模式-透明式            
    =======================================
    =文件夹:根目录
    =文件夹:图片文件夹
    		文件:盾山-海报.png
    		文件:曹操-海报.png
    		文件:关羽-海报.jpg
    =文件夹:音频文件夹
    		文件:鲁班七号-配音.mp3
    		文件:狄仁杰-配音.mp3
    		文件:孙尚香-配音.mpd
    =文件夹:视频文件夹
    		文件:芈月-使用攻略.mp4
    		文件:复活甲-属性介绍.mp4
    		文件:扁鹊-使用攻略.mp4
    
  • 安全式组合

    案例同上,区别是抽象构建只声明子类公共的接口

    /**
     *创建抽象构件(安全式组合:仅声明子类公共接口)
     *设置文件名称和展示文件结构
     */
    public abstract class AbsFile2 {
    
        protected String name;
    
        public String getName() {return name;}
    
        public void setName(String name) {this.name = name;}
    
        public abstract void show();
    }
    
    /**
     *创建树枝构件(声明管理子构件的add remove等方法)
     */
    public class Folder2 extends AbsFile2 {
    
        List<AbsFile2> file2List;
    
        public Folder2(String name) {
            this.setName(name);
            file2List = new ArrayList<>();
        }
        
        public void add(AbsFile2 file2){
            file2List.add(file2);
        }
    
        public void remove(AbsFile2 file2){
            file2List.remove(file2);
        }
    
        @Override
        protected void show() {
            System.out.println("=文件夹:" + this.getName());
            for (AbsFile2 file2 : file2List) {
                file2.show();
            }
        }
    }
    
    /**
     *3、创建树叶构件-具体文件
     */
    public class File2 extends AbsFile2 {
    
        public File2(String name){this.setName(name);}
    
        @Override
        protected void show() {
            System.out.println("\t\t文件:" + this.getName());
        }
    }
    

    测试

    /**
     * 组合模式-安全式,客户端调用需要明确知道是树枝还是树叶构件
     */
    System.out.println("=======================================\n" +
                        "              组合模式-安全式            \n" +
                        "=======================================");
    
    
    Folder2 rootFolder2 = new Folder2("根目录");
    //树枝构件
    Folder2 picFolder2 = new Folder2("图片文件夹");
    //树叶构件
    File2 pic1File2 = new File2("盾山-海报.png");
    File2 pic2File2 = new File2("曹操-海报.png");
    File2 pic3File2 = new File2("关羽-海报.jpg");
    picFolder2.add(pic1File2);
    picFolder2.add(pic2File2);
    picFolder2.add(pic3File2);
    
    Folder2 audioFolder2 = new Folder2("音频文件夹");
    File2 audio1File2 = new File2("鲁班七号-配音.mp3");
    File2 audio2File2 = new File2("狄仁杰-配音.mp3");
    File2 audio3File2 = new File2("孙尚香-配音.mpd");
    audioFolder2.add(audio1File2);
    audioFolder2.add(audio2File2);
    audioFolder2.add(audio3File2);
    
    Folder2 videoFolder2 = new Folder2("视频文件夹");
    File2 video1File2 = new File2("芈月-使用攻略.mp4");
    File2 video2File2 = new File2("复活甲-属性介绍.mp4");
    File2 video3File2 = new File2("扁鹊-使用攻略.mp4");
    videoFolder2.add(video1File2);
    videoFolder2.add(video2File2);
    videoFolder2.add(video3File2);
    
    rootFolder2.add(picFolder2);
    rootFolder2.add(audioFolder2);
    rootFolder2.add(videoFolder2);
    
    rootFolder2.show();
    
    =======================================
                  组合模式-安全式            
    =======================================
    =文件夹:根目录
    =文件夹:图片文件夹
    		文件:盾山-海报.png
    		文件:曹操-海报.png
    		文件:关羽-海报.jpg
    =文件夹:音频文件夹
    		文件:鲁班七号-配音.mp3
    		文件:狄仁杰-配音.mp3
    		文件:孙尚香-配音.mpd
    =文件夹:视频文件夹
    		文件:芈月-使用攻略.mp4
    		文件:复活甲-属性介绍.mp4
    		文件:扁鹊-使用攻略.mp4
    
应用场景
  • 在需要表示一个对象整体与部分的层次结构场合
  • 要求对用户隐私组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合。
扩展
1. 复杂的组合模式

如果对前面的组合模式中的树叶和树枝构件进行抽象,也就是说树叶和树枝节点也有子节点,这时候组合模式就扩展成复杂的组合模式了,结构如下:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值