23种设计模式讲解之结构型篇(Java实现并附保姆级注释)

前言

最近系统复习设计模式,边学习边实现,学习过程中我主要参考了程杰的《大话设计模式》和这个网站。大话中的示例代码是C++,后者网站虽然用的是Java,但每一个示例都用窗体展示,夹杂了大量无关模式的awt和swing代码,不利于集中理解模式运作过程。网上大量关于设计模式的文章总是充斥着很多抽象说明和到处复制粘贴的UML图,对初学者理解模式本身反而造成干扰。例如下面这种抽象描述,实际上是对模式完全理解之后的高度概括,很多文章上来就写这种抽象定义,完全是浪费读者时间。这种描述对模式识别的理解是不是必须的,甚至对初学者是有害的。

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

因此我会用三篇文章,侧重模式的代码实现,去芜存菁地讲解每一个模式,每一个代码示例我都已确保在本地成功运行,且代码尽可能遵守Java编程规范(主要是阿里的规范)。每一个模式讲解顺序如下:

  • 模式说明:简要说明模式使用场景和该模式相比简单粗暴的方法(通常是if-esle或者单个类封装)的好处,另外像组合模式里有透明方式和安全方式,单例模式里有饿汉方式和懒汉方式,模板模式里有钩子方法也会简要说明。
  • 结构:列明该模式用到的抽象和具体类。
  • 代码演示:可执行的符合Java编程规范的模式示例代码,并对关键语句都加上了注释。

这是第二篇,结构型篇,其他两篇文章地址如下:

23种设计模式讲解之创建型篇(Java实现并附保姆级注释)

23种设计模式讲解之行为型篇(Java实现并附保姆级注释)

结构型模式

描述如何将类或对象按某种方式组成更大的结构,包括如下:

  • 代理模式
  • 适配器模式
  • 桥接模式
  • 装饰器模式
  • 外观模式
  • 享元模式
  • 组合模式

代理模式

模式说明

客户端不直接声明真实主题,而是声明代理类,通过调用代理类的方法来间接执行真实主题类的行为。

本示例中以代理售房为例,房东需要告知购房者房子类型和价格,但不直接告知购房者,而是通过代理来完成告知动作。代理类中持有房东实例,客户端只声明代理类,调用代理类实例中与房东类相同的方法来完成。房东类和代理类都是目标接口实现类,实现相同的抽象方法,代理类实现该抽象方法的方式是调用其持有的房东类的方法。

结构

抽象主题类

  定义真实主题的方法。

具体主题类

  继承抽象类,实现抽象方法。

代理类

  内部持有一个具体主题类的实例,通过调用该具体主题实例的方法来实现抽象主题类的抽象方法。

代码演示

package com.yukiyama.pattern.structure;

/**
 * 代理模式
 */
public class ProxyDemo {

    public static void main(String[] args) {
        // 声明一个房屋代理类
        HouseAgent agent = new HouseAgent();
        // 调用代理类的方法
        // 输出“三室一厅”
        agent.showHouse();
        // 输出“100万”
        agent.negotiatePrice();
    }

}

/**
 * 抽象主题类
 * 下例为抽象房东类,有抽象展示房屋方法和抽象价格交涉方法。
 */
abstract class Subject{
    public abstract void showHouse();
    public abstract void negotiatePrice();
}

/**
 * 具体主题类
 * 继承抽象主题类,实现抽象方法。
 * 下例为房东类。
 */
class HouseHolder extends Subject{
    private String house;
    private int price;
    
    @Override
    public void showHouse() {
        house = "三室一厅";
        System.out.println(house);
    }
    @Override
    public void negotiatePrice() {
        price = 100;
        System.out.println(price + "万");
    }
}

/**
 * 代理类
 * 持有一个具体主题类实例(房东),实现接口抽象方法,在内部调用真实主题的方法。
 */
class HouseAgent extends Subject{
    HouseHolder holder = new HouseHolder();
    
    @Override
    public void showHouse() {
        holder.showHouse();
    }
    @Override
    public void negotiatePrice() {
        holder.negotiatePrice();
    }
}
复制代码

适配器模式

模式说明

