史上最全的是适合理解的设计模式教程(创建型模式)

一、设计模式是什么

1.1 设计模式简介

设计模式(Design Patterns)是软件工程中用于解决特定问题的一系列最佳实践。它们是经过时间考验的、被广泛认可的软件设计经验,可以帮助开发者在面对常见问题时做出更好的设计决策。设计模式不是现成的代码,而是一套指导原则,用来指导开发者如何组织代码结构,以便于更好地应对变化和提高代码的可维护性。

设计模式通常具有以下特点:

  1. 普遍性:设计模式解决的问题在软件设计中非常普遍,很多设计问题都可以用相应的设计模式来解决。
  2. 可重用性:设计模式提供了一种可重用的解决方案,可以帮助开发者避免重复发明轮子。
  3. 灵活性:设计模式通常具有良好的灵活性,可以适应不同的应用场景。
  4. 可维护性:使用设计模式可以提高代码的可维护性,因为它们提供了一种标准化的解决方案。
  5. 可扩展性:设计模式通常具有良好的可扩展性,可以方便地添加新的功能。

1.2 设计模式分类

设计模式通常根据它们在软件设计中的作用和目的被分为三大类:创建型模式、结构型模式和行为型模式

创建型模式

  • 主要关注对象的创建过程,隐藏创建逻辑,而不是直接使用new运算符实例化对象。常用的创建型模式有单例模式、工厂方法模式、抽象工厂模式、建造者模式
  • 解决的问题:创建型模式帮助解决对象创建过程中的耦合问题,提高代码的可维护性和可扩展性。

结构型模式

  • 主要关注对象的组合,以及它们之间的关系。常用的结构型模式有适配器模式、桥接模式、组合模式、装饰器模式、外观模式、代理模式。
  • 解决的问题:结构型模式帮助解决对象组合和对象间关系的问题,降低系统的复杂性,提高代码的灵活性和可维护性。

行为型模式

  • 主要关注对象间的交互以及它们如何相互协作。常用的行为型模式有责任链模式、、迭代器模式、中介者模式、观察者模式、策略模式、模板方法模式。
  • 解决的问题:行为型模式帮助解决对象间的交互问题,提高代码的可复用性和灵活性,同时降低对象间的耦合度。

二、创建型设计模式的美妙之处

创建型设计模式的美妙之处在于它们提供了一种灵活而高效的方式来创建对象,同时解决了一些常见的软件设计问题。以下是创建型设计模式的一些关键优点:

  1. 封装性:创建型模式隐藏了对象创建的具体细节,只暴露出一个创建接口。这使得客户端代码不需要知道对象是如何被创建和表示的。

  2. 控制:它们允许对对象的创建过程进行更细致的控制,比如控制对象的创建数量(单例模式)或者创建特定的对象类型(工厂方法模式)。

  3. 可扩展性:创建型模式使得在不修改现有代码的情况下引入新的对象类型成为可能。例如,工厂方法模式和抽象工厂模式允许在运行时根据条件创建不同的产品。

  4. 解耦:通过将对象的创建逻辑与使用逻辑分离,创建型模式减少了对象创建和使用之间的耦合度。

  5. 复用性:创建型模式促进了代码的复用。例如,建造者模式允许构建复杂对象的各个部分,然后以不同的方式组装它们。

  6. 灵活性:原型模式提供了一种快速创建对象的方式,特别是当对象包含大量状态信息时,通过复制已有对象来创建新对象比从头开始构建要高效得多。

  7. 单一职责原则:创建型模式通常遵循单一职责原则,每个类只负责一件事情,比如对象的创建,这使得代码更加清晰和易于管理。

  8. 易于测试:由于对象的创建逻辑被封装在特定的类中,因此可以更容易地编写单元测试来验证对象的创建过程。

  9. 支持开闭原则:开闭原则是说软件实体应当对扩展开放,对修改封闭。创建型模式通过提供接口来扩展功能,而不需要修改现有代码,从而支持这一原则。

  10. 设计灵活性:创建型模式提供了设计上的灵活性,使得设计者可以在不同的层次上考虑问题,例如,可以在不同的层次上实现对象的创建逻辑。

总的来说,创建型设计模式通过提供一种清晰和一致的方式来创建对象,帮助开发者编写出更加模块化、可维护和可扩展的代码,具体的例子可以往下看看,可能你看到这个地方还不可以体会到设计模式的好处,但是看下面的例子可以帮助你很好的理解。

