「Java」设计模式-创造型

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

设计模式

什么是设计模式:

在某些场景下,针对某类问题的某种通用的解决方案

创造型

对象实例化的模式,创建型模式用于解耦对象的实例化过程

Singleton 单例模式

某个类只能有一个实例,提供一个全局的访问点。

image.png
特点

  1. 只有一个实例。

  2. 自我实例化。

  3. 提供全局访问点。

优点: 约系统资源、提高了系统效率,同时也能够严格控制客户对它的访问

单例模式的几种实现方式:

懒汉式,线程不安全

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作


public class Singleton {

    private static Singleton instance;

    private Singleton (){}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

复制代码

懒汉式,线程安全

这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)


public class Singleton {

private static Singleton instance;

private Singleton (){}

public static synchronized Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

复制代码

饿汉式

这种方式比较常用,但容易产生垃圾对象。

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。


public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton (){}

    public static Singleton getInstance() {

        return instance;

    }

}

复制代码

双检锁/双重校验锁(DCL,即 double-checked locking)

种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键


public class Singleton {

    private volatile static Singleton singleton;

    private Singleton (){}

    public static Singleton getSingleton() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

                    singleton = new Singleton();

                }
            }
        }
        return singleton;
    }

}

复制代码

登记式/静态内部类

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。

这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。


public class Singleton {

    private static class SingletonHolder {

        private static final Singleton INSTANCE = new Singleton();
    
    }

    private Singleton (){}

    public static final Singleton getInstance() {

        return SingletonHolder.INSTANCE;

    }

}

复制代码

枚举

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。


public enum Singleton {

    INSTANCE;

    public void whateverMethod() {

    }

}

复制代码
简单工厂

用new导致代码不够灵活,用工厂来实例化对象很灵活

image.png


public class OperationFactory{
    //静态方法
    public static Operation createOperate(string operate){

        Operation oper = null;

        //分支判断,在增加新的运算类的时候这里需要修改,违背了开放封闭原则
        switch (operate) {

            case "+":

                oper = new OperationAdd();

            break;

            case "-":

                oper = new OperationSub();

            break;

            case "*":

                oper = new OperationMul();

            break;

            case "/":

                oper = new OperationDiv();

            break;

            }

        return oper;

    }

}

复制代码

优点

  1. 简单工厂包含必要的判断逻辑,简单工厂实现了对象的创建和使用的分离。

  2. 客户端无需知道所创建的具体产品类的类名,只需要具体产品类对应的参数即可

  3. 在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性

缺点

  1. 工厂类的职责过重,从类图中可以看出简单工厂中包含加减乘除的逻辑判断语句,它一旦有问题,整个系统都要出问题

  2. 在添加新的类的时候,例如我添加了开根号运算,那么系统中的简单工厂类就要修改,违反了开放——封闭原则.这样及其不利于系统的扩展和维护

  3. 简单工厂的静态方法,使得工厂角色无法形成基于继承的等级结构

工厂方法

工厂方法是简单工厂的进一步的延伸,这样说是因为简单工厂违反了开放——封闭的原则,而此时工厂方法却可以完美的解决这个问题

image.png

需要创建的类:


class OperationAdd :Operation {

public override double GetResult()

{

double result = 0;

result = NumberA + NumberB;

return result;

}

}

复制代码

工厂:


class AddFactory : Ifactory {

//这个是加法工厂的代码,省去了那些逻辑判断
public Operation CreateOperation() {

    return new OperationAdd(); //返回到了加法类

    }

}

复制代码

优点

  1. 工厂方法用来创建客户所需要的产品,同时隐藏了哪种具体产品类将被实例化的细节,用户只需要要关注工厂,不需要关注创建的细节,从客户端代码就可以看出,只知道对应的工厂就好

  2. 在增加修改新的运算类的时候不用修改代码,只需要增加对应的工厂就好,完全符合开放——封闭性原则

  3. 创建对象的细节完全封装在具体的工厂内部,而且有了抽象的工厂类,所有的具体工厂都继承了自己的父类!完美的体现了多态性

缺点

  1. 在增加新的产品(对应UML图的算法)时,也必须增加新的工厂类,会带来额外的开销

  2. 抽象层的加入使得理解程度加大

抽象工厂

抽象工厂模式是工厂方法模式的进一步延伸,由于它提供了功能更为强大的工厂类并且具备较好的可扩展性

image.png

优点

  1. 分离了具体的类。客户通过抽象接口操纵实例,产品的类名也在具体工厂的实现中被分离,它们不出现在客户代码中。

  2. 易于交换产品系列。一个具体工厂类只在初始化时出现一次,这使得改变一个应用的具体工厂变得很容易,只需改变具体的工厂即可使用不同的产品配置。

  3. 有利于产品的一致性。当一个系列的产品对象被设计成一起工作时,一个应用一次只能使用同一个系列中的对象,这一点很重要,而抽象工厂很容易实现这一点

缺点

难以支持新种类的产品。因为抽象工厂接口确定了可以被创建的产品集合,所以难以扩展抽象工厂以生产新种类的产品。

建造者模式

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

image.png

优点

  1. 建造者独立,易扩展。

  2. 便于控制细节风险。

缺点

  1. 产品必须有共同点,范围有限制。

  2. 如内部变化复杂,会有很多的建造类。

使用场景

  1. 需要生成的对象具有复杂的内部结构。

  2. 需要生成的对象内部属性本身相互依赖。

实例

假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。

创建一个表示食物条目和食物包装的接口。


public interface Item {

    public String name();

    public Packing packing();

    public float price();

}

复制代码

public interface Packing {

    public String pack();

}

复制代码

创建实现 Packing 接口的实体类。


public class Wrapper implements Packing {

