【学习笔记】设计模式之创建型模式

104 阅读10分钟

本文内容包含:单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式、建造者模式

1 单例模式

  保证某个类只存在一个对象实例,并且只提供一个获取其对象实例的方法。

1.1 饿汉式—静态

  • 构造器私有化
  • 在类的内部创建对象
  • 向外暴露一个静态的公共方法
public class MySingleton {
    // instance也可以在静态代码块里实例化
    private final static MySingleton instance = new MySingleton();

    // 构造器私有化,外部不能new
    private MySingleton() {}

    public static MySingleton getInstance() {
        return instance;
    }
}
  • 优点:写法比较简单,在类装载的时候就完成实例化。避免了线程同步问题
  • 缺点:在类装载的时候就完成实例化,没有懒加载的效果。如果从始至终都未使用过这个实例,则会造成内存的浪费

1.2 懒汉式—线程不安全

public class MySingleton {
    private static MySingleton instance;
    private MySingleton() {}

    /**
     * 当需要到instance时,采取创建,即懒汉式
     */
    public static MySingleton getInstance() {
        if (null == instance) {
            instance = new MySingleton();
        }
        return instance;
    }
}
  • 有懒加载的效果,但只能在单线程下使用
  • 在多线程下可能会产生多个实例,所以多线程环境下不能用这种方式

1.3 懒汉式—同步方法

public class MySingleton {
    private static MySingleton instance;
    private MySingleton() {}

    public static synchronized MySingleton getInstance() {
        if (null == instance) {
            instance = new MySingleton();
        }
        return instance;
    }
}
  • 这种写法虽然解决了线程问题,但效率低,因为在获取实例时也会上锁

1.4 懒汉式—双重检查*

public class MySingleton {
    /**
     * volatile关键字:
     *  1.能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象
     *  2.具有可见性和有序性
     */
    private static volatile MySingleton instance;
    private MySingleton() {}

    public static MySingleton getInstance() {
        if (null == instance) {
            synchronized (MySingleton.class) {
                if (null == instance) {
                    instance = new MySingleton();
                }
            }
        }
        return instance;
    }
}

1.5 静态内部类*

public class MySingleton {
    private MySingleton() {}

    /**
     * 静态内部类:
     *  1.当加载外部类MySingleton时,静态内部类SingletonInstance不会立即加载,做到了懒加载
     *  2.当调用getInstance()方法时,才会加载静态内部类,而加载过程是线程安全的
     */
    private static class SingletonInstance {
        private static final MySingleton INSTANCE = new MySingleton();
    }
    public static MySingleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}

1.6 枚举*

public enum MySingleton {
    INSTANCE;
}

2 简单工厂模式

  简单工厂模式(又称静态工厂模式):有一个工厂对象决定创建出某种产品类的实例;即定义了一个创建对象的类(工厂类),由这个类来封装实例化对象的行为,用户(使用类)不管工厂如何生产出产品。步骤如下

  • 创建抽象产品类
  • 创建具体产品类继承抽象产品类
  • 创建工厂类,通过创建静态方法根据传入不同参数从而创建不同具体产品类的实例
  • 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例

2.1 例子

  需求:模拟披萨店,用户输入披萨类型来购买对应的披萨。

1、编写Pizza抽象类

public abstract class Pizza {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 准备原材料,不同种类的披萨准备方法不同
     */
    public abstract void prepare();
    public void make() {
        System.out.println(name + "制作中...");
    }
    public void finish() {
        System.out.println(name + "制作完毕");
    }
}

2、编写培根披萨类和奶酪披萨类

public class BaconPizza extends Pizza {
    public BaconPizza() {
        this.setName("培根披萨");
    }

    @Override
    public void prepare() {
        System.out.println(this.getName() + "准备材料中...");
    }
}

public class CheesePizza extends Pizza {
    public CheesePizza() {
        this.setName("奶酪披萨");
    }

    @Override
    public void prepare() {
        System.out.println(this.getName() + "准备材料中...");
    }
}

3、编写工厂类

public class SimpleFactory {
    public static Pizza createPizza(String type) {
        Pizza pizza = null;
        switch (type) {
            case "cheese":
                pizza = new CheesePizza();
                break;
            case "bacon":
                pizza = new BaconPizza();
                break;
            default:
                break;
        }
        if (null != pizza) {
            pizza.prepare();
            pizza.make();
            pizza.finish();
        }
        return pizza;
    }
}

4、编写使用类

