设计模式之创建型模式

一、设计模式分类

1) 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式

2) 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式

3) 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)。

注意:不同的书籍上对分类和名称略有差别

二、创建型模式

创建型模式的主要关注点是“怎样创建对象?”,它的主要特点是“将对象的创建与使用分离”。

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

创建型模式分为:

  • 单例模式
  • 工厂方法模式
  • 抽象工程模式
  • 原型模式
  • 建造者模式

1、单例模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。

实现单例模式的八种方式

  1. 饿汉式(静态常量)

  2. 饿汉式(静态代码块)

  1. 懒汉式(线程不安全)

  2. 懒汉式(线程安全,同步方法)

  3. 懒汉式(线程不安全,同步代码块)

  4. 双重检查

  5. 静态内部类

  6. 枚举

1.1 饿汉式(静态变量)

步骤如下

  1. 构造器私有化 (防止外部类 new )

  2. 类的内部创建对象

  3. 向外暴露一个静态的公共方法提供对象

public class Type01 {
    public static void main(String[] args) {
        // 测试
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton {
    // 1、构造器私有化
    private Singleton(){};
    // 2、提供一个静态对象
    public static final Singleton instance = new Singleton();
    // 3、提供一个公共方法返回对象
    public static Singleton getInstance(){
        return  instance;
    }
}

饿汉式(静态变量) 的优缺点

优点:这种写法比较简单,由于是静态变量,在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

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

1.2 饿汉模式(静态代码块)

和 静态变量类似,只是将实例化的步骤放到静态代码块中。同样可能会有 内存浪费 的缺点

public class Type02 {
    public static void main(String[] args) {
        // 测试
        Singleton1 instance = Singleton1.getInstance();
        Singleton1 instance1 = Singleton1.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton1 {
    // 1、构造器私有化
    private Singleton1() {
    }

    ;
    // 2、提供一个静态属性
    public static Singleton1 instance;

    // 3、在静态代码块中实例化
    static {
        instance = new Singleton1();
    }

    // 4、提供一个公共方法返回对象
    public static Singleton1 getInstance() {
        return instance;
    }
}

1.3 懒汉式(线程不安全)

public class Type03 {
    public static void main(String[] args) {
        // 测试
        Singleton2 instance = Singleton2.getInstance();
        Singleton2 instance1 = Singleton2.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton2 {
    // 1、构造器私有化
    private Singleton2() {
    }

    ;
    // 2、提供一个静态属性
    public static Singleton2 instance;

    // 3、提供一个公共方法返回对象,在获取对象时实例化对象,实现懒加载效果
    public static Singleton2 getInstance() {
        if (instance == null) {
            instance = new Singleton2();
        }
        return instance;
    }
}

缺点

  1. 起到了 Lazy Loading的效果,但是只能在单线程下使用。

  2. 如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式

结论:在实际开发中,不要使用这种方式.

1.4 懒汉式(线程安全,同步方法)

public class Type04 {
    public static void main(String[] args) {
        // 测试
        Singleton3 instance = Singleton3.getInstance();
        Singleton3 instance1 = Singleton3.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton3 {
    // 1、构造器私有化
    private Singleton3() {
    }

    ;
    // 2、提供一个静态属性
    public static Singleton3 instance;

    // 3、提供一个公共方法返回对象,在获取对象时实例化对象,实现懒加载效果
    // 使用同步方法,解决同步安全问题
    public static synchronized Singleton3 getInstance() {

        if (instance == null) {
                instance = new Singleton3();
        }
        return instance;
    }
}

优缺点

  1. 解决了线程不安全问题

  2. 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低

结论:在实际开发中,不推荐使用这种方式

1.5懒汉式(线程不安全,同步代码块)

public class Type05 {
    public static void main(String[] args) {
        // 测试
        Singleton4 instance = Singleton4.getInstance();
        Singleton4 instance1 = Singleton4.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton4 {
    // 1、构造器私有化
    private Singleton4() {
    }

    ;
    // 2、提供一个静态属性
    public static Singleton4 instance;

    // 3、提供一个公共方法返回对象,在获取对象时实例化对象,实现懒加载效果
    public static  Singleton4 getInstance() {

        if (instance == null) {
            // 使用同步代码块
            synchronized (Singleton4.class) {
                instance = new Singleton4();
            }
        }
        return instance;
    }
}

  1. 这种方式,本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块
  2. 但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

结论:在实际开发中,不能使用这种方

1.6 双重检查(Double Check)

public class Type06 {
    public static void main(String[] args) {
        // 测试
        Singleton5 instance = Singleton5.getInstance();
        Singleton5 instance1 = Singleton5.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton5 {
    // 1、构造器私有化
    private Singleton5() {
    }

    ;
    // 2、提供一个静态属性
    public static Singleton5 instance;

    // 3、提供一个公共方法返回对象,在获取对象时实例化对象
    // 使用双重检查 + synchronized 实现了懒加载,并解决线程安全问题
    public static  Singleton5 getInstance() {
        // 双重检查
        if (instance == null) {
            // 使用同步代码块
            synchronized (Singleton5.class) {
                if (instance == null ){
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

优缺点

  1. Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null) 检查,这样就可以保证线程安全了。
  2. 在第一个线程进入 synchronized 时完成实例化,其他线程在执行 if 时 就会直接 return
  3. 实现了懒加载 、解决了线程安全问题

结论:推荐使用这种方式

1.7 静态内部类

public class Type07 {
    public static void main(String[] args) {
        // 测试
        Singleton6 instance = Singleton6.getInstance();
        Singleton6 instance1 = Singleton6.getInstance();
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

class Singleton6 {
    // 1、构造器私有化
    private Singleton6() {
    };

    // 2、静态内部类:只有在使用该类时才会进行类装载,并且保证是线程安全的
    private static class InnerSingleton6{
        public static final Singleton6 instance = new Singleton6();
    }

    // 3、提供一个公共方法返回对象
    public static  Singleton6 getInstance() {
        return InnerSingleton6.instance;
    }
}

总结:

  1. 只有在调用 getInstance 时,才会对静态内部类进行加载,保证了延迟加载
  2. 静态变量随着类的加载而进行初始化,JVM 保证了其线程安全性。

推荐使用这种方式!

1.8 枚举

这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。

public class Type08 {
    public static void main(String[] args) {
        // 测试
        Singleton8 instance = Singleton8.INSTANCE;
        Singleton8 instance1 = Singleton8.INSTANCE;
        // 无论调用多少次,对象都是一样的
        System.out.println(instance == instance1); // true

        System.out.println(instance.hashCode() == instance1.hashCode()); // true
    }
}

// 使用枚举的方式,也可以保证单例
enum Singleton8 {
    INSTANCE;
    public void m1(){
        System.out.println("aaa");
    }
}

1.9 单例模式使用注意事项

在 java.lang 包下的 Runtime 类就是经典的一个单例模式,使用饿汉式【静态变量】的方式实现。

image-20230221204004787

  1. 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
  2. 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

2、工厂模式

2.1 概述

需求:设计一个咖啡店点餐系统。

设计一个咖啡抽象类(Coffee),并定义其两个子类(美式咖啡【AmericanCoffee】和拿铁咖【LatteCoffee】);再设计一个咖啡店类(CoffeeStore),咖啡店具有点咖啡的功能。

image-20230222152135860

public abstract class Coffee {
    // 不同的咖啡名字不同,供子类重写
    public abstract String getName();

    public void addSugar() {
        System.out.println("加糖");
    }

    public void addMilk() {
        System.out.println("加奶");
    }
}

public class LatteCoffee extends Coffee{
    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

public class AmericanCoffee extends Coffee{
    @Override
    public String getName() {
        return "美式咖啡";
    }
}

public class CoffeeStore {

    // 点咖啡
    public Coffee orderCoffee(String type) {
        Coffee coffee = null ;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        }else if ("latte".equals(type)) {
            coffee  = new LatteCoffee();
        }else {
            throw new RuntimeException("没有该咖啡类型...");
        }
        // 打印咖啡名字,加糖,加奶
        System.out.println(coffee.getName());
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}

public class Client {
    public static void main(String[] args) {
        CoffeeStore coffeeStore = new CoffeeStore();
        Coffee coffee = coffeeStore.orderCoffee("latte");
    }
}

以上这种方式的缺点

在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦

三种工厂的使用

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

2.2 简单工厂模式

简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式

简单工厂包含如下角色:

  • 抽象产品 :定义了产品的规范,描述了产品的主要特性和功能。【Coffee】
  • 具体产品 :实现或者继承抽象产品的子类【LatteCoffee、AmericanCoffee】
  • 具体工厂 :提供了创建产品的方法,调用者通过该方法来获取产品。【SimpleCoffeeFactory】

使用简单工厂模式对以上代码进行优化
image-20230222155902018

public class SimpleCoffeeFactory {

    // 由工厂生产咖啡
    public Coffee createCoffee(String type) {
        Coffee coffee = null ;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        }else if ("latte".equals(type)) {
            coffee  = new LatteCoffee();
        }else {
            throw new RuntimeException("没有该咖啡类型...");
        }
        return coffee;
    }
}

public class CoffeeStore {

    // 点咖啡
    public Coffee orderCoffee(String type) {
        // 从工厂中获取咖啡对象
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        Coffee coffee = factory.createCoffee(type);
        // 打印咖啡名字,加糖,加奶
        System.out.println(coffee.getName());
        coffee.addSugar();
        coffee.addMilk();
        return coffee;
    }
}

使用 简单工厂模式的优缺点

优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点

增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”

2.3 拓展:静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这个就是静态工厂模式。代码如下:

唯一的好处就是不用 new SimpleCoffeeFactory

    // 由工厂生产咖啡
    // 在开发中,有些人喜欢使用静态方法——静态工厂
    public static Coffee createCoffee(String type) {
        Coffee coffee = null ;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        }else if ("latte".equals(type)) {
            coffee  = new LatteCoffee();
        }else {
            throw new RuntimeException("没有该咖啡类型...");
        }
        return coffee;
    }

2.4 工厂方法模式

定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

工厂方法模式的主要角色

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
  • 具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应。

使用工厂方法模式能够解决上面问题,完全遵循 开闭原则

image-20230222162711381

/**
 * Handsome Man.
 *
 * Author: YZG
 * Date: 2023/2/22 16:20
 * Description: 抽象工厂,用于提供子类实例化具体的产品
 */
public interface CoffeeFactory {
    // 提供给子类重写,用于创建具体的产品
    Coffee createCoffee();
}

/**
 *
 * Author: YZG
 * Date: 2023/2/22 16:22
 * Description: 具体工厂:用来实例化具体的产品
 */
public class AmericanCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}
/**
 *
 * Author: YZG
 * Date: 2023/2/22 16:22
 * Description: 具体工厂:用来实例化具体的产品
 */
public class LatteCoffeeFactory implements CoffeeFactory{
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

public class CoffeeStore {

    // 依赖于抽象接口
    private CoffeeFactory factory;

    public void setFactory(CoffeeFactory factory) {
        this.factory = factory;
    }

    public Coffee orderCoffer() {
        // 通过工厂创建具体的产品
        Coffee coffee = factory.createCoffee();

        System.out.println(coffee.getName());
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

// 客户端
public class Client {
    public static void main(String[] args) {

        CoffeeStore coffeeStore = new CoffeeStore();
        // 指定具体的工厂,创建具体的产品
        // coffeeStore.setFactory(new AmericanCoffeeFactory());
        coffeeStore.setFactory(new LatteCoffeeFactory());
        Coffee coffee = coffeeStore.orderCoffer();
    }
}

优点:

  • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
  • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

缺点:

  • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

2.5 抽象工厂模式

前面介绍的工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机

这些工厂只生产同种类产品,同种类产品称为同等级产品,也就是说:工厂方法模式只考虑生产同等级的产品,但是在现实生活中许多工厂是综合型的工厂,能生产多等级(种类) 的产品,如电器厂既生产电视机又生产洗衣机或空调,大学既有软件专业又有生物专业等。

抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族,下图所示横轴是产品等级,也就是同一类产品;纵轴是产品族,也就是同一品牌的产品,同一品牌的产品产自同一个工厂。

image-20230222164027611

概念

是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品

抽象工厂模式的主要角色如下

  • 抽象工厂(Abstract Factory):提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品。
  • 具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
  • 抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
  • 具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它 同具体工厂之间是多对一的关系。

实现:

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等,要是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。

其中拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级;拿铁咖啡和提拉米苏是同一产品族(也就是都属于意大利风味),美式咖啡和抹茶慕斯是同一产品族(也就是都属于美式风味)。所以这个案例可以使用抽象工厂模式实现。类图如下:

image-20230222165525879

/**
 * Handsome Man.
 *
 * Author: YZG
 * Date: 2023/2/22 16:59
 * Description:  抽象工厂,定义同一产品组的产品
 */
public interface DessertFactory {
    Coffee createCoffee();
    Dessert createDessert();
}

/**
 *
 * Author: YZG
 * Date: 2023/2/22 17:01
 * Description: 具体工厂: 实现抽象工厂,重写多个抽象方法完成产品的创建
 */
public class AmericanDessertFactory implements DessertFactory{

    // 创建美式风味的咖啡和甜品【美式咖啡、抹茶慕斯】
    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }

    @Override
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}

/**
 *
 * Author: YZG
 * Date: 2023/2/22 17:02
 * Description: 具体工厂: 实现抽象工厂,重写多个抽象方法完成产品的创建
 */
public class ItalyDessertFactory implements DessertFactory {
    // 创建意大利风味的咖啡、甜品【拿铁、提拉米苏】
    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }

    @Override
    public Dessert createDessert() {
        return new Tiramisu();
    }

    // 客戶端
 public class Client {
    public static void main(String[] args) {
        // 美式风味产品
        // AmericanDessertFactory factory = new AmericanDessertFactory();
        // 意大利风味产品
        ItalyDessertFactory factory = new ItalyDessertFactory();
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
        dessert.show();
        System.out.println(coffee.getName());
    }
}

如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。如果在加一个汉堡分类,那么抽象工厂以及具体工厂都需要改变。

使用场景:

  • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。

  • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。

  • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

2.6 扩展:简单工厂+配置文件

可以通过工厂模式+配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。

1、创建配置文件

american=com.yzg.patterns.factory.configfactory.AmericanCoffee
latte=com.yzg.patterns.factory.configfactory.LatteCoffee

2、简单工厂类

public class SimpleCoffeeFactory {

    private static Map<String,Coffee> map = new HashMap();

    static {
        Properties p = new Properties();
        InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            p.load(is);
            //遍历Properties集合对象
            Set<Object> keys = p.keySet();
            for (Object key : keys) {
                //根据键获取值(全类名)
                String className = p.getProperty((String) key);
                //获取字节码对象
                Class clazz = Class.forName(className);
                Coffee obj = (Coffee) clazz.newInstance();
                map.put((String)key,obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static Coffee createCoffee(String name) {

        return map.get(name);
    }
}

2.7 JDK源码解析-Collection.iterator方法

JDK 中的 Collection、Iterator 就使用了工厂方法模式:

Collection接口是抽象工厂,里面定义了一个 iterator 方法,用于子类实现创建 Iterator 实例对象。ArrayList是具体的工厂类,Iterator 是抽象产品类,ArrayList 的内部类 Itr 就是具体的产品。

ArrayList 实现了 List 接口,List 实现了 Collection 接口,并实现了 iterator 方法,创建了 Itr 具体的产品。

image-20230222192930106

2.8 抽象工厂模式 与 工厂方法模式有什么区别

1、工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。

2、 **工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。**但是多个具体产品类都属于同一产品族,比如:华为是一个抽象产品类,其具体产品类有手机、电脑、IPad…都是由华为生产。

如果产品单一,最合适用工厂模式,但是如果有多个业务品种、业务分类时,通过抽象工厂模式产生需要的对象是一种非常好的解决方式。再通俗深化理解下:工厂模式针对的是一个产品等级结构 ,抽象工厂模式针对的是面向多个产品等级结构的。

image-20230222203804809

3、原型模式

用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

原型模式包含如下角色

  • 抽象原型类:规定了具体原型对象必须实现的的 clone() 方法。
  • 具体原型类:实现抽象原型类的 clone() 方法,它是可被复制的对象。
  • 访问类:使用具体原型类中的 clone() 方法来复制新的对象。

实现

原型模式的克隆分为俩种:

浅克隆: 被Clone的对象的所有变量都含有原来对象相同的值,而引用变量还是原来对用的引用【拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。】

深克隆:被克隆对象的所有变量都含有原来的对象相同的值,引用变量也重新复制了一份【不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象】

克隆对象与原有对象不相等,内存地址不一样,仅仅是拷贝了变量

3.1 浅克隆

Java中的Object类中提供了 clone() 方法来实现浅克隆。 Cloneable 接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:

public class Realizetype implements Cloneable{

    public Realizetype() {
        System.out.println("通过无参构造创建了对象..");
    }

  
    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("使用clone方法克隆了对象...");
        return (Realizetype) super.clone();
    }
}

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Realizetype realizetype = new Realizetype();
        Realizetype clone = realizetype.clone();
        System.out.println("原型对象是否和克隆对象是同一个?" + (realizetype == clone));
    }
}

举个例子

同一学校的“三好学生”奖状除了获奖人姓名不同,其他都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。

image-20230223173136938

public class Citation implements Cloneable{

    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }

    public void show(){
        System.out.println(name + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}

public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        Citation citation = new Citation();
        Citation citation1 = citation.clone();
        citation1.setName("zs");
        citation1.show();
        Citation citation2 = citation.clone();
        citation2.setName("ls");
        citation1.show();
        Citation citation3 = citation.clone();
        citation3.setName("ww");
        citation1.show();
    }
}

原型模式使用场景

  • 对象的创建非常复杂,可以使用原型模式快捷的创建对象。
  • 性能和安全要求比较高。

3.2 深克隆

//奖状类
public class Citation implements Cloneable {
    private Student stu;

    public Student getStu() {
        return stu;
    }

    public void setStu(Student stu) {
        this.stu = stu;
    }

    void show() {
        System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }
}

//学生类
public class Student {
    private String name;
    private String address;

    public Student(String name, String address) {
        this.name = name;
        this.address = address;
    }

    public Student() {
    }

    public String getName() {
        return name;
    }

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

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

//测试类
public class CitationTest {
    public static void main(String[] args) throws CloneNotSupportedException {

        Citation c1 = new Citation();
        Student stu = new Student("张三", "西安");
        c1.setStu(stu);

        //复制奖状
        Citation c2 = c1.clone();
        //获取c2奖状所属学生对象
        Student stu1 = c2.getStu();
        stu1.setName("李四");

        //判断stu对象和stu1对象是否是同一个对象
        System.out.println("stu和stu1是同一个对象?" + (stu == stu1));

        c1.show();
        c2.show();
    }
}

输出结果:

image-20230223175419976

​ stu对象和stu1对象是同一个对象,就会产生将stu1对象中name属性值改为“李四”,两个Citation(奖状)对象中显示的都是李四。这就是浅克隆的效果,对具体原型类(Citation)中的引用类型的属性进行引用的复制。这种情况需要使用深克隆,而进行深克隆需要使用对象流。代码如下:

错误:使用对象流需要实现序列化接口

image-20230223180512794

public class Citation implements Cloneable, Serializable {

    private Student stu;

    public void setStu(Student stu) {
        this.stu = stu;
    }

    public Student getStu() {
        return stu;
    }

    @Override
    public Citation clone() throws CloneNotSupportedException {
        return (Citation) super.clone();
    }

    public void show(){
        System.out.println(stu.getName() + "同学:在2020学年第一学期中表现优秀,被评为三好学生。特发此状!");
    }
}

public class Student implements Serializable {
    private String name;

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

public class Client {
    public static void main(String[] args) throws Exception {
        Citation citation = new Citation();
        Student stu = new Student();
        stu.setName("张三");
        citation.setStu(stu);

        // 这里的 stu1 和 stu 是同一个对象。
        // 因此如果想对引用类型的属性进行克隆,必须使用深克隆
        // Citation citation1 = citation.clone();
        // Student stu1 = citation1.getStu();
        // stu1.setName("李四");


        // 使用对象流进行深拷贝
        // 创建对象输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Java\\java_notes\\22.设计模式\\design_patterns_code\\src\\main\\resources\\deepCopy.txt"));
        // 输出对象
        oos.writeObject(citation);
        // 释放资源
        oos.close();

        // 创建对象输入流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Java\\java_notes\\22.设计模式\\design_patterns_code\\src\\main\\resources\\deepCopy.txt"));
        // 输入对象
        Citation citation1 = (Citation) ois.readObject();
        // 释放资源
        ois.close();

        // 重新监测一下:看是否是同一个Stu 对象
        citation1.getStu().setName("lisi");


        citation.show();
        citation1.show();
    }
}

从结果中可以看出,stu1 也被克隆了,与 stu 不是同一个对象。

image-20230223180744804

4、建造者模式

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

image-20230223182616467

  • 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
  • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
  • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

建造者(Builder)模式包含如下角色

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的部件对象的创建。
  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product):要创建的复杂对象。
  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

image-20230223182738151

例子:

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。

这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。类图如下:

image-20230223182921044

//自行车类
public class Bike {
    private String frame;
    private String seat;

    public String getFrame() {
        return frame;
    }

    public void setFrame(String frame) {
        this.frame = frame;
    }

    public String getSeat() {
        return seat;
    }

    public void setSeat(String seat) {
        this.seat = seat;
    }
}

// 抽象 builder 类
public abstract class Builder {

    protected Bike mBike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
}

//摩拜单车Builder类
public class MobikeBuilder extends Builder {

    @Override
    public void buildFrame() {
        mBike.setFrame("铝合金车架");
    }

    @Override
    public void buildSeat() {
        mBike.setSeat("真皮车座");
    }

    @Override
    public Bike createBike() {
        return mBike;
    }
}

//ofo单车Builder类
public class OfoBuilder extends Builder {

    @Override
    public void buildFrame() {
        mBike.setFrame("碳纤维车架");
    }

    @Override
    public void buildSeat() {
        mBike.setSeat("橡胶车座");
    }

    @Override
    public Bike createBike() {
        return mBike;
    }
}

//指挥者类
public class Director {
    private Builder mBuilder;

    public Director(Builder builder) {
        mBuilder = builder;
    }

    public Bike construct() {
        mBuilder.buildFrame();
        mBuilder.buildSeat();
        return mBuilder.createBike();
    }
}

//测试类
public class Client {
    public static void main(String[] args) {
        showBike(new OfoBuilder());
        showBike(new MobikeBuilder());
    }
    private static void showBike(Builder builder) {
        Director director = new Director(builder);
        Bike bike = director.construct();
        System.out.println(bike.getFrame());
        System.out.println(bike.getSeat());
    }
}

注意:

上面示例是 Builder模式的常规用法,指挥者类 Director 在建造者模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合

// 抽象 builder 类
public abstract class Builder {

    protected Bike mBike = new Bike();

    public abstract void buildFrame();
    public abstract void buildSeat();
    public abstract Bike createBike();
    
    // 将 抽象建造者 与 指挥者 相结合
    public Bike construct() {
        this.buildFrame();
        this.BuildSeat();
        return this.createBike();
    }
}

说明:

这样做确实简化了系统结构,但同时也加重了抽象建造者类的职责,也不是太符合单一职责原则,如果construct() 过于复杂,建议还是封装到 Director 中。

4.1 模式扩展

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    public Phone(String cpu, String screen, String memory, String mainboard) {
        this.cpu = cpu;
        this.screen = screen;
        this.memory = memory;
        this.mainboard = mainboard;
    }

    public String getCpu() {
        return cpu;
    }

    public void setCpu(String cpu) {
        this.cpu = cpu;
    }

    public String getScreen() {
        return screen;
    }

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getMainboard() {
        return mainboard;
    }

    public void setMainboard(String mainboard) {
        this.mainboard = mainboard;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }
}

public class Client {
    public static void main(String[] args) {
        //构建Phone对象
        Phone phone = new Phone("intel","三星屏幕","金士顿","华硕");
        System.out.println(phone);
    }
}

上面在客户端代码中构建Phone对象,传递了四个参数,如果参数更多呢?代码的可读性及使用的成本就是比较高。

使用建造者模式进行重构:

/**
 *
 * Author: YZG
 * Date: 2023/2/23 19:14
 * Description:  手机类: 如果参数过多的话,可读性并不是很好。容易混乱。
 *  可以使用建造者模式进行重构
 */
public class Phone {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;

    // 防止外部类new
    private Phone(Builder builder){
        this.cpu = builder.cpu;
        this.mainboard = builder.mainboard;
        this.memory = builder.memory;
        this.screen = builder.screen;
    }

    @Override
    public String toString() {
        return "Phone{" +
                "cpu='" + cpu + '\'' +
                ", screen='" + screen + '\'' +
                ", memory='" + memory + '\'' +
                ", mainboard='" + mainboard + '\'' +
                '}';
    }

    // 使用构建者模式进行重构
    public static final class Builder{
        private String cpu;
        private String screen;
        private String memory;
        private String mainboard;

        public Builder cpu(String cpu){
            this.cpu = cpu;
            return this;
        }
        public Builder screen(String screen){
            this.screen = screen;
            return this;
        }
        public Builder memory(String memory){
            this.memory = memory;
            return this;
        }
        public Builder mainboard(String mainboard){
            this.mainboard = mainboard;
            return this;
        }
        // 构建出Phone类
        public Phone build(){
            return new Phone(this);
        }
    }
}

// 客户端: 将组装顺序交给客户端
public class Client {
    public static void main(String[] args) {
        Phone phone = new Phone.Builder()
                .cpu("麒麟9099")
                .memory("三星")
                .mainboard("三星")
                .screen("三星")
                .build();
        System.out.println(phone);
    }
}

5、创建型模式对比

5.1 工厂方法模式与建造者模式区别

工厂方法模式注重的是整体对象的创建方式;而建造者模式注重的是部件构建的过程,意在通过一步一步地精确构造创建出一个复杂的对象。

我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,于是一个超人就诞生了。

5.2 抽象工厂模式与建造者模式的区别

抽象工厂模式实现对产品家族的创建,一个产品家族是这样的一系列产品:具有不同分类维度的产品组合,采用抽象工厂模式则是不需要关心构建过程,只关心什么产品由什么工厂生产即可。

建造者模式则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品。

如果将抽象工厂模式看成汽车配件生产工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鲨瓜2号

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值