三、单例模式

 学习一个具体的设计模式之前,我觉得重要的是我们要知道什么场景下会用到下面介绍的设计模式,如果就单例模式来说的话,我们可以知道很多情况下可能有些类在全局都是只需要使用到一个实例变量就可以了,也就是说如果我们是使用到的全局状态管理的话,我们所有要查看状态的操作都只需要访问一个实例就可以了,不需要每次使用到这个变量都去new一次,这样子不仅效率很低而且也可能导致oom问题创建过多对象。

3.1 单例模式介绍

 单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。

单例模式所出现的场景非常简单也是我们日常开发所能见到的,例如:

  1. 数据库的连接池不会反复创建
  2. spring中一个单例模式bean的生成和使用
  3. 在我们平常的代码中需要设置全局的的一些属性保存

3.2 七种单例模式介绍

3.2.1 静态类使用

import org.junit.jupiter.api.Test;

/*
 * @description 静态类单例模式
 * @author 罗添煦
 * @create 2024-08-20 下午2:51
 */
public class Singleton_00 {
    // 私有构造函数,防止外部通过new创建实例
    private Singleton_00() {}

    // 静态内部类,含有单一实例
    private static class SingletonHolder {
        private static final Singleton_00 INSTANCE = new Singleton_00();
    }

    // 公有静态方法,返回唯一的实例
    public static Singleton_00 getInstance() {
        return SingletonHolder.INSTANCE;
    }

    @Test
    public void test() {
        Singleton_00 instance1 = Singleton_00.getInstance();
        Singleton_00 instance2 = Singleton_00.getInstance();
        System.out.println(instance1 == instance2);
    }
}

  使用场景:

  • 全局访问点:当需要在应用程序的任何地方提供一个全局访问点时,静态类单例模式允许通过类名直接访问其静态成员。
  • 资源管理:对于管理共享资源,如配置信息、数据库连接或文件句柄等,静态类单例模式可以确保资源被所有客户端共享且只创建一次。
  • 控制实例数量:当需要严格限制某个类只能有一个实例时,静态类单例模式可以确保这一点。
  • 延迟初始化:如果实例化资源消耗较大,希望在实际需要时才创建实例,静态类单例模式可以配合懒加载实现。

3.2.2 懒汉式(线程不安全)

 懒汉式单例模式就如其名字只有你需要使用的时候才会加载,也就是懒加载在用户有实际使用情况下才会加载实例。

import org.junit.jupiter.api.Test;

/*
 * @description 懒汉式(线程不安全)
 * @author 罗添煦
 * @create 2024-08-20 下午3:34
 */
public class Singleton_01 {

    public static Singleton_01 instance;

    private Singleton_01() {}

    /**
     * 获取唯一实例
     * @return 实例
     */
    public static Singleton_01 getInstance() {
        if (instance == null) {
            instance = new Singleton_01();
        }
        return instance;
    }

    /**
     * 测试是否是单例对象
     */
    @Test
    public void test1() {
        Singleton_01 instance1 = Singleton_01.getInstance();
        Singleton_01 instance2 = Singleton_01.getInstance();
        System.out.println(instance1 == instance2);
    }

    /**
     * 测试多线程,导致线程不安全问题
     */
    @Test
    public void test2() {
        Thread thread1 = new Thread(()->{
            Singleton_01 instance1 = Singleton_01.getInstance();
            System.out.println(instance1);
        });
        Thread thread2 = new Thread(()->{
            Singleton_01 instance2 = Singleton_01.getInstance();
            System.out.println(instance2);
        });
        thread1.start();
        thread2.start();
    }

}

在你进行test2多次执行的情况之下,就会可能导致下面的情况可能会导致两个加载的实例是不一致的。

所以这个时候我们就需要处理这个问题,我们都一个是单例模式了,导致了产生不同的实例对象,这个时候我们就需要保证线程安全保证只生成一个实例。 

3.2.3 懒汉式(线程安全)

import org.junit.jupiter.api.Test;

/*
 * @description 懒汉式(线程安全)
 * @author 罗添煦
 * @create 2024-08-20 下午3:35
 */
public class Singleton_02 {

    public static Singleton_02 instance;

    private Singleton_02() {}