当前有一工具(一个已存在的类),能实现若干功能(方法),需要在某平台(目标)上使用,但与平台的接口不同(已存在的类的方法与接口方法名不同),需提供一个适配器(适配器类)转换为平台接口。这种场景可以使用适配器模式实现。与代理模式比较类似,但代理类中的RealSubjectProxy都需要实现Subject抽象类。而适配器模式中的Adaptee类是原本就已存在的类,内部的方法与接口中定义的抽象方法不同(行为一致,方法名不同)。

本示例中,以Typec为抽象目标类,其内有标准充电方法typecChange和标准数据传输方typecDataTran。现有Typeb类和Lightning类,均有自己的充电方法和数据传输方法。演示如何为他们设置适配器类,使得客户端能够通过调用适配器中的符合Typec的标准方法来间接使用TypebLightning的充电和数据传输功能(方法)。

结构

抽象目标类

  定义抽象标准方法。

被适配类

  有与目标类相同功能的方法,但接口(方法名)不符合标准接口(与目标类内定义的方法的方法名不同)。

适配器类

  继承目标抽象类,内部持有一个被适配类实例,实现目标抽象类的抽象方法,在方法内部调用其持有的被适配类的原有方法。

代码演示

package com.yukiyama.pattern.structure;

/**
 * 适配器模式
 */
public class AdapterDemo {

    public static void main(String[] args) {
        // 使用非标准物件时,声明其适配器
        Typec t1 = new AdapterB2C();
        Typec t2 = new AdapterL2C();
        // 通过调用对应适配器的标准方法,间接使用
        // 输出“使用TypeB接口充电”
        t1.typecCharge();
        // 输出“使用TypeB接口传输数据”
        t1.typecDataTrans();
        // 输出“使用TypeB接口充电”
        t2.typecCharge();
        // 输出“使用TypeB接口传输数据”
        t2.typecDataTrans();
    }

}

/**
 * 抽象目标类
 * 定义抽象标准方法。
 * 下例是Typec方式的充电方法和数据传输方法。
 */
abstract class Typec{
    public abstract void typecCharge();
    public abstract void typecDataTrans();
}

/**
 * 抽象目标类的继承类
 * 在本模式中不是必须的,只是为了展示无需适配的情况。
 */
class HuaweiTypeC extends Typec{
    @Override
    public void typecCharge() {
        System.out.println("使用TypeC接口充电");
    }
    @Override
    public void typecDataTrans() {
        System.out.println("使用TypeC接口传输数据");
    }
}

/**
 * 被适配类(需要适配的类)
 * 有与目标类相同功能的方法,但接口(方法名)不符合标准接口(与目标类内定义
 * 的方法的方法名不同)。
 * 下例是Typeb类,使用Typeb方式的充电和数据传输方法。
 */
class Typeb{
    public void typebCharge() {
        System.out.println("使用TypeB接口充电");
    }
    public void typebDataTrans() {
        System.out.println("使用TypeB接口传输数据");
    }
}

/**
 * 适配器类
 * 继承目标抽象类,内部持有一个被适配类实例,实现目标抽象类的抽象方法,在
 * 方法内部调用其持有的被适配类的原有方法。
 * 下例是Typeb转Typec的适配器。
 */
class AdapterB2C extends Typec{
    Typeb typeb = new Typeb();
    
    @Override
    public void typecCharge() {
        typeb.typebCharge();
    }
    @Override
    public void typecDataTrans() {
        typeb.typebDataTrans();
    }
}

/**
 * 被适配类(需要适配的类)
 * 下例是Lightning类,使用Lightning方式的充电和数据传输方法。
 */
class Lightning{
    public void lightningCharge() {
        System.out.println("使用Lightning接口充电");
    }
    public void lightningDataTrans() {
        System.out.println("使用Lightning接口传输数据");
    }
}

/**
 * 适配器类
 * 下例是Lightning转Typec的适配器。
 */
class AdapterL2C extends Typec{
    Lightning ln = new Lightning();
    
    @Override
    public void typecCharge() {
        ln.lightningCharge();;
    }
    @Override
    public void typecDataTrans() {
        ln.lightningDataTrans();
    }
}
复制代码

桥接模式

模式说明

当一个事物可以通过多个维度描述时,要避免多个维度层层继承该事物的抽象类,而应该将这些不同的维度从事物中分离出来独立变化。例如电脑可以通过不同品牌和不同软件描述,可以将电脑作为一个PC抽象类,以品牌为主体,不同品牌继承该PC类如ApplePC。软件分离出来成为一个独立的抽象类Software,具体软件继承该Software抽象类,例如Browser类。在PC抽象类中以组合形式拥有Software,并通过调用Software的方法,实施Software中的行为。在UML图中分离事物抽象类通过带组合箭头的线与主体事物抽象类连接,故名桥接。

