设计模式 - 创建型模式总结

在软件工程中,创建型模式是处理对象创建的设计模式,试图根据实际情况使用合适的方式创建对象。基本的对象创建方式可能会导致设计上的问题,或增加设计的复杂度。创建型模式通过以某种方式控制对象的创建来解决问题。

常用创建型模式有:单例模式、工厂模式、抽象工厂模式、原型模式、建造者模式

一、单例模式

单例模式有以下8种写法:

  1. 饿汉式:
    1. 静态常量
    2. 静态代码块
  2. 懒汉式:
    1. 线程不安全
    2. 线程安全,同步方法
    3. 线程安全,同步代码块
  3. 双重检查
  4. 静态内部类
  5. 枚举

单例模式的使用场景:

需要频繁创建和销毁的对象;创建时耗时过多或消耗资源过多,但又经常用到的对象(比如session工厂、数据源等)

1. 饿汉式 - 静态常量写法

代码实现:

/**
 * 设计模式之单例模式
 * 饿汉式(静态常量)
 */
public class SingletonTest01 {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println("两次获取的实例一样吗:" + (instance1 == instance2)); //true
    }
}

class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //定义为常量,保证实例对象不变
    private final static Singleton instance = new Singleton();

    //通过此方法获取实例
    public static Singleton getInstance() {
        return instance;
    }

}

分析:

优点:

  • 使用方式简单,在类加载的时候创建实例对象,避免了线程同步问题

缺点:

  • 在类加载的时候创建实例对象,但不确定何时使用、是否使用,可能造成内存浪费

2. 饿汉式 - 静态代码块写法

代码实现:

/**
 * 设计模式之单例模式
 * 饿汉式(静态代码块写法)
 */
class Singleton{
    
    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton(){
    }
    
    //定义为常量,保证实例对象不变
    private final static Singleton instance;

    static {
        instance = new Singleton();
    }

    //通过此方法获取实例
    public static Singleton getInstance(){
        return instance;
    }

}

分析:

和静态常量一致,只不过初始化的位置不同,一个在静态代码块,一个直接在常量声明处初始化

3. 懒汉式 - 线程不安全

代码实现:

/**
 * 设计模式之单例模式
 * 懒汉式(线程不安全)
 */
class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //声明实例对象
    private static Singleton instance;

    //通过此方法获取实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

分析:

优点:

  • 满足随用随拿的特点,解决了内存浪费的问题

缺点:

  • 线程不安全,当多个线程访问时,可能创建多个实例,因此实际开发中不可使用

4. 懒汉式 - 线程安全 - 同步方法写法

代码实现:

/**
 * 设计模式之单例模式
 * 懒汉式(同步方法)
 */
class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //声明实例对象
    private static Singleton instance;

    //通过此方法获取实例
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

}

分析:

虽然解决了线程不安全问题,但锁的范围太大,效率低,开发中尽量不要使用

5. 懒汉式 - 线程安全 - 同步代码块写法

代码实现:

/**
 * 设计模式之单例模式
 * 懒汉式(同步代码块写法)
 */
class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //声明实例对象
    private static Singleton instance;

    //通过此方法获取实例
    public static Singleton getInstance() {
        if (instance == null) {
            //同步代码
            synchronized (Singleton.class) {
                instance = new Singleton();
            }
        }
        return instance;
    }

}

分析:

这种方式将同步锁缩小了范围,本意是解决效率问题,但又造成了线程不安全,因此开发中不可使用

6. 懒汉式 - 双重检查(推荐使用)

代码实现:

/**
 * 设计模式之单例模式
 * 双重检查
 */
class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //声明实例对象
    private static volatile Singleton instance;

    //双重判断 + 同步锁
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

分析:

既提高了效率也解决了线程安全问题,推荐使用这种方法

7. 懒汉式 - 静态内部类(推荐使用)

代码实现:

/**
 * 设计模式之单例模式
 * 静态内部类
 */
class Singleton {

    //私有构造方法,使其不可在外部通过构造器实例化
    private Singleton() {
    }

    //静态内部类
    private static class SingletonInstance{
        private final static Singleton INSTANCE = new Singleton();
    }

    //获取实例
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }

}

分析:

利用了类加载机制,保证初始化实例时只有一个线程。Singleton类被装载时并不会被实例化,当调用getInstance方法时才会装载SingletonInstance

8. 懒汉式 - 枚举法(推荐使用)

代码实现:

/**
 * 设计模式之单例模式
 * 枚举
 */
enum Singleton{
    INSTANCE;
}

分析:

不仅能规避线程不安全,还能防止反序列化重新创建新的对象

二、工厂模式

1. 简单工厂模式

1.1 介绍

​ 严格来说,简单工厂模式并不是23种常见的设计模式之一,它只算工厂模式的一个特殊实现。简单工厂模式在实际中的应用相对于其他2个工厂模式用的还是相对少得多,因为它只适应很多简单的情况。

​ 简单工厂模式违背了 开闭原则 (但可以通过反射的机制来避免) 。因为每次你要新添加一个功能,都需要在生switch-case 语句(或者if-else 语句)中去修改代码,添加分支条件。

1.2 适用场景

  • 需要创建的对象较少
  • 客户端不关心对象的创建过程

1.3 简单工厂模式角色分配

  • 工厂(Factory)角色 :简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
  • 抽象产品(Product)角色 :简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • 具体产品(Concrete Product)角色:简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。

1.4 简单工厂模式代码实现

新建一个抽象产品 Shape

public interface Shape {
    void draw();
}

具体产品 Circle、Square,实现 Shape 接口

public class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("圆形");
    }
}

public class Square implements Shape {
    @Override
    public void draw() {
        System.out.println("正方形");
    }
}

工厂 ShapeFactory

public class ShapeFactory {
    public static Shape getShape(String name){
        if(name == null){
            return null;
        }
        if ("SQUARE".equalsIgnoreCase(name)){
            return new Square();
        }else if("CIRCLE".equalsIgnoreCase(name)){
            return new Circle();
        }else{
            return null;
        }
    }
}

客户端 Client

public class Client {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.getShape("circle");
        circle.draw();
        Shape square = ShapeFactory.getShape("square");
        square.draw();
    }
}

运行结果

圆形
正方形

虽然实现了简单工厂模式,但是当我们新增一个需求的时候,需要修改ShapeFactory类的代码,违反了开闭原则,我们可以用反射的方式重写工厂方法