    /**
     * 获取唯一实例
     * @return 实例
     */
    public static synchronized Singleton_02 getInstance() {
        if (instance == null) {
            instance = new Singleton_02();
        }
        return instance;
    }

    /**
     * 测试是否是单例对象
     */
    @Test
    public void test1() {
        Singleton_02 instance1 = Singleton_02.getInstance();
        Singleton_02 instance2 = Singleton_02.getInstance();
        System.out.println(instance1 == instance2);
    }

    /**
     * 测试多线程,导致线程不安全问题
     */
    @Test
    public void test2() {
        Thread thread1 = new Thread(()->{
            Singleton_02 instance1 = Singleton_02.getInstance();
            System.out.println(instance1);
        });
        Thread thread2 = new Thread(()->{
            Singleton_02 instance2 = Singleton_02.getInstance();
            System.out.println(instance2);
        });
        thread1.start();
        thread2.start();
    }
}
  • 此种模式虽然是安全的,但由于把锁加到方法上后,所有的访问都因需要锁占用导致资源的浪费。如果不是特殊情况下,不建议此种方式实现单例模式。

3.2.4 饿汉式

import org.junit.jupiter.api.Test;

/*
 * @description 饿汉式
 * @author 罗添煦
 * @create 2024-08-20 下午3:35
 */
public class Singleton_03 {
    private static Singleton_03 instance = new Singleton_03();

    private Singleton_03() {
    }

    public static Singleton_03 getInstance() {
        return instance;
    }

    @Test
    public void test1() {
        Singleton_03 instance1 = Singleton_03.getInstance();
        Singleton_03 instance2 = Singleton_03.getInstance();
        System.out.println(instance1 == instance2);
    }
}
  • 此种方式与我们开头的第一个静态类实例化基本一致,在程序启动的时候直接运行加载,后续有外部需要使用的时候获取即可。
  • 但此种方式并不是懒加载,也就是说无论你程序中是否用到这样的类都会在程序启动之初进行创建。

3.2.5 使用类的内部类

/*
 * @description 使用内部类
 * @author 罗添煦
 * @create 2024-08-20 下午3:35
 */
public class Singleton_04 {
    
    private static class SingletonHolder {
        private static Singleton_04 instance = new Singleton_04();
    }

    private Singleton_04() {
    }

    public static Singleton_04 getInstance() {
        return SingletonHolder.instance;
    }
}
  • 使用类的静态内部类实现的单例模式,既保证了线程安全又保证了懒加载,同时不会因为加锁的方式耗费性能。
  • 这主要是因为JVM虚拟机可以保证多线程并发访问的正确性,也就是一个类的构造方法在多线程环境下可以被正确的加载。

3.2.6 双锁校验机制

import org.junit.jupiter.api.Test;

/*
 * @description 双锁校验机制
 * @author 罗添煦
 * @create 2024-08-20 下午3:36
 */
public class Singleton_05 {

    public static volatile Singleton_05 instance;

    private Singleton_05() {}

    /**
     * 获取唯一实例
     * @return 实例
     */
    public static Singleton_05 getInstance() {
        if (instance != null) return instance;
        synchronized (Singleton_05.class){
            if (instance == null) {
                instance = new Singleton_05();
            }
            return instance;
        }
    }

    /**
     * 测试是否是单例对象
     */
    @Test
    public void test1() {
        Singleton_05 instance1 = Singleton_05.getInstance();
        Singleton_05 instance2 = Singleton_05.getInstance();
        System.out.println(instance1 == instance2);
    }

    /**
     * 测试多线程,导致线程不安全问题
     */
    @Test
    public void test2() {
        Thread thread1 = new Thread(()->{
            Singleton_05 instance1 = Singleton_05.getInstance();
            System.out.println(instance1);
        });
        Thread thread2 = new Thread(()->{
            Singleton_05 instance2 = Singleton_05.getInstance();
            System.out.println(instance2);
        });
        thread1.start();
        thread2.start();
    }
}
  • 双重锁的方式是方法级锁的优化,减少了部分获取实例的耗时。
  • 同时这种方式也满足了懒加载
  • 但是有个地方需要特别注意我们在实例上需要加上volatile关键字来保持可见性,不然当一个程序进行初始化的时候另一个程序已经初始化了我们就需要实时的可以知道实例对象是否更新