本示例演示上述PC场景。如果希望增加一个华为品牌PC只需增加继承PC类的HuaweiPC类即可,无需改动其他类。同理希望增加视频软件时,也只需要增加VideoPlayer类而无需关注品牌。这就是各自独立变化。另,本示例中主体事物PC只持有一个Software实例,如果想同时持有多个Software实例,可以声明为集合类型(如List),然后增加相应的add方法。

结构

抽象主体事物类

  主体事物,类内声明一个以protected修饰的分离事物(以抽象类型声明)。声明一个用于实施分离事物行为的抽象方法。

具体主体事物类

  继承抽象主体事物类,实现抽象方法。

抽象分离事物类

  能够描述主体事物的某个维度的事物,声明自己的行为。

具体分离事物类

  继承抽象分离事物类,实现抽象方法。

代码演示

package com.yukiyama.pattern.structure;

/**
 * 桥接模式
 */
public class BridgeDemo {

    public static void main(String[] args) {
        // 声明一个PC(主体事物)
        PC pc = new ApplePC();
        // 声明一个软件(分离事物)
        Software soft1 = new Browser();
        // 将软件组合进PC中(主体持有分离)
        pc.setSoftware(soft1);
        // 通过调用主体事物的方法来间接执行分离事物的方法
        // 输出"启动: Browser"
        pc.run();
        // 增加一个软件后,同上
        Software soft2 = new MusicPlayer();
        pc.setSoftware(soft2);
        // 输出"启动: MusicPlayer"
        pc.run();
    }

}

/**
 * 主体事物抽象类
 * 以PC为例,内部声明protected修饰的分离事物Software。非抽象方法
 * setSoftware()传入分离事物使得主体持有分离。声明用于实施Software
 * 行为抽象方法run()。
 */
abstract class PC{
    protected Software soft;
    
    public void setSoftware(Software soft) {
        this.soft = soft;
    }
    public abstract void run();
}

/**
 * 主体事物具体类
 * 继承抽象主体事物类,实现抽象方法run,内部实际调用Software自身的run。
 */
class ApplePC extends PC{
    @Override
    public void run() {
        this.soft.run();
    }
}

/**
 * 抽象分离事物类
 * 有自身的属性和方法。
 * 下例是从软件维度描述PC的软件抽象类。
 */
abstract class Software{
    private String name;
    
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public abstract void run();
}

/**
 * 具体分离事物类
 * 继承抽象分离事物类,实现抽抽象方法。
 * 下例是浏览器类。
 */
class Browser extends Software{
    public Browser() {
        this.setName("Browser");
    }
    @Override
    public void run() {
        System.out.println("启动: "+this.getName());
    }
}

/**
 * 具体分离事物类
 * 下例是音乐播放器类。
 */
class MusicPlayer extends Software{
    public MusicPlayer() {
        this.setName("MusicPlayer");
    }
    @Override
    public void run() {
        System.out.println("启动: "+this.getName());
    }
}
复制代码

装饰器模式

模式说明

使用组合关系来创建一个可以被层层包装的对象(即装饰对器象)来包裹被装饰的主体对象,并在保持主体对象的类结构不变的前提下,通过装饰器对象自己的方法,为被装饰的主体对象提供额外的功能(装饰)。

本示例以技术为抽象主体类(TechComponent),一项核心技术为具体主体类(JavaComponent)。以其他扩展技术为抽象装饰类(TechDecorator),其具体装饰类为web技术(WebTechDecorator)和Spring技术(SpringTechDecorator)。演示如何在核心技术上扩展web和Spring技术。

结构

主体接口

  定义一种行为方法。

具体主体类

  实现抽象主体接口,实现抽象方法。

抽象装饰器类

  装饰部件抽象类,实现抽象主体接口,实现抽象方法。

具体装饰器类

  继承抽象装饰类,拥有自己的行为方法的同时实现抽象方法,并在其中调用自己的行为方法以及完成其所装饰的主体的行为方法。即所谓在保持主体对象不变的前提下,为其提供额外功能。

代码演示

package com.yukiyama.pattern.structure;

/**
 * 装饰器模式
 */