public class ShapeFactory2 {
    public static Object getClass(Class<? extends Shape> clazz){
        if (clazz == null){
            return null;
        }
        try {
            return clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}

测试

public class Client2 {
    public static void main(String[] args) {
        Circle circle = (Circle) ShapeFactory2.getClass(Circle.class);
        circle.draw();
        Square square = (Square) ShapeFactory2.getClass(Square.class);
        square.draw();
    }
}

运行结果

圆形
正方形

2. 工厂方法模式

2.1 介绍

​ 工厂方法模式应该是在工厂模式家族中是用的最多模式,一般项目中存在最多的就是这个模式。

工厂方法模式是简单工厂的仅一步深化, 在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的对象,而是针对不同的对象提供不同的工厂。也就是说 每个对象都有一个与之对应的工厂

2.2 适用场景

  • 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。

  • 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏

  • 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无需关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。

2.3 工厂方法模式角色分配

  • 抽象工厂(Abstract Factory)角色:是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
  • 具体工厂(Concrete Factory)角色 :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
  • 抽象产品(Abstract Product)角色 :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • 具体产品(Concrete Product)角色 :这个角色实现了抽象产品角色所定义的接口。某具体产品有专门的具体工厂创建,它们之间往往一一对应

2.4 工厂方法模式代码实现

抽象工厂 Factory

public interface Factory {
    Shape getShape();
}

具体工厂 CircleFactory、SquareFactory

public class CircleFactory implements Factory {
    @Override
    public Shape getShape() {
        return new Circle();
    }
}

public class SquareFactory implements Factory {
    @Override
    public Shape getShape() {
        return new Square();
    }
}

抽象产品和具体产品继续使用简单工厂模式中的类

客户端

public class Client {
    public static void main(String[] args) {
        Shape circle = new CircleFactory().getShape();
        circle.draw();
        Shape square = new SquareFactory().getShape();
        square.draw();
    }
}

运行结果

圆形
正方形

3. 抽象工厂模式

3.1 介绍

​ 在工厂方法模式中,其实我们有一个潜在意识的意识。那就是我们生产的都是同一类产品。抽象工厂模式是工厂方法的仅一步深化,在这个模式中的工厂类不单单可以创建一种产品,而是可以创建一组产品。

​ 将工厂抽象成两层,Abstract Factory(抽象工厂)和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展。

3.2 适用场景

  • 和工厂方法一样客户端不需要知道它所创建的对象的类。

  • 需要一组对象共同完成某种功能时,并且可能存在多组对象完成不同功能的情况。(同属于同一个产品族的产品)

  • 系统结构稳定,不会频繁的增加对象。(因为一旦增加就需要修改原有代码,不符合开闭原则)

3.3 抽象工厂模式角色分配

  • 抽象工厂(Abstract Factory)角色 :是工厂方法模式的核心,与应用程序无关。任何在模式中创建的对象的工厂类必须实现这个接口。
  • 具体工厂类(Concrete Factory)角色 :这是实现抽象工厂接口的具体工厂类,包含与应用程序密切相关的逻辑,并且受到应用程序调用以创建某一种产品对象。
  • 抽象产品(Abstract Product)角色 :工厂方法模式所创建的对象的超类型,也就是产品对象的共同父类或共同拥有的接口。
  • 具体产品(Concrete Product)角色 :抽象工厂模式所创建的任何产品对象都是某一个具体产品类的实例。在抽象工厂中创建的产品属于同一产品族,这不同于工厂模式中的工厂只创建单一产品。

3.4 抽象工厂的工厂和工厂方法中的工厂有什么区别?

​ 抽象工厂是生产一整套有产品的(至少要生产两个产品),这些产品必须相互是有关系或有依赖的,而工厂方法中的工厂是生产单一产品的工厂。

3.5 抽象工厂模式代码实现

抽象产品:Gun、Bullet

public interface Gun {
    void shooting();
}

public interface Bullet {
    void loading();
}

具体产品:AK47、AK47Bullet、M16、M16Bullet

public class AK47 implements Gun {
    @Override
    public void shooting() {
        System.out.println("AK47射击");
    }
}

public class AK47Bullet implements Bullet {
    @Override
    public void loading() {
        System.out.println("AK47装子弹");
    }
}

public class M16 implements Gun {
    @Override
    public void shooting() {
        System.out.println("M16射击");
    }
}

public class M16Bullet implements Bullet {
    @Override
    public void loading() {
        System.out.println("M16装子弹");
    }
}

抽象工厂:ArmsFactory

public interface ArmsFactory {
    Gun produceGun();
    Bullet produceBullet();
}

具体工厂:

public class AK47Factory implements ArmsFactory{

    @Override
    public Gun produceGun() {
        return new AK47();
    }

    @Override
    public Bullet produceBullet() {
        return new AK47Bullet();
    }
}

public class M16Factory implements ArmsFactory{

    @Override
    public Gun produceGun() {
        return new M16();
    }

    @Override
    public Bullet produceBullet() {
        return new M16Bullet();
    }
}

测试

public class Client {
    public static void main(String[] args) {
        ArmsFactory factory;

        factory = new AK47Factory();
        Gun ak47 = factory.produceGun();
        Bullet ak47Bullet = factory.produceBullet();
        ak47Bullet.loading();
        ak47.shooting();
        
        factory = new M16Factory();
        Gun m16 = factory.produceGun();
        Bullet m16Bullet = factory.produceBullet();
        m16Bullet.loading();
        m16.shooting();
    }
}

结果

AK47装子弹
AK47射击
M16装子弹
M16射击

参考:深入理解工厂模式

三、原型模式

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

原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节

可以通过重写clone方法实现拷贝,拷贝又分为浅拷贝和深拷贝

1. 浅拷贝:

  • 对于基本数据类型的成员变量,浅拷贝会直接进行值传递,将该属性值复制一份给新的对象

  • 对于引用类型的成员变量,浅拷贝知识将该成员变量的引用值(内存地址)复制一份给新的对象,因此会造成一个对象修改值影响另外一个对象

  • 浅拷贝时使用默认的clone()方法来实现,例如:

    Person = (Person)super.clone()

2. 深拷贝:

  • 复制对象的所有基本数据类型的成员变量值
  • 为所有引用类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象
  • 可通过重写clone方法和对象序列化方式(推荐)实现
//使用clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
    Company company = null;
    try {
        company = (Company) super.clone();
        //处理引用类型
        company.employee = (Employee) employee.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return company;
}
//使用序列化
protected Object deepClone(){
    ByteArrayInputStream bis = null;
    ByteArrayOutputStream bos = null;
    ObjectInputStream ois = null;
    ObjectOutputStream oos = null;

    try {
        //序列化
        bos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        //反序列化
        bis = new ByteArrayInputStream(bos.toByteArray());
        ois = new ObjectInputStream(bis);
        return ois.readObject();
    }catch (Exception e){
        e.printStackTrace();
    }
    return null;
}

3. 原型模式的分析:

  • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率
  • 不用重新初始化对象,而是动态的获取对象运行时的状态
  • 如果原始对象发生变化(增加或减少属性),其它克隆对象也会发生变化,无需修改代码
  • 浅拷贝和深拷贝对引用类型的处理方式不一样

四、建造者模式

1. 基本介绍

  1. 建造者模式(Builder Pattern)又叫生成器模式,是一种对象构建模式。它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
  2. 建造者模式是一步步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构造它们,用户不需要知道内部的具体构建细节。

2. 建造者模式的四个角色

  • 产品角色(Product):一个具体的产品对象
  • 抽象建造者(Builder):创建一个Product对象的各个部件指定的接口抽象类
  • 具体建造者(Concrete Builder):实现接口,构建和装配各个部件。
  • 指挥者(Director):构建一个使用Builder接口的对象。它主要是用于创建一个复杂的对象。它主要有两个作用:① 隔离了客户与对象的生产过程,②负责控制产品对象的生产过程。

3. 原理类图:

4. 建造者模式在 JDK - StringBuilder 中的应用

在 StringBuilder 继承关系中:

StringBuilder 继承了 AbstractStringBuilder ,AbstractStringBuilder 实现了 Appendable 接口

角色分析:

  • 抽象建造者:Appendable
  • 具体建造者:AbstractStringBuilder
  • 指挥者:StringBuilder

5. 建造者模式注意事项与细节

  1. 客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  2. 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
  3. 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
  4. 增加新的具体建造者无须修改原有类库的代码,指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”

6. 建造者模式代码实现

具体产品:House

public class House {
    private String basic;
    private String wall;
    private String roofed;
    //省略getter setter toString()方法
}

抽象建造者:HouseBuilder

public abstract class HouseBuilder {
    protected House house = new House();

    public abstract void buildBasic();

    public abstract void buildWall();

    public abstract void buildRoof();

    public House buildHouse() {
        return house;
    }
}

具体建造者:CommonHouse、HighHouse

public class CommonHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("打10m的地基");
    }