3.2.7 单例枚举模式

import org.junit.jupiter.api.Test;

/*
 * @description 单例枚举模式
 * @author 罗添煦
 * @create 2024-08-20 下午3:36
 */
public enum Singleton_06 {
    INSTANCE;
    public void testA(){
        System.out.println("hi~");
    }
}
  • Effective Java 作者推荐使用枚举的方式解决单例模式,此种方式可能是平时最少用到的。
  • 这种方式解决了最主要的;线程安全、自由串行化、单一实例。

四、工厂模式

4.1 工厂模式介绍

工厂模式,用口语化的方式理解,可以想象成一家餐厅里的“点餐系统”。在这个系统中,顾客(客户端代码)不需要知道食物(对象)是怎么做出来的,只需要告诉服务员(工厂)他想要什么。服务员会去厨房(工厂内部逻辑)告诉厨师(具体的类),然后厨师根据要求做出相应的美食(实例化对象),再由服务员端给顾客。

就像这样:

  1. 顾客:"你好,我想要一份汉堡。"
  2. 服务员:"好的,请稍等。"(接收到请求,知道需要制作一个汉堡)
  3. 厨师:根据服务员的指示,开始制作汉堡。
  4. 服务员:汉堡做好了,端给顾客。

在这个过程中,顾客不需要知道汉堡是如何制作的,也不需要直接和厨师交流。他只需要向服务员点餐,后面的事情就由服务员和厨师来完成。这样一来,如果餐厅推出了新的菜品,比如披萨,顾客也只需要向服务员点披萨,而不需要知道披萨的制作过程。

重点 :工厂模式我们需要关注的产品是同一类的产品,就比如华为手机和苹果手机都同属于手机,所以我们可以在工厂里面去创建,也可以叫做具体工厂。

4.2 工厂模式实现

 我们下面用一个造交通工具的工厂来说明一下

想象我们有一个Vehicle接口,就像一个“交通工具”的概念。然后我们有几种不同的交通工具,比如Car(汽车)和Bike(自行车),它们都实现了Vehicle接口。

// 想象这是“交通工具”的一个通用概念
interface Vehicle {
    void drive(); // 所有交通工具都能“drive”(开)
}

// 这是“汽车”实现的“交通工具”概念
class Car implements Vehicle {
    public void drive() {
        System.out.println("汽车在路上开");
    }
}

// 这是“自行车”实现的“交通工具”概念
class Bike implements Vehicle {
    public void drive() {
        System.out.println("自行车在自行车道上骑");
    }
}

现在,我们有一个VehicleFactory工厂接口,就像一个“交通工具制造厂”,它有一个方法来创建交通工具。

// 这是“交通工具制造厂”的蓝图
interface VehicleFactory {
    Vehicle createVehicle(String type); // 根据类型创建交通工具
}

我们实现了这个工厂接口,叫做SimpleVehicleFactory,它知道怎么制造汽车和自行车。

// 这是简单的“交通工具制造厂”实现
class SimpleVehicleFactory implements VehicleFactory {
    public Vehicle createVehicle(String type) {
        if ("car".equals(type)) {
            return new Car(); // 如果要的是汽车,就制造一个汽车
        } else if ("bike".equals(type)) {
            return new Bike(); // 如果要的是自行车,就制造一个自行车
        }
        throw new IllegalArgumentException("不知道如何制造这种交通工具");
    }
}

最后,我们看看怎么使用这个工厂来制造交通工具:

public class FactoryPatternDemo {
    public static void main(String[] args) {
        // 创建我们的“交通工具制造厂”实例
        VehicleFactory factory = new SimpleVehicleFactory();

        // 通过工厂制造一个汽车
        Vehicle car = factory.createVehicle("car");
        car.drive(); // 输出:汽车在路上开

        // 通过工厂制造一个自行车
        Vehicle bike = factory.createVehicle("bike");
        bike.drive(); // 输出:自行车在自行车道上骑
    }
}

五、抽象工厂模式

5.1 抽象工厂介绍

 首先我们要知道为什么是叫做抽象工厂,抽象工厂是因为它所创建的和工厂模式有区别,抽象工厂可以创建的是一组互相相关互相依赖的产品,如我可以创建一个电脑配件工厂,可以同时创建电脑的多个配件,如显示器、键盘、鼠标等,这些配件属于同一个品牌或系列。