public class BuyPizza {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入披萨类型:");
        String type = scanner.nextLine();
        Pizza pizza = SimpleFactory.createPizza(type);
        if (null == pizza) {
            System.out.println("抱歉,暂时没有该披萨!");
        }
    }
}

2.2 总结

  使用这种模式,将来想添加新的披萨类型时,直接编写对应的披萨类,然后在工厂类中添加分支即可,不需要修改使用类。

3 工厂方法模式

  简单工厂模式的缺点如下:

  • 一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂
  • 一旦这个工厂不能正常工作,整个系统都会受到影响

  于是引入工厂方法模式,把简单工厂中生产产品的方法抽象化,把具体生产产品的方法交由其子类完成。

3.1 例子

  需求:有一家工厂生产A产品,但随着发展,客户需要生产B产品。

1、编写抽象产品类以及它的具体产品类

public abstract class Product {
    public abstract void show();
}

public class ProductA extends Product {
    @Override
    public void show() {
        System.out.println("生产了A产品");
    }
}

public class ProductB extends Product {
    @Override
    public void show() {
        System.out.println("生产了B产品");
    }
}

2、编写抽象工厂类以及它的具体工厂类

public abstract class Factory {
    public abstract Product create();
}

public class FactoryA extends Factory {
    @Override
    public Product create() {
        return new ProductA();
    }
}

public class FactoryB extends Factory {
    @Override
    public Product create() {
        return new ProductB();
    }
}

3、编写使用类

public class Test {
    public static void main(String[] args) {
        new FactoryA().create().show();
        new FactoryB().create().show();
    }
}

3.2 总结

  这种模式有以下缺点:

  • 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度;同时,有更多的类需要编译和运行,会给系统带来一些额外的开销
  • 一个具体工厂只能创建一种具体产品

4. 抽象工厂模式

  个人觉得该模式与工厂方法模式大致相似,最大的区别就是工厂方法模式只有一个抽象产品类,具体工厂类只能创建一个具体产品类的实例;而抽象工厂模式有多个抽象产品类 ,具体工厂类能创建多个具体产品类的实例。

  需求:有两家工厂,A厂生产纸张,B厂生产塑料,但随着发展,两家工厂都需要生产这两种产品。

1、编写产品的族类

public abstract class Product {
    public abstract void show();
}

public abstract class Paper extends Product {}

public abstract class Plastics extends Product {}

public class PaperA extends Paper {
    @Override
    public void show() {
        System.out.println("生产了纸张,来自A");
    }
}

public class PaperB extends Paper {
    @Override
    public void show() {
        System.out.println("生产了纸张,来自B");
    }
}

public class PlasticsA extends Plastics {
    @Override
    public void show() {
        System.out.println("生产了塑料,来自A");
    }
}

public class PlasticsB extends Plastics {
    @Override
    public void show() {
        System.out.println("生产了塑料,来自B");
    }
}

2、编写工厂类的族类

public abstract class Factory {
    public abstract Product createPaper();
    public abstract Product createPlastics();
}

public class FactoryA extends Factory {
    @Override
    public Product createPaper() {
        return new PaperA();
    }

    @Override
    public Product createPlastics() {
        return new PlasticsA();
    }
}

public class FactoryB extends Factory {
    @Override
    public Product createPaper() {
        return new PaperB();
    }

    @Override
    public Product createPlastics() {
        return new PlasticsB();
    }
}

3、编写使用类

public class Test {
    public static void main(String[] args) {
        FactoryA factoryA = new FactoryA();
        factoryA.createPaper().show();
        factoryA.createPlastics().show();

        FactoryB factoryB = new FactoryB();
        factoryB.createPaper().show();
        factoryB.createPlastics().show();
    }
}

5. 原型模式

  通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。

5.1 例子

  需求:有一个自定义的Sheep对象,复制它以创建更多对象

1、编写Sheep类

public class Sheep implements Cloneable{
    private String name;
    private int age;
    private String color;

