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

272 阅读22分钟

前言

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

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

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

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

这是第一篇,创建型篇,其他两篇文章地址如下:

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

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

创建型模式

描述如何创建一个对象实例的模式,包括如下:

  • 单例模式
  • 原型模式
  • 简单工厂模式 (不在GoF23种之列)
  • 工厂方法模式
  • 抽象工厂模式
  • 建造者模式

单例模式

模式说明

某些场景要求一个对象只能有一个实例,可以通过单例模式实现。由于只能有一个实例,因此该类的构造器必须用private修饰来禁止通过new操作符来新建实例。类内持有一个private修饰的本类类型的实例,又因为只能有一个实例,所以使其为类成员,如该类类名Singleton,可声明为:

private static Singleton instance;

通过类内的一个public修饰的getIntance方法来获取实例,该方法也是static的。根据实例产生的时机分为饿汉模式和懒汉模式,懒汉模式中有线程安全和不安全的写法。

饿汉模式

类属性Singleton instance在声明时就实例化。

private static Singleton instance = new Singleton();

懒汉模式

类属性Singleton只声明但不实例化,将实例化动作放到getInstance方法中,则只有调用getInstance时才能获取Singleton的实例。懒汉模式因为在调用getInstance时才获得实例,存在多线程竞争的问题,因此可以结合volatile(避免指令重排)和synchronized(保证原子性)以双锁检测方式将Singleton写成线程安全的类。

注:关于双锁检测,不是模式本身的要点,这里略过不讲,可以参考周志明的《深入理解深入理解Java虚拟 第3版》的12.3.3节 "对于volatile型变量的特殊规则"。

本示例演示饿汉写法,非线程安全懒汉写法和双锁检测线程安全写法。

结构

单例类

  持有一个本类的类属性,维护一个getInstance方法。

代码演示

package com.yukiyama.pattern.creation;

/**
 * 单例模式
 */
public class SingletonDemo {

    public static void main(String[] args) {
        SingletonSimple s1 = SingletonSimple.getInstance();
        SingletonSimple s2 = SingletonSimple.getInstance();
        // 输出“true”,说明s1与s2是同一个实例
        System.out.println(s1 == s2);
        SingletonHungry s3 = SingletonHungry.getInstance();
        SingletonHungry s4 = SingletonHungry.getInstance();
        // 输出“true”,说明s3与s4是同一个实例
        System.out.println(s3 == s4);
        Singleton s5 = Singleton.getInstance();
        Singleton s6 = Singleton.getInstance();
        // 输出“true”,说明s3与s4是同一个实例
        System.out.println(s5 == s6);
    }

}

/**
 * 饿汉模式
 * 在类加载时创建常量化实例,不存在多线程导致可能出现多个实例的问题
 */
class SingletonHungry{
    // 在定义SingletonHungry类型的属性时直接实例化,类内可以访问private构造器
    private static final SingletonHungry instance = new SingletonHungry();
    
    // 将构造器声明为private,外部无法用new获取
    private SingletonHungry() {}
    // 外部通过一个public的getInstance()方法获取该类实例
    public static SingletonHungry getInstance() {
        return instance;
    }
}

/**
 * 多线程下的双锁检测(Double-Check Locking)单例
 * 懒汉模式
 */
class Singleton{
    // 以volatile修饰
    private static volatile Singleton instance;
    