也就是在抽象工厂内我们需要一个概念叫做产品族,一个品牌下面的所有产品;例如华为下面的手机,路由器,电脑称为华为的产品族;

我觉得只要看懂上面的UML图就可以很好的理解这个概念了,首先我们需要关注的是抽象产品,也就是产品族的概念一个产品族下可能会有很多的不同的产品比如电脑、手机,而电脑和手机下又会有不同的品牌把品牌确定下来之后就可以说是具体产品了,然后这些产品只需要在抽象工厂下找到自己的具体的工厂就可以进行创建。

5.2 抽象工厂实现

抽象工厂下面我就使用手机工厂的例子来给理解一下,下面分为ipone、huawei两个厂家,他们可以创建不同的产品。类结构如下所示

首先我们需要创建抽象产品接口Phone和Computer接口

Computer接口

/*
 * @description 电脑抽象产品
 * @author 罗添煦
 * @create 2024-08-21 上午9:57
 */
public interface Computer {
    /**
     * 电脑开机
     */
    void trunOn();
    /*
    * 电脑关机
     */
    void trunOff();
}

Phone接口 

/*
 * @description 手机抽象产品
 * @author 罗添煦
 * @create 2024-08-21 上午9:57
 */
public interface Phone {
    /*
    * @description 发送邮件
     */
    void sendEamil();
    /*
    * @description 关闭手机
     */
    void trunOff();
    /*
    * @description 打开手机
     */
    void trunOn();
    /*
    * @description 打电话
     */
    void callUp();
}

创建抽象产品下的具体产品这个地方我们需要创建苹果手机、苹果电脑、华为手机、华为电脑。

/*
 * @description 苹果手机
 * @author 罗添煦
 * @create 2024-08-21 上午9:58
 */
public class PgPhone implements Phone{
    @Override
    public void sendEamil() {
        System.out.println("PG手机发送邮件");
    }

    @Override
    public void trunOff() {
        System.out.println("PG手机关机");
    }

    @Override
    public void trunOn() {
        System.out.println("PG手机开机");
    }

    @Override
    public void callUp() {
        System.out.println("PG手机打电话");
    }
}


/*
 * @description 苹果电脑
 * @author 罗添煦
 * @create 2024-08-21 上午10:06
 */
public class PgComputer implements Computer{
    @Override
    public void trunOn() {
        System.out.println("苹果电脑开机");
    }

    @Override
    public void trunOff() {
        System.out.println("苹果电脑关机");
    }
}


/*
 * @description 华为手机
 * @author 罗添煦
 * @create 2024-08-21 上午9:58
 */
public class HuaweiPhone implements Phone{
    @Override
    public void sendEamil() {
        System.out.println("华为手机发送邮件");
    }

    @Override
    public void trunOff() {
        System.out.println("华为手机关机");
    }

    @Override
    public void trunOn() {
        System.out.println("华为手机开机");
    }

    @Override
    public void callUp() {
        System.out.println("华为手机打电话");
    }
}


/*
 * @description 华为电脑
 * @author 罗添煦
 * @create 2024-08-21 上午10:05
 */
public class HuaweiComputer implements Computer{
    @Override
    public void trunOn() {
        System.out.println("华为电脑开机");
    }

    @Override
    public void trunOff() {
        System.out.println("华为电脑关机");
    }
}

 创建抽象工厂类

/*
 * @description 抽象产品工厂
 * @author 罗添煦
 * @create 2024-08-21 上午10:00
 */
public interface ProductFactory {
    /**
     * 创建手机
     * @return 返回具体手机
     */
    Phone createPhone();

    /**
     * 创建电脑
     * @return 返回具体电脑
     */
    Computer createComputer();
}

创建具体工厂

/*
 * @description 华为产品工厂
 * @author 罗添煦
 * @create 2024-08-21 上午10:07
 */
public class HuaweiFactory implements ProductFactory{
    @Override
    public Phone createPhone() {
        return new HuaweiPhone();
    }

    @Override
    public Computer createComputer() {
        return new HuaweiComputer();
    }
}


/*
 * @description 苹果产品工厂
 * @author 罗添煦
 * @create 2024-08-21 上午10:01
 */
public class PgFactory implements ProductFactory{

    @Override
    public Phone createPhone() {
        return new PgPhone();
    }