public class DecoratorDemo {

    public static void main(String[] args) {
        // 声明一个主体com
        Functionable com = new JavaComponent();
        // 声明修饰对象deco1和deco2
        TechDecorator deco1 = new WebTechDecorator();
        TechDecorator deco2 = new SpringTechDecorator();
        // 此时主体为com,要在其上装饰deco1,为deco1传入com
        deco1.decorate(com);
        // 此时的主体为deco1,要在其上装饰deco2,为deco2传入deco1
        deco2.decorate(deco1);
        // 调用最后一层装饰对象的func(),会依次调用之前的所有装饰对象的
        // func()方法,最终调用到原始主体com的func()方法
        deco2.func();
        // 修饰顺序不同,调用的装饰器的功能顺序也不同
    }

}

/**
 * 主体接口
 * 声明主体的行为方法show()
 */
interface Functionable{
    void func();
}

/**
 * 具体主体类
 * 实现抽象主体接口。
 */
class JavaComponent implements Functionable{
    private final String tech = "JavaSE";
    @Override
    public void func() {
        System.out.printf("展示主体技术%s的技术内容。\n", tech);
    }
}

/**
 * 抽象装饰器类
 * 实现抽象主体接口,持有一个被本装饰器类装饰前的主体类,实现一个非抽象装饰
 * 方法addTech传入被修饰对象,一个抽象的主体行为方法。
 */
abstract class TechDecorator implements Functionable{
    protected Functionable component;
    
    public void decorate(Functionable component) {
        this.component = component;
    }
    // 抽象类实现接口,可以不必实现接口内的抽象方法
    @Override
    public abstract void func();
}

/**
 * 具体装饰器类
 * 继承抽象装饰器类,拥有自己的行为方法,实现抽象方法的时候调用自己的行为
 * 方法并完成其所装饰的主体的行为方法。
 * 下例是Web技术。
 */
class WebTechDecorator extends TechDecorator{
    private final String tech = "WebTech";
    
    // 重写func()并在其中执行自身功能,即装饰效果
    @Override
    public void func() {
        myFunc();
        component.func();
    }
    // 具体装饰类自身的功能
    public void myFunc() {
        System.out.printf("展示扩展技术%s的技术内容。\n", tech);
    }
}

/**
 * 具体装饰器类
 * 下例是Spring技术。
 */
class SpringTechDecorator extends TechDecorator{
    private final String tech = "SpringTech";
    
    // 重写func()并在其中执行自身功能,即装饰效果
    @Override
    public void func() {
        myFunc();
        component.func();
    }
    // 具体装饰类自身的功能
    public void myFunc() {
        System.out.printf("展示扩展技术%s的技术内容。\n", tech);
    }
}
复制代码

外观模式

模式说明

当客户端需要通过调用多个对象各自不同的方法时,可以建立一个持有所有这些对象的外观类,根据客户端对上述多个对象的需要,外观类内有多个方法,这些方法内部调用多个不同对象的方法以满足客户端的不同需求。如此客户端就只需要调用外观类的方法,而无需访问多个对象。例如投资者买股票,投资者是客户端,不同公司的股票形成数量众多的对象。此时投资者可以通过基金经理来间接买股票,基金经理就是外观类,他通过不同的投资策略封装了多个股票的买入和卖出等方法,投资者只需要调用基金经理不同的策略方法即可,无需了解每一支股票。

本示例以上述投资者买股票场景为例,演示外观模式的使用。

结构

外观类

  持有所有子系统的信息,并为客户端提供了一系列访问子系统的方法,这些方法内根据需要调用一个或多个子系统的方法。

子系统类

  可以有多个子系统,每个子系统都有各自的方法。

代码演示

package com.yukiyama.pattern.structure;

/**
 * 外观模式
 */
public class FacadeDemo {

    public static void main(String[] args) {
        // 声明一个外观类
        Fund f = new Fund();
        System.out.println("====执行基金策略1====");
        // 执行既定策略1
        f.strategy1();
        System.out.println("====执行基金策略2====");
        // 执行既定策略2
        f.strategy2();
    }

}

/**
 * 外观类
 * 下例为基金,持有三支股票对象,有两个策略方法,封装对三只股票的不同行为组合。
 */
class Fund{
    private StockApple sa = new StockApple();
    private StockMaotai sm = new StockMaotai();
    private NationalDebt nd = new NationalDebt();
    