    @Override
    public void buildWall() {
        System.out.println("砌20cm的墙");
    }

    @Override
    public void buildRoof() {
        System.out.println("封瓦片顶");
    }
}

public class HighHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println("打20m的地基");
    }

    @Override
    public void buildWall() {
        System.out.println("砌50cm的墙");
    }

    @Override
    public void buildRoof() {
        System.out.println("封玻璃顶");
    }
}

指挥者:HouseDirector

public class HouseDirector {

    private HouseBuilder builder;

    public HouseDirector(HouseBuilder builder) {
        this.builder = builder;
    }

    public House build(){
        builder.buildBasic();
        builder.buildWall();
        builder.buildRoof();
        return builder.buildHouse();
    }
}

测试:

public class Client {
    public static void main(String[] args) {
        HouseDirector builderDirector1 = new HouseDirector(new CommonHouse());
        builderDirector1.build();
        System.out.println("---------");
        HouseDirector builderDirector2 = new HouseDirector(new HighHouse());
        builderDirector2.build();
    }
}

结果:

打10m的地基
砌20cm的墙
封瓦片顶
---------
打20m的地基
砌50cm的墙
封玻璃顶

p.s. 所有代码和笔记均可在 我的GitHub 中获取,如果对您有帮助的话,可以点个 star 支持一下 🙈

posted @ 2020-03-20 14:27  农夫三拳有点疼~  阅读(492)  评论(3编辑  收藏  举报