    @Override
    public Computer createComputer() {
        return new PgComputer();
    }
}

 测试类

/*
 * @description 抽象工厂测试类
 * @author 罗添煦
 * @create 2024-08-21 上午10:15
 */
public class AbstractFactoryTest {
    public static void main(String[] args) {
        //创建苹果工厂
        ProductFactory pgFactory = new PgFactory();
        //创建苹果电脑
        Computer pgComputer = pgFactory.createComputer();
        pgComputer.trunOn();
        Phone pgPhone = pgFactory.createPhone();
        pgPhone.trunOn();
        //创建华为工厂
        ProductFactory huaweiFactory = new HuaweiFactory();
        Computer huaweiComputer = huaweiFactory.createComputer();
        huaweiComputer.trunOn();
    }
}

如果建立了抽象工厂之后我们如果后期工厂还需要创建新的产品我们就可以直接在抽象产品内加上一个耳机抽象产品然后在具体工厂里面创建就好了,如果使用ifelse实现的话我们就需要平凡的去改动工厂的代码。

// 华为工厂(改动前)
class HuaweiFactory {
    // 根据传入的类型创建不同的电子设备
    public ElectronicDevice createDevice(String type) {
        if ("phone".equalsIgnoreCase(type)) {
            return new HuaweiPhone();
        } else if ("computer".equalsIgnoreCase(type)) {
            return new HuaweiComputer();
        } else {
            throw new IllegalArgumentException("未知的设备类型: " + type);
        }
    }
}

//华为工厂(改动后)
class HuaweiFactory {
    // 根据传入的类型创建不同的电子设备
    public ElectronicDevice createDevice(String type) {
        if ("phone".equalsIgnoreCase(type)) {
            return new HuaweiPhone();
        } else if ("computer".equalsIgnoreCase(type)) {
            return new HuaweiComputer();
        } else if ("headset".equalsIgnoreCase(type)) {
            return new HuaweiHeadset();
        }else {
            throw new IllegalArgumentException("未知的设备类型: " + type);
        }
    }
}

六、建造者模式

6.1 建造者模式介绍

 构建者模式主要用于解决对象创建过程中的复杂性问题,特别是当对象的构建涉及到多个步骤或者具有多个组件时。构建者模式允许你将一个复杂对象的构建与其表示分离,从而使得同样的构建过程可以创建不同的表示。

6.2 建造者模式实现

很多时候我们去商场买东西,我们只是关注的是商品的本身而不是商品是如何被一步一步创建出来的,而创建者就是把一个产品分为多个步骤一步一步的组装出来。

下面我们以一个简单的列子去介绍一下建造者模式也就是建造汽车的一个例子。

// 产品角色:汽车
class Car {
    private String engine;
    private String wheels;
    private String color;

    // 构造函数,私有化以防止外部直接创建
    private Car(Builder builder) {
        this.engine = builder.engine;
        this.wheels = builder.wheels;
        this.color = builder.color;
    }

    // 产品方法
    public void assemble() {
        System.out.println("汽车组装完成:");
        System.out.println("引擎:" + engine);
        System.out.println("轮子:" + wheels);
        System.out.println("颜色:" + color);
    }

    // 构建者接口
    public static class Builder {
        private String engine;
        private String wheels;
        private String color;

        public Builder setEngine(String engine) {
            this.engine = engine;
            return this;
        }

        public Builder setWheels(String wheels) {
            this.wheels = wheels;
            return this;
        }