    public void strategy1() {
        sa.buy();
        sm.toYuebao();
        nd.sell();
    }
    
    public void strategy2() {
        sa.sell();
        sm.sell();
        nd.buy();
    }
}

/**
 * 子系统类
 * 苹果公司股票
 */
class StockApple{
    public void sell() {
        System.out.println("卖出Apple股票");
    }
    public void buy() {
        System.out.println("买入Apple股票");
    }
}

/**
 * 子系统类
 * 茅台股票
 */
class StockMaotai{
    public void toYuebao() {
        System.out.println("Maotai股票转入余额宝");
    }
    public void sell() {
        System.out.println("买入Maotai股票");
    }
}

/**
 * 子系统类
 * 国债
 */
class NationalDebt{
    public void sell() {
        System.out.println("卖出国债");
    }
    public void buy() {
        System.out.println("买入国债");
    }
}
复制代码

享元模式

模式说明

某些对象相似度很高,只有少量字段或方法不同,当需要大量这类对象时,若为每一次需求都创建实例,内存开销会很大。此时可以提炼出这些对象的相同部分,作为所谓的享元,创建一个享元工厂类,持有享元实例。需要使用上述对象时,从享元工厂中获取该实例。若这些对象有不同部分,提取这些不同部分单独成类,使用时作为享元中的方法参数传入享元(使相同和不同部分结合)。这样无论使用多少次,对于享元而言,只有一个实例的开销。

本示例以网站为抽象享元,博客网站和电子商务网站为具体享元,用户为外部非享元。展示如何通过享元工厂类添加和获取享元,如何将非享元作为享元方法的参数传入到享元内。

结构

抽象享元类

  被使用对象中提炼出的相同内容的集合,定义享元的抽象方法。

具体享元类

  继承抽象享元类,实现抽象方法。

非享元类

  需要使用但不能被提炼为享元内容的部分,单独成类,需要时传入享元。

享元工厂类

  持有具体享元实例,有添加享元和获取享元的方法。

代码演示

package com.yukiyama.pattern.structure;

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

/**
 * 享元模式
 */
public class FlyweightDemo {

    public static void main(String[] args) {
        // 声明享元工厂
        WebsiteFactory fa = new WebsiteFactory();
        // 声明具体享元博客网站和电子商务网站
        Website blog = new BlogWebsite("Blog");
        Website ec = new BlogWebsite("EC");
        // 向享元工厂中添加如上两种享元
        fa.addFlyweight(blog);
        fa.addFlyweight(ec);
        // 通过享元工厂,创建两个博客网站和两个电子商务网站
        Website blog1 = fa.getFlyweight("Blog");
        Website blog2 = fa.getFlyweight("Blog");
        Website ec1 = fa.getFlyweight("EC");
        Website ec2 = fa.getFlyweight("EC");
        // 将两个博客网站和两个电子商务网站分给不同的使用者
        blog1.use(new User("莫小言"));
        blog2.use(new User("金大庸"));
        ec1.use(new User("马风"));
        ec2.use(new User("刘强西"));
        // 如下均输出“true”,即通过享元工厂获取的多个享元以及
        // 工厂内持有的享元均为同一个。
        System.out.println(blog == blog1 && blog1 == blog2);
        System.out.println(ec == ec1 && ec1 == ec2);
    }

}

/**
 * 享元工厂类
 * 以Map数据结构持有享元,key为网站类型,value为Website实例。
 * 实现添加享元,获取享元的方法。
 * 客户端声明享元工厂后,需要继续创建享元并将其添加进享元工厂中。
 */
class WebsiteFactory{
    private Map<String, Website> flyweights = new HashMap<>();
    
    public void addFlyweight(Website web) {
        if(!flyweights.containsKey(web.getCatagory())) {
            flyweights.put(web.getCatagory(), web);
        } else {
            System.out.println("已存在该享元。");
        }
    }
    
    public Website getFlyweight(String key) {
        if(!flyweights.containsKey(key)) {
            System.out.println("无此享元。");
            return null;
        } else {
            return flyweights.get(key);
        }
    }
    
}

/**
 * 享元抽象类
 * 声明享元的字段和相关方法。
 */
abstract class Website{
    private String catagory;
    
    public Website(String catagory) {
        this.catagory = catagory;
    }
    public String getCatagory() {
        return catagory;
    }
    public abstract void use(User user);
}