    @Override
    public String pack() {

        return "Wrapper";

    }

}

复制代码

public class Bottle implements Packing {

    @Override
    public String pack() {
    
        return "Bottle";

    }

}

复制代码

创建实现 Item 接口的抽象类,该类提供了默认的功能。


public abstract class Burger implements Item {

    @Override
    public Packing packing() {

        return new Wrapper();

    }

    @Override
    public abstract float price();

    }

复制代码

public abstract class ColdDrink implements Item {

    @Override
    public Packing packing() {

        return new Bottle();

    }

    @Override
    public abstract float price();

    }

复制代码

创建扩展了 Burger 和 ColdDrink 的实体类。


public class VegBurger extends Burger {

    @Override
    public float price() {

        return 25.0f;

    }

    @Override
    public String name() {

        return "Veg Burger";

    }

}

复制代码

public class ChickenBurger extends Burger {

    @Override
    public float price() {

        return 50.5f;

    }

    @Override
    public String name() {

        return "Chicken Burger";

    }

}

复制代码

public class Coke extends ColdDrink {

    @Override
    public float price() {

        return 30.0f;

    }

    @Override
    public String name() {

        return "Coke";

    }

}

复制代码

public class Pepsi extends ColdDrink {

    @Override
    public float price() {

        return 35.0f;

    }

    @Override
    public String name() {

        return "Pepsi";

    }

}

复制代码

创建一个 Meal 类,带有上面定义的 Item 对象。


import java.util.ArrayList;

import java.util.List;

public class Meal {

    private List<Item> items = new ArrayList<Item>();

    public void addItem(Item item){

        items.add(item);

    }

    public float getCost(){

        float cost = 0.0f;

        for (Item item : items) {

            cost += item.price();

        }

        return cost;

    }

    public void showItems(){

        for (Item item : items) {
            System.out.print("Item : "+item.name());
            System.out.print(", Packing : "+item.packing().pack());
            System.out.println(", Price : "+item.price());
        }

    }

}

复制代码

创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。


public class MealBuilder {

    public Meal prepareVegMeal (){
        Meal meal = new Meal();
        meal.addItem(new VegBurger());
        meal.addItem(new Coke());
        return meal;

    }


    public Meal prepareNonVegMeal (){
        Meal meal = new Meal();
        meal.addItem(new ChickenBurger());
        meal.addItem(new Pepsi());
        return meal;
    }

}

复制代码

BuiderPatternDemo 使用 MealBuider 来演示建造者模式(Builder Pattern)。


public class BuilderPatternDemo {

    public static void main(String[] args) {

        MealBuilder mealBuilder = new MealBuilder();

        Meal vegMeal = mealBuilder.prepareVegMeal();

        System.out.println("Veg Meal");

        vegMeal.showItems();

        System.out.println("Total Cost: " +vegMeal.getCost());

        Meal nonVegMeal = mealBuilder.prepareNonVegMeal();

        System.out.println("\n\nNon-Veg Meal");

        nonVegMeal.showItems();

        System.out.println("Total Cost: " +nonVegMeal.getCost());

    }

}

复制代码
原型模式

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

关键代码

1.实现克隆操作,在 JAVA 继承 Cloneable,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。

  1. 原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

优点

  1. 性能提高。

  2. 逃避构造函数的约束。

缺点

  1. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

  2. 必须实现 Cloneable 接口。

实现

创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。PrototypePatternDemo,我们的演示类使用 ShapeCache 类来获取 Shape 对象。

image.png

创建一个实现了 Cloneable 接口的抽象类。


public abstract class Shape implements Cloneable {

    private String id;

    protected String type;

    abstract void draw();

   
   public String getType(){
        return type;
    }


    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public Object clone() {

        Object clone = null;

        try {
            clone = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return clone;

    }

}

复制代码

创建扩展了上面抽象类的实体类。


public class Rectangle extends Shape {

    public Rectangle(){
        type = "Rectangle";
    }

    @Override
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }

}

复制代码

public class Square extends Shape {

    public Square(){
        type = "Square";
    }

    @Override
    public void draw() {
        System.out.println("Inside Square::draw() method.");
    }

}

复制代码

public class Circle extends Shape {

    public Circle(){

        type = "Circle";

    }

    @Override
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }

}

复制代码

创建一个类,从数据库获取实体类,并把它们存储在一个 Hashtable 中。


import java.util.Hashtable;

public class ShapeCache {

    private static Hashtable<String, Shape> shapeMap

    = new Hashtable<String, Shape>();
    
    public static Shape getShape(String shapeId) {
        Shape cachedShape = shapeMap.get(shapeId);
        return (Shape) cachedShape.clone();

    }

// 对每种形状都运行数据库查询,并创建该形状

// shapeMap.put(shapeKey, shape);

// 例如,我们要添加三种形状

    public static void loadCache() {

        Circle circle = new Circle();
        circle.setId("1");

        shapeMap.put(circle.getId(),circle);

        Square square = new Square();
        square.setId("2");

        shapeMap.put(square.getId(),square);

        Rectangle rectangle = new Rectangle();
        rectangle.setId("3");

        shapeMap.put(rectangle.getId(),rectangle);

    }

}

复制代码

PrototypePatternDemo 使用 ShapeCache 类来获取存储在 Hashtable 中的形状的克隆。


public class PrototypePatternDemo {

    public static void main(String[] args) {

        ShapeCache.loadCache();

        Shape clonedShape = (Shape) ShapeCache.getShape("1");
        System.out.println("Shape : " + clonedShape.getType());

        Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
        System.out.println("Shape : " + clonedShape2.getType());

        Shape clonedShape3 = (Shape) ShapeCache.getShape("3");
        System.out.println("Shape : " + clonedShape3.getType());
    }

}

复制代码