        public Builder setColor(String color) {
            this.color = color;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }
}

// 客户端代码
class Client {
    public static void main(String[] args) {
        Car car = new Car.Builder()
                .setEngine("V8")
                .setWheels("20 inch")
                .setColor("Red")
                .build();

        car.assemble();
    }
}

我们看建造者模式一个需要特别关注的一点就是链式编程我们每次操作后都是返回了对象。

七、原型模式

7.1 原型模式介绍

它使用一个已有的实例作为原型,通过复制这个原型来创建新的实例,而不是通过新建实例的方式来创建对象。这种模式适用于对象的创建成本较高,或者对象的创建过程复杂,或者对象的创建需要灵活性时。

从上面的UML图可以看出,原型模式涉及到的角色有如下三个:

- 客户端角色:负责创建对象的请求。

- 抽象原型角色:该角色是一个抽象类或者是接口,提供拷贝的方法。

- 具体原型角色:该角色是拷贝的对象,需要重写抽象原型的拷贝方法,实现浅拷贝或者深拷贝。

深浅拷贝如果不清楚的建议去啊看一下我的另一篇博客java深浅拷贝 

7.2 原型模式实现

我们生活中很常见的一个例子就可以使用到原型模式来代办,我们都知道小学生可能会发各种各样的奖状但是奖状很大部分都是相同的,只有在学生的个人信息或者获得的奖项上有所区别这个时候其实我们就可以用到原型模式。

创建抽象原型接口

/*
 * @description: 奖状接口
 */
interface AwardCertificate {
    AwardCertificate clone(); // 克隆方法
    void setStudentName(String name); // 设置学生姓名
    void setAwardName(String award); // 设置奖项名称
    void display(); // 展示奖状
}

创建具体的奖状类

/*
 * @description: 奖状类
 */
class ConcreteAwardCertificate implements AwardCertificate {
    private String studentName;
    private String awardName;
    private static String format = "学生 %s 在本学期表现优异,特此颁发 %s 奖项。";

    public ConcreteAwardCertificate() {
        // 默认构造函数,可以添加一些奖状的初始化信息
    }

    @Override
    public AwardCertificate clone() {
        try {
            // 对于奖状格式等静态信息,无需复制,因为它们是不变的
            return (ConcreteAwardCertificate) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public void setStudentName(String name) {
        this.studentName = name;
    }

    @Override
    public void setAwardName(String award) {
        this.awardName = award;
    }

    @Override
    public void display() {
        System.out.println(String.format(format, studentName, awardName));
    }
}

 创建学校类作为测试类

// 客户端代码
class School {
    public static void main(String[] args) {
        // 创建一个奖状原型
        ConcreteAwardCertificate prototype = new ConcreteAwardCertificate();
        
        // 假设已经为一个学生创建了奖状
        prototype.setStudentName("张三");
        prototype.setAwardName("优秀学生奖");
        prototype.display();

        // 现在需要为另一个学生颁发相同的奖状,只需复制原型并修改学生姓名
        AwardCertificate newCertificate = prototype.clone();
        newCertificate.setStudentName("李四");
        newCertificate.setAwardName("优秀学生奖");
        newCertificate.display();
    }
}

客户端代码 School 中,我们首先创建了一个奖状的原型,并为一个学生设置了姓名和奖项,然后展示了奖状。接下来,我们通过调用 clone() 方法复制了这个原型,并为新的学生设置了不同的姓名,再次展示奖状。这样,我们就快速地为不同的学生生成了格式相同的奖状,只修改了必要的个人信息。 

八、总结

创建型设计模式是用于创建对象的设计模式,它们旨在提供一种灵活的方式来创建对象,同时隐藏对象的创建逻辑和细节。以下是创建型设计模式的一些好处和缺点以及适用场景:

好处:

  1. 隐藏对象的创建细节:创建型设计模式可以将对象的创建逻辑封装起来,使客户端代码无需关心具体的创建细节,从而简化了代码并提高了代码的可维护性。
  2. 提供灵活的创建方式:创建型设计模式可以根据需求提供不同的创建方式,例如通过工厂模式可以根据不同的参数创建不同的对象,通过原型模式可以复制现有对象来创建新对象等。
  3. 降低依赖性:创建型设计模式可以降低代码之间的依赖性,通过抽象工厂模式、建造者模式等可以将对象的创建解耦出来,使得代码更加灵活、可扩展和可复用。

缺点:

  1. 增加了复杂性:创建型设计模式可能引入额外的类和接口,从而增加了代码的复杂性和理解难度。
  2. 增加了系统的灵活性:创建型设计模式的灵活性可能会导致系统的复杂性增加,从而增加了代码的维护成本和调试难度。

适用场景:

  1. 需要创建一组相关或相互依赖的对象时,可以使用抽象工厂模式。
  2. 需要按照一定的顺序和步骤来创建对象时,可以使用建造者模式。
  3. 需要复制对象时,可以使用原型模式。
  4. 需要根据条件来创建对象时,可以使用工厂方法模式。
  5. 需要为对象的创建提供一个统一的接口时,可以使用简单工厂模式。
  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

用草书谱写兰亭序

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

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

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

打赏作者

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

抵扣说明:

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

余额充值