/**
 * 具体享元类
 * 继承享元抽象类,实现抽象方法。
 * 如下是博客网站类。
 */
class BlogWebsite extends Website{
    public BlogWebsite(String catagory) {
        super(catagory);
    }
    @Override
    public void use(User user) {
        System.out.printf("这是一个%s网站,提供文章发布服务。\n", getCatagory());
        System.out.printf("网站用户为%s。\n", user.getUser());
    }
}

/**
 * 具体享元类
 * 如下是电子商务网站类。
 */
class ECWebsite extends Website{
    public ECWebsite(String catagory) {
        super(catagory);
    }
    @Override
    public void use(User user) {
        System.out.printf("这是一个%s网站,提供商品发布服务。\n", getCatagory());
        System.out.printf("网站用户为%s。\n", user.getUser());
    }
}

/**
 * 非享元类
 * 享元Website需要结合非享元User使用,例如对博客网站来说,他们可能共用
 * 相同的文章编辑器控件(作为享元的一部分),但各自使用的用户不同(非享元)。
 */
class User{
    private String user;
    
    public User(String user) {
        this.user = user;
    }
    public String getUser() {
        return user;
    }
}
复制代码

组合模式

模式说明

一系列对象可以用树来描述,形成树-子树-叶子的层次机构。如一家大型公司,根节点是总公司,下设若干子公司和若干非公司部门如总公司财经部,总公司人力资源部门。子公司中如同总公司结构,也包含它自己的子公司和非公司部门。则子公司对象是所属公司对象的子树,非公司部门对象是所属公司对象的叶子。客户端对单个对象和组合对象具有一致的访问性。即上层公司A(根)和其子公司B(树枝或子树),其非子公司部门C(树叶)继承同一个抽象构件类,有相同的方法。但因为树叶不再有子节点,根据树叶中是否包含针对子节点的方法(例如add增加儿子,removegetChild等),分为透明方式和安全方式。

本示例以总公司和子公司场景为例,演示透明方式的组合模式。在客户端中将树枝和树叶机构加入到根(总公司)中,然后调用总公司的方法,实现对其下属机构的递归调用。

透明方式

树叶与树枝具有同样的,在抽象构件类中声明的针对子节点的方法时,对客户端而言,一个对象是树枝还是树叶是透明的,客户端无需关心。这种方式下,树叶无儿子,却仍需实现针对儿子的各种方法(空方法或抛出异常),存在安全性问题。

安全方式

与透明方式相对,不在抽象构件类和树叶实现类中声明针对儿子的方法,只在树枝中实现。缺点是客户端需要提前知道哪些对象是树枝,哪些是树叶,失去透明性。

结构

抽象构件类

  树枝和树叶的抽象类,声明了公共抽象方法,实现默认行为。

具体实现树枝类

  继承抽象构件类,具有儿子的对象,包含针对儿子的各种方法。

具体实现树叶类

  继承抽象构件,不具有儿子的对象。若包含针对儿子的各种方法为透明方式,不包含则为安全模式。

代码演示

package com.yukiyama.pattern.structure;

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

/**
 * 组合模式
 * 一系列对象可以用树来描述,形成树-子树-叶子的层次机构。如一家大型公司,
 * 根节点是总公司,下设若干子公司和若干非公司部门如总公司财经部,总公司
 * 人力资源部门。子公司中如同总公司结构,也包含它自己的子公司和非公司部门。
 * 则子公司对象是所属公司对象的子树,非公司部门对象是所属公司对象的叶子。
 * 客户端对单个对象和组合对象具有一致的访问性。即上层公司A(根)和其子公司B
 * (树枝或子树),其非子公司部门C(树叶)继承同一个抽象构件类,有相同的方法。
 * 但因为树叶不再有子节点,根据树叶中是否包含针对子节点的方法(例如add
 * 增加儿子,remove,getChild等),分为透明方式和安全方式。
 * 
 * 本示例以总公司和子公司场景为例,演示透明方式的组合模式。在客户端中将
 * 树枝和树叶机构加入到根(总公司)中,然后调用总公司的方法,实现对其下属
 * 机构的递归调用。
 * 
 * 透明方式
 * 树叶与树枝具有同样的,在抽象构件类中声明的针对子节点的方法时,对客户端
 * 而言,一个对象是树枝还是树叶是透明的,客户端无需关心。这种方式下,树叶
 * 无儿子,却仍需实现针对儿子的各种方法(空方法或抛出异常),存在安全性问题。
 * 安全方式
 * 与透明方式相对,不在抽象构件类和树叶实现类中声明针对儿子的方法,只在树枝
 * 中实现。缺点是客户端需要提前知道哪些对象是树枝,哪些是树叶,失去透明性。
 * 
 * 结构
 * 抽象构件类
 *   树枝和树叶的抽象类,声明了公共抽象方法,实现默认行为。
 * 具体实现树枝类
 *   继承抽象构件类,具有儿子的对象,包含针对儿子的各种方法。
 * 具体实现树叶类
 *   继承抽象构件,不具有儿子的对象。若包含针对儿子的各种方法为透明方式,
 *   不包含则为安全模式。
 */