    public Sheep(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
	// 重写toString()方法...

    @Override
    protected Object clone() {
        Sheep sheep = null;
        try {
            sheep = (Sheep) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return sheep;
    }
}

2、编写测试类

public class Client {
    public static void main(String[] args) {
        Sheep sheep = new Sheep("tom", 1, "白色");
        Sheep clone1 = (Sheep) sheep.clone();
        Sheep clone2 = (Sheep) sheep.clone();
        Sheep clone3 = (Sheep) sheep.clone();
        System.out.println(clone1);
    }
}

  上述例子的是浅拷贝,那么当Sheep类的属性是引用时,其复制类的该属性引用与原型的指向同一空间,就会导致当修改某个复制的该属性时,其他复制以及原型的也会被修改。

5.2 深拷贝

1、创建Cat类和Person类

public class Cat implements Serializable, Cloneable {
    private String name;
    private int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
	// 重写toString()方法...
}


public class Person implements Serializable, Cloneable {
    private String name;
    private int age;
    // 引用类型属性
    private Cat cat;

    public Person(String name, int age, Cat cat) {
        this.name = name;
        this.age = age;
        this.cat = cat;
    }
    // get和set方法...
    // 重写toString()方法...

    /**
     * 深拷贝的方法1
     *  1.先完成基本数据类型的拷贝
     *  2.再完成引用类型的拷贝
     *  3.缺点是如果引用类型里还有引用类型,则编写麻烦
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Object temp = null;
        // 完成对基本数据类型的克隆
        temp = super.clone();
        // 单独处理引用类型
        Person person = (Person) temp;
        person.setCat((Cat) this.getCat().clone());
        return person;
    }

    /**
     * 深拷贝的方法2:使用序列化与反序列化的机制(推荐)
     */
    public Object clone2() {
        // 创建流对象
        ByteArrayOutputStream baos = null;
        ByteArrayInputStream bais = null;
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;

        Person person = null;
        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(this);  // 把当前对象写到baos流中

            bais = new ByteArrayInputStream(baos.toByteArray());
            ois = new ObjectInputStream(bais);
            person = (Person) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                baos.close();
                bais.close();
                oos.close();
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return person;
    }
}

3、编写测试类

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("小明", 10, new Cat("小猫", 1));
        Person clone1 = (Person) person.clone();
        Person clone2 = (Person) person.clone2();

        System.out.println(clone1.getCat() == person.getCat());
        System.out.println(clone2.getCat() == person.getCat());
    }
}

6 建造者模式

  当一个类的构造函数参数个数超过4个,而且这些参数有些是可选的参数,考虑使用建造者模式。传统的建造者模式下有以下角色:

  • 抽象建造者(Builder):描述具体建造者的公共接口,一般用来定义建造细节的方法,并不涉及具体的对象部件的创建
  • 具体建造者(ConcreteBuilder):描述具体建造者,并实现抽象建造者公共接口
  • 指挥者(Director):调用具体建造者来创建复杂对象(产品)的各个部分,并按照一定顺序(流程)来建造复杂对象
  • 产品(Product):一个由一系列部件组成的较为复杂的对象

6.1 例子

  需求:组装一台电脑

1、编写产品

public class Computer {
    private String cpu;
    private String memory;
    private String gpu;
    // get和set方法...
    // 重写toString()方法...
}

2、编写抽象建造者和具体建造者

public abstract class Builder {
    public abstract void addCPU(String cpu);
    public abstract void addMemory(String memory);
    public abstract void addGPU(String gpu);
    public abstract Computer getComputer();
}

public class Assembler extends Builder {
    private Computer computer = new Computer();
    @Override
    public void addCPU(String cpu) {
        computer.setCpu(cpu);
    }

    @Override
    public void addMemory(String memory) {
        computer.setMemory(memory);
    }

    @Override
    public void addGPU(String gpu) {
        computer.setGpu(gpu);
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}

3、编写指挥者

public class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }
    public Computer construct(String cpu, String memory, String gpu) {
        builder.addCPU(cpu);
        builder.addMemory(memory);
        builder.addGPU(gpu);
        return builder.getComputer();
    }
}

4、编写测试类

public class Buyer {
    public static void main(String[] args) {
        Director director = new Director(new Assembler());
        Computer computer = director.construct("i9-9980XE", "32G", "RTX 2080Ti");
        System.out.println(computer);
    }
}

6.2 总结

  建造者模式与抽象工厂模式的比较:

  • 与抽象工厂模式相比,建造者模式返回一个组装好的完整产品,而抽象工厂模式返回一系列相关的产品,这些产品位于不同的产品等级结构,构成了一个产品族

  • 在抽象工厂模式中,客户端实例化工厂类,然后调用工厂方法获取所需产品对象,而在建造者模式中,客户端可以不直接调用建造者的相关方法,而是通过指挥者类来指导如何生成对象,包括对象的组装过程和建造步骤,它侧重于一步步构造一个复杂对象,返回一个完整的对象

  • 工厂模式用于处理如何获取实例对象问题,建造者模式用于处理如何建造实例对象问题

  • 建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更复杂,因此将对象的创建过程独立出来组成一个新的类——指挥类

7 参考资料