    private Singleton() {}
    public static Singleton getInstance() {
        // 第一次判断的目的是避免每次getInstance()都加锁
        // 若已经存在实例,直接返回
        if(instance == null) {
            synchronized (Singleton.class) {
                // 再次判断是防止两个线程在instance==null时
                // 同时进入第一个if内,由于加锁,其中一个先new了
                // 实例,此时必须再判断一次防止第二个也new一个实例
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

/**
 * 懒汉模式
 * 非线程安全版
 */
class SingletonSimple {
    private static SingletonSimple instance;
    
    // 将构造器声明为private,外部无法用new获取
    private SingletonSimple() {}
    // 外部通过一个public的getInstance()方法获取该类实例
    public static SingletonSimple getInstance() {
        // 每次获取前判断该类实例是否已存在,若无则new一个
        if (instance != null) {
            instance = new SingletonSimple();
        }
        return instance;
    }

}

原型模式

模式说明

需要获取一个类的多个实例的场景下,若该类的构造器需要做较多的初始化工作,则创建多个实例会耗时耗资源。可以在创建一个实例后,后续实例都直接拷贝该实例可以避免每次获取实例时的初始化开销,对于需要修改的字段针对性修改即可,这就是原型模式。根据对引用类型字段的拷贝程度分为浅拷贝和深拷贝。

浅拷贝

对原型P的实例p1执行实例p1.clone()方法拷贝给p2时,对p1内的引用类型字段(准确地说是可变引用类型)q(类型Q),只拷贝其引用。Java中重写clone方法时若只调用super.clone(),则该clone方法在使用时即为浅拷贝,即上述p1.q == p2.q。注意clone方法在Cloneable接口中是native方法,返回类型是Object,赋值给p2时要向下转型。

P p1 = new P();
P p2 = (P)p1.clone();

可以这样描述以上两条语句。p1.clone()在堆中生成一个新的实例,p2是这个新实例的引用(地址),新实例中有p1的基本数据和不变类型(final修饰的引用类型如String)的完整拷贝,但对于可变的引用类型,只拷贝其引用。即如果p1中有一可变引用字段Q,那么p1.q == p2.q,p1p2中的q指向同一个实例,即原本p1中的q实例。

深拷贝

以浅拷贝中的描述为例,深拷贝对p中的可变引用类型q,也拷贝一个完整的q到新的地址中(与q并列的Q类型新实例),此时p1.q !=p2.q。深拷贝要求Q自身也要实现Cloneable接口并重写clone方法。即在原型P的重写clone方法内针对q要有类似如下的语句。

p.q = (Q)p.getQ().clone();

Q中还有引用类型R,则R也要实现Cloneable接口并重写clone方法,以此类推。

本示例定义一个Person类,使其实现Cloneable接口并通过重写clone方法使其支持浅拷贝。在Person类中定义一个不变引用类型Country和一个非不变引用类型WorkExperience,展示浅拷贝Person时,对不变类型Country拷贝出一个新实例,对非不变引用类型WorkExperience拷贝的是它的引用(地址)。作为对比,本示例定义一个PersonDeep类,该类内有一个非不变引用类型WorkExperienceDeep,使WorkExperienceDeep也实现Cloneable接口并重写clone方法,且在PersonDeep类的clone方法中,针对非不变引用l类型WorkExperienceDeep,执行一条对它的clone方法来得到它的一个实例,从而实现对PersonDeep的深拷贝。

结构

原型接口(通常就是Cloneable)

具体原型类

  实现Cloneable接口并重写clone方法,分为深拷贝和浅拷贝。

代码演示

package com.yukiyama.pattern.creation;

/**
 * 原型模式
 */
public class ProtoTypeDemo {

    public static void main(String[] args) {
        System.out.println("====如下是深拷贝示例====");
        // 声明一个支持深拷贝的实例p1
        PersonDeep pd1 = new PersonDeep();
        // 通过原型类中的clone()方法拷贝p1
        PersonDeep pd2 = (PersonDeep) pd1.clone();
        // p1和p2是两个不同的实例,输出“false”
        System.out.println(pd1 == pd2);
        // 设置p1
        pd1.setName("张三");
        pd1.setOccupation("程序员");
        pd1.setWorkExperienceDeep("Alibaba");
        // 设置拷贝而来的p2
        pd2.setName("李四");
        pd2.setOccupation("老师");
        pd2.setWorkExperienceDeep("Tsinghua");
        // 以下3行分别输出“张三”, “程序员”, “Alibaba”
        // WorkExperienceDeep没有被pd2修改为"Tsinghua",说明PersonDeep是深拷贝类型
        System.out.println(pd1.getName());
        System.out.println(pd1.getOccupation());
        System.out.println(pd1.getWorkExperience().getCompany());
        // 以下3行分别输出“李四”, “老师”, “Tsinghua”
        System.out.println(pd2.getName());
        System.out.println(pd2.getOccupation());
        System.out.println(pd2.getWorkExperience().getCompany());
        // 如下输出“false”,说明p1和p2中的引用类型WorkExperience是不同的实例
        System.out.println(pd1.getWorkExperience() == pd2.getWorkExperience());
        
        System.out.println("====如下是浅拷贝示例====");
        Person p1 = new Person();
        Person p2 = (Person) p1.clone();
        // p1和p2是两个不同的实例,输出“false”
        System.out.println(p1 == p2);
        // 设置p1
        p1.setName("张三");
        p1.setOccupation("程序员");
        p1.setWorkExperience("Alibaba");
        p1.setCountry("CHINA");
        // 设置拷贝而来的p2
        p2.setName("李四");
        p2.setOccupation("老师");
        p2.setWorkExperience("Tsinghua");
        p2.setCountry("SINGAPORE");
        // 以下4行分别输出“张三”, “程序员”, “Tsinghua”,“CHINA”
        // WorkExperience被p2修改为"Tsinghua",说明Person是浅拷贝类型
        // 另外,p1中的CHINA并没有因为p2设置为了SINGAPORE而受到影响,
        // 说明自定义的不变类Country在浅拷贝中也拷贝到了新实例,而不是只拷贝其引用
        System.out.println(p1.getName());
        System.out.println(p1.getOccupation());
        System.out.println(p1.getWorkExperience().getCompany());
        System.out.println(p1.getCountry());
        // 以下4行分别输出“李四”, “老师”, “Tsinghua”,“SINGAPORE”
        System.out.println(p2.getName());
        System.out.println(p2.getOccupation());
        System.out.println(p2.getWorkExperience().getCompany());
        System.out.println(p2.getCountry());
        // 如下输出“true”,说明p1和p2中的引用类型WorkExperience是相同的实例
        System.out.println(p1.getWorkExperience() == p2.getWorkExperience());
        // 如下输出“false”,说明p1和p2中的引用类型WCountry是不同的实例
        System.out.println(p1.getCountry() == p2.getCountry());
    }
}

/**
 * 深拷贝版
 * 原型类
 * 实现Cloneable接口并重写clone方法
 * 本类中的非不变引用类型字段也通过该字段自身的clone()进行拷贝
 */
class PersonDeep implements Cloneable{
    private String name;
    private String occupation;
    private int age;
    private WorkExperienceDeep workExperience;
    
    // 在构造器中实例化引用类型WorkExperience
    public PersonDeep() {
        workExperience = new WorkExperienceDeep();
    }
    
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getOccupation() {
        return occupation;
    }
    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }
    public WorkExperienceDeep getWorkExperience() {
        return workExperience;
    }
    public void setWorkExperienceDeep(String company) {
        this.workExperience.setCompany(company);
    }
    // 重写clone()使得原型类Person能够执行clone()从而被拷贝
    @Override
    protected Object clone() {
        PersonDeep person = null;
        try {
            person = (PersonDeep) super.clone();
            person.workExperience = (WorkExperienceDeep) person.getWorkExperience().clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }
}

/**
 * 深拷贝版
 * 原型类型中的引用类型字段
 */
class WorkExperienceDeep implements Cloneable{
    private String company;

    public String getCompany() {
        return company;
    }
    public void setCompany(String company) {
        this.company = company;
    }
    @Override
    protected Object clone() {
        WorkExperienceDeep we = null;
        try {
            we = (WorkExperienceDeep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return we;
    }
}

/**
 * 浅拷贝版
 * 原型类,实现Cloneable接口并重写clone方法
 */
class Person implements Cloneable{
    private String name;
    private String occupation;
    private int age;
    private WorkExperience workExperience;
    private Country country;
    
    // 在构造器中实例化引用类型WorkExperience
    public Person() {
        workExperience = new WorkExperience();
    }
    
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getOccupation() {
        return occupation;
    }
    public void setOccupation(String occupation) {
        this.occupation = occupation;
    }
    public WorkExperience getWorkExperience() {
        return workExperience;
    }
    public String getCountry() {
        return country.getCountryName();
    }
    public void setCountry(String countryName) {
        this.country = new Country(countryName);
    }

    public void setWorkExperience(String company) {
        this.workExperience.setCompany(company);
    }
    // 重写clone()使得原型类Person能够对外拷贝
    @Override
    protected Object clone() {
        Person person = null;
        try {
            person = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return person;
    }
}

/**
 * 浅拷贝版
 * 原型类型中的引用类型字段
 */
class WorkExperience{
    private String company;

    public String getCompany() {
        return company;
    }
    public void setCompany(String company) {
        this.company = company;
    }
}

/**
 * 浅拷贝版
 * 自定义不变类,用于演示在浅拷贝中对不变类的拷贝结果
 */
class Country{
    private String countryName;
    
    public Country(String countryName) {
        this.countryName = countryName;
    }
    public String getCountryName() {
        return countryName;
    }
}

简单工厂模式

模式说明

当客户端需要根据某个变量的不同值来声明不同的对象时,可以使用简单工厂模式。该模式设置了一个生产产品(前述的对象)的工厂类,此工厂类通过静态方法(所以称为静态工厂),传入变量参数,方法体内有选择分支(if-elseswitch-case),根据参数返回具体的产品。举个例子,天气是变量,客户端根据天气的不同情况,令服装工厂生产不同的服装,下雨生产雨衣,低温天气生产羽绒服,高温天气生产泳衣等等。在这个例子中服装工厂就是静态工厂,不同的雨衣是不同的具体产品。天气情况就是静态方法的参数。让不同的产品都继承同一个抽象产品类,那么这个抽象产品类,具体的产品类和静态工厂类就形成了简单工厂的完整结构。

注:简单工厂模式不在GoF的23种设计模式之列。

当产品类有增删改时,需要在静态工厂类中修改选择分支,因此简单工厂模式不满足开闭原则。这个问题要留到工厂方法模式解决。

本示例以四则运算为例,演示客户端如何通过运算符从运算工厂中获取相应的运算产品,然后执行该产品的产品方法(执行运算)返回结果。

结构

静态工厂类

  维护一个静态方法,根据参数返回具体产品类的实例(返回类型是抽象产品类型)。

抽象产品类

  定义产品字段和方法(操作数a,b和运算方法result())

具体产品类

  继承抽象产品类,实现具体运算方法

代码演示

package com.yukiyama.pattern.creation;

/**
 * 简单工厂模式(静态工厂模式)
 */
public class StaticFactoryDemo {

    public static void main(String[] args) {
        // 通过静态工厂类静态方法返回一个具体产品。
        Operation oper = OperationFactory.operate("*");
        // 执行该产品的方法。输出“12”
        System.out.println(oper.result(3, 4));
    }

}

/**
 * 静态工厂类
 * 通过一个类静态方法,根据输入返回相应的工厂类。内部通常为if-else或
 * switch-case。
 */
class OperationFactory{
    public static Operation operate(String sign) {
        Operation oper = null;
        switch (sign) {
        case "+":
            oper = new OperationAdd();
            break;
        case "-":
            oper = new OperationSub();
            break;
        case "*":
            oper = new OperationMul();
            break;
        case "/":
            oper = new OperationDiv();
            break;
        default:
            System.out.println("不支持此运算。");
        }
        return oper;
    }
}

/**
 * 抽象产品类
 * 定义产品的抽象方法。
 * 下例以四则运算为产品,内有对两个int数的运算方法。
 */
abstract class Operation{
    public abstract int result(int a, int b);
}

/**
 * 具体产品类
 * 继承抽象产品类,实现抽象方法。
 * 下例为加法产品。
 */
class OperationAdd extends Operation{
    @Override
    public int result(int a, int b) {
        return a + b;
    }
}

/**
 * 具体产品类,减法
 */
class OperationSub extends Operation{
    @Override
    public int result(int a, int b) {
        return a - b;
    }
}

/**
 * 具体产品类,乘法
 */
class OperationMul extends Operation{
    @Override
    public int result(int a, int b) {
        return a * b;
    }
}

/**
 * 具体产品类,除法
 */
class OperationDiv extends Operation{
    @Override
    public int result(int a, int b) {
        if(b == 0) {
            System.err.println("除数不能为0.");
            throw new IllegalArgumentException();
        }
        return a / b;
    }
}

工厂方法模式

模式说明

在简单工厂模式中,当出现产品类的增删改时,均需要修改静态产品工厂类内的选择分支,破坏了开闭原则。针对这个问题,可以将生产不同产品的工厂也单独成类,他们继承同一个抽象工厂类。这样在出现新产品时只增加相应的产品类和生产他的工厂类,对其他代码均无需改动其他。等到要使用这个新工厂时,再在客户端中声明这个工厂的实例,由它来生产产品即可。这就是符合开闭原则的工厂方法模式。

本示例以四则运算为例,演示客户端如何声明一个具体的运算工厂并通过该工厂生产相应的运算产品,然后执行该产品的产品方法(执行运算)返回结果。

结构

抽象工厂类

  定义一个创建产品的抽象方法。

具体工厂类

  继承抽象工厂类,实现创建产品的抽象方法。

抽象产品类

  定义抽象产品方法。

具体产品类

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

代码演示

package com.yukiyama.pattern.creation;

/**
 * 工厂方法模式
 */
public class FactoryMethodDemo {

    public static void main(String[] args) {
        // 声明一个具体运算工厂
        OperFactory fa = new AddFactory();
        // 由该工厂提供运算产品
        Oper oper = fa.createOper();
        // 执行产品的运算方法得到结果,输出“7”
        System.out.println(oper.result(3, 4));
    }

}

/**
 * 抽象工厂类
 * 定义一个创建产品的抽象方法。
 */
abstract class OperFactory{
    public abstract Oper createOper();
}

/**
 * 具体工厂类
 * 继承抽象工厂类,实现创建产品的抽象方法。
 * 下例是加法工厂。
 */
class AddFactory extends OperFactory{
    @Override
    public Oper createOper() {
        return new OperAdd();
    }
}

/**
 * 具体工厂类
 * 下例是减法工厂。
 */
class SubFactory extends OperFactory{
    @Override
    public Oper createOper() {
        return new OperSub();
    }
}

/**
 * 下例是乘法工厂
 */
class MulFactory extends OperFactory{
    @Override
    public Oper createOper() {
        return new OperMul();
    }
}

/**
 * 下例是除法工厂
 */
class DivFactory extends OperFactory{
    @Override
    public Oper createOper() {
        return new OperDiv();
    }
}

/**
 * 抽象产品类
 * 定义抽象产品方法。
 * 本示例的产品为四则运算,产品方法为运算过程。
 */
abstract class Oper{
    public abstract int result(int a, int b);
}

/**
 * 具体产品类
 * 继承抽象产品类,实现抽象产品方法。
 * 下例是加法产品。
 */
class OperAdd extends Oper{
    @Override
    public int result(int a, int b) {
        return a + b;
    }
}

/**
 * 具体产品类
 * 下例是减法产品。
 */
class OperSub extends Oper{
    @Override
    public int result(int a, int b) {
        return a - b;
    }
}

/**
 * 具体产品类
 * 下例是乘法产品。
 */
class OperMul extends Oper{
    @Override
    public int result(int a, int b) {
        return a * b;
    }
}

/**
 * 具体产品类
 * 下例是除法产品。
 */
class OperDiv extends Oper{
    @Override
    public int result(int a, int b) {
        if(b == 0) {
            System.err.println("除数不能为0.");
            throw new IllegalArgumentException();
        }
        return a / b;
    }
}

抽象工厂模式

模式说明

工厂方法模式的扩展,结构相似,但工厂方法中的工厂类只能生产一种产品,抽象工厂的工厂类可以生产多种产品。形式上,若工厂模式中的抽象工厂类,有不止一个生产产品的抽象方法,且这些产品(方法返回类型)不同,具体工厂类实现该抽象工厂类时重写所有抽象方法,那它就是抽象工厂模式。

本示例以四则运算为例,演示客户端如何声明一个具体的运算工厂并获取多种不同的运算产品,然后这些产品执行其产品方法(执行运算)返回结果。

结构

抽象工厂类

  定义多个创建产品的抽象方法。有多少种产品就写多少个方法。

具体工厂类

  继承抽象工厂类,实现创建产品的抽象方法。

抽象产品类

  定义抽象产品方法。

具体产品类

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

代码演示

package com.yukiyama.pattern.creation;

/**
 * 抽象工厂模式
 */
public class AbstractFactoryDemo {

    public static void main(String[] args) {
        // 声明一个具体运算工厂,加法工厂
        IFactory fa = new OperationAddFactory();
        // 该加法工厂生产int型加法产品(运算)
        OperationInt operInt = fa.createOperationInt();
        // 该加法工厂生产另一种产品(运算),double型产品
        OperationDouble operDouble = fa.createOperationDouble();
        // int型产品执行其产品方法(执行运算)
        System.out.println(operInt.result(4, 5));
        // double型产品执行其产品方法(执行运算)
        System.out.println(operDouble.result(1.2, 1.7));
    }

}

/**
 * 抽象工厂类
 * 定义多个创建产品的抽象方法。有多少种产品就写多少个方法。
 */
abstract class IFactory{
    public abstract OperationInt createOperationInt();
    // 工厂方法模式的产品(运算)只有int型的,抽象工厂模式中多了double型产品
    public abstract OperationDouble createOperationDouble();
}

/**
 * 具体工厂类
 * 继承抽象工厂类,实现创建产品的抽象方法。
 * 下例是加法工厂。
 */
class OperationAddFactory extends IFactory{
    @Override
    public OperationInt createOperationInt() {
        return new OperationIntAdd();
    }
    @Override
    public OperationDouble createOperationDouble() {
        return new OperationDoubleAdd();
    }
}

/**
 * 抽象产品类
 * 定义抽象产品方法。
 * 下例是int类型运算产品的抽象类。
 */
abstract class OperationInt{
    public abstract int result(int a, int b);
}

/**
 * 抽象产品类
 * 下例是double类型运算产品的抽象类。
 */
abstract class OperationDouble{
    public abstract double result(double a, double b);
}

/**
 * 具体产品类
 * 继承抽象产品类,实现抽象产品方法。
 * 下例是int加法产品。
 */
class OperationIntAdd extends OperationInt {
    @Override
    public int result(int a, int b) {
        return a + b;
    }
}

/**
 * 具体产品类
 * 下例是double加法类。
 */
class OperationDoubleAdd extends OperationDouble {
    @Override
    public double result(double a, double b) {
        return a + b;
    }
}

建造者模式

模式说明

有一些对象,他们由多个部件构成,构成的方式相同,但每个部件在不同对象中可能有不同的特征细节。举例来说,无论是哪个品牌的电脑,都由CPU,显示器,硬盘内存,键盘等部件构成,只是对不同品牌不同型号的PC,部件的细节不同,例如苹果电脑的CPU是苹果自研的,联想电脑CPU是英特尔的。在客户端中完成对不同对象的建造,最直接的方式是分别声明声明他们,然后调用各自的建造动作来完成。这样的做法无法利用不同对象建造过程稳定且相同(只是细节不同)的特点,达不到代码复用的效果。针对这种场景,可以使用建造者模式来实现。

建造者模式提供一个抽象建造者类,类内定义了稳定的产品建造过程,即建造具体产品各部件的抽象方法。这样具体建造者继承抽象建造者类后,可以根据需要重写某些部件的建造方法。具体建造者类内持有一个产品类实例,用于保存建造完成后的产品,并通过他的getter的方法获取该产品的实例。展示产品细节的动作在产品类中完成。这就是所谓的将复杂对象的构造和表示分离,实际就是建造者类和产品类分离。至此,若客户端发起造,就可以声明一个具体建造类,并调用其建造过程(多个部件的build方法),但这样一来就对客户端暴露了建造过程,且加重了客户端的任务。所以另外设置一个指挥者类,该类只有一个produce方法,方法参数是具体建造者实例,传入该实例,在produce内部调用该实例建造各部件的方法。如此,客户端就只需要声明一个指挥者,再声明一个具体建造者,将具体建造者作为指挥者的produce方法参数,指挥者执行其produce完成建造。执行后需要查看得到的产品细节,再调用产品建造者的getProduce方法即可。

本示例以汽车生产为例,展示如何利用建造者模式来生产F1赛车和普通家用汽车。

结构

指挥者类

  提供一个指挥生产的方法produce,传入建造者,其内调用建造者的建造方法。

抽象建造者类

  提供建造产品各部件的抽象方法和一个返回产品的方法。

具体建造者类

  继承抽象建造者类,并持有一个产品实例以供获取。实现产品各部分抽象建造方法和返回产品的抽象方法。

产品类

  持有产品各部件的一个集合实例,有一个增加部件的add方法,一个用于展现组成部件的show方法。

代码演示

package com.yukiyama.pattern.creation;

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

/**
 * 建造者模式(生成器模式)
 */
public class BuilderDemo {

    public static void main(String[] args) {
        // 声明一个指挥者
        Director director = new Director();
        // 声明一个具体建造者
        CarMaker carMaker = new F1CarMaker();
        // 指挥者执行produce方法,传入具体建造者执行建造
        director.produce(carMaker);
        // 通过建造者的get方法得到建造后的产品
        Car car = carMaker.getCar();
        // 产品调用show方法展示建造后的产品细节
        car.show();
    }

}

/**
 * 指挥者类
 * 提供一个指挥生产的方法produce,传入具体建造者,执行既定的建造产品的方法
 * 完成产品制造。
 */
class Director{
    public void produce(CarMaker cm) {
        cm.buildWheels();
        cm.buildEngine();
        cm.buildSeats();
    }
}

/**
 * 抽象建造者类
 * 提供建造产品各部件的抽象方法和一个返回产品的方法。
 * 下例为一个抽象汽车制造者类。
 */
abstract class CarMaker{
    public abstract void buildWheels();
    public abstract void buildEngine();
    public abstract void buildSeats();
    public abstract Car getCar();
}

/**
 * 具体建造者类
 * 继承抽象建造者类,并持有一个产品实例,实现产品各部分抽象建造方法和返回
 * 产品的抽象方法(返回其持有的产品实例)。
 * 下例是F1赛车制造者
 */
class F1CarMaker extends CarMaker{
    private Car car = new Car();
    
    @Override
    public void buildWheels() {
        car.add("赛车轮胎");
    }
    @Override
    public void buildEngine() {
        car.add("赛车引擎");
    }
    @Override
    public void buildSeats() {
        car.add("赛车座椅");
    }
    @Override
    public Car getCar() {
        return car;
    }
}

/**
 * 具体建造者类
 * 按照该建造者的实际情况实现产品各部分建造方法和返回产品方法
 * 下例是家用车制造者
 */
class HomeCarMaker extends CarMaker{
    private Car car = new Car();
    
    @Override
    public void buildWheels() {
        car.add("家用车轮胎");
    }
    @Override
    public void buildEngine() {
        car.add("家用车引擎");
    }
    @Override
    public void buildSeats() {
        car.add("家用车座椅");
    }
    @Override
    public Car getCar() {
        return car;
    }
}

/**
 * 要建造的产品类
 * 持有产品各部件的集合List<String>,定义一个add方法用于添加构件。另定义
 * 一个show方法用于展示建造的产品细节。
 */
class Car{
    private List<String> parts = new ArrayList<>();
    
    public void add(String part) {
        parts.add(part);
    }
    public void show() {
        System.out.println(parts);
    }
}

<-本篇结束->

这是第一篇,创建型篇,其他两篇文章地址如下:

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

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