public class CompositeDemo {

    public static void main(String[] args) {
        // 声明上海总公司
        Company hq = new ConcreteCompany();
        hq.setName("上海总公司");
        // 声明上海总公司的叶子机构并添加到总公司中
        Company hqHR = new HRDepartment();
        hqHR.setName("上海总公司人力资源部");
        hq.add(hqHR);
        Company hqFi = new FinanceDepartment();
        hqFi.setName("上海总公司财务部");
        hq.add(hqFi);
        
        // 声明上海总公司的树枝机构广州分公司,并完成其下属机构的添加
        Company gzSub = new ConcreteCompany();
        gzSub.setName("广州分公司");
        Company gzHR = new HRDepartment();
        gzHR.setName("广州分公司人力资源部");
        gzSub.add(gzHR);
        Company gzFi = new FinanceDepartment();
        gzFi.setName("广州分公司财务部");
        gzSub.add(gzFi);
        
        // 将广州分公司添加到上海总公司
        hq.add(gzSub);
        // 调用总公司hq的display()方法,递归调用其下分支机构的display()方法
        System.out.println("====机构====");
        hq.display();
        // 调用总公司hq的duty()方法,递归调用其下分支机构的duty()方法
        System.out.println("====职责====");
        hq.duty();
    }

}

/**
 * 抽象构件类
 * 持有实例字段和非抽象实例方法,定义抽象方法。
 * 下例以公司为抽象构件类。
 */
abstract class Company{
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public abstract void add(Company com);
    public abstract void remove(Company com);
    public abstract void display();
    public abstract void duty();
}

/**
 * 树枝类
 * 继承抽象构件类,比抽象类多一个用于保存儿子的List,实现抽象方法。
 */
class ConcreteCompany extends Company{
    private List<Company> subs = new ArrayList<>();
    
    @Override
    public void add(Company com) {
        subs.add(com);
    }
    @Override
    public void remove(Company com) {
        subs.remove(com);
    }
    @Override
    public void display() {
        System.out.println(this.getName());
        for(Company com : subs) {
            com.display();
        }
    }
    @Override
    public void duty() {
        System.out.printf("%s,统筹公司所有事务。\n", this.getName());
        for(Company com : subs) {
            com.duty();
        }
    }
}

/**
 * 树叶类
 * 继承抽象构件类,实现抽象方法,但对于针对儿子的方法,被调用时打印不支持
 * 操作的提示。
 * 下例是HR部门类。
 */
class HRDepartment extends Company{
    @Override
    public void add(Company com) {
        System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
    }
    @Override
    public void remove(Company com) {
        System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
    }
    @Override
    public void display() {
        System.out.println(this.getName());
    }
    @Override
    public void duty() {
        System.out.printf("%s,负责公司员工招聘薪酬管理。\n", this.getName());
    }
}

/**
 * 树叶类
 * 下例是财务部门类。
 */
class FinanceDepartment extends Company{
    @Override
    public void add(Company com) {
        System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
    }
    @Override
    public void remove(Company com) {
        System.out.printf("%s无子机构,不支持此操作。\n", this.getName());
    }
    @Override
    public void display() {
        System.out.println(this.getName());
    }
    @Override
    public void duty() {
        System.out.printf("%s,负责公司财务管理。\n", this.getName());
    }
}
复制代码

<-本篇结束->

这是第二篇,结构型篇,其他两篇文章地址如下:

23种设计模式讲解之创建型篇(Java实现并附保姆级注释)

23种设计模式讲解之行为型篇(Java实现并附保姆级注释)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享