设计模式——工厂模式、抽象工厂、单例模式、建造者模式、原型模式

目录

设计模式

1.1、工厂模式

1.2、抽象工厂模式

1.3、单例模式

单例模式的几种实现方式

1.3.1 懒汉式(线程不安全)

1.3.2 懒汉式-优化(线程安全)

1.3.3 饿汉式

1.3.4 懒汉式-优化(线程安全)

1.3.5 登记式/静态内部类

1.3.6 枚举

单例模式常见的场景  

1.4建造者模式

建造者模式常见的情况

1.5原型模式


设计模式

设计模式简介 | 菜鸟教程      

        设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。

        设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。

1.1、工厂模式

        定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行

        应用实例: 1、您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。 2、Hibernate 换数据库只需换方言和驱动就可以。

        优点: 1、一个调用者想创建一个对象,只要知道其名称就可以了。 2、扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以。 3、屏蔽产品的具体实现,调用者只关心产品的接口。

        缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。

        常见场景

  1. 数据库连接工厂:在后端应用程序中,经常需要与数据库进行交互。使用工厂模式可以创建一个数据库连接工厂,根据配置文件或者运行时参数的不同,动态地选择使用不同类型的数据库连接(例如MySQL、PostgreSQL等),而无需在代码中直接实例化具体的数据库连接对象。

  2. 日志记录器工厂:日志记录在后端开发中非常重要,可以用于调试和问题追踪。使用工厂模式可以创建一个日志记录器工厂,根据需要选择使用不同的日志记录器(例如文件日志、数据库日志等),而不需要在代码中显式地创建特定类型的日志记录器。

  3. 消息队列工厂:后端应用程序中经常需要处理异步任务和消息队列。使用工厂模式可以创建一个消息队列工厂,根据需求选择使用不同的消息队列实现(例如RabbitMQ、Kafka等),而无需直接实例化特定的消息队列对象。

  4. 邮件发送工厂:在后端应用程序中发送电子邮件是常见的任务。使用工厂模式可以创建一个邮件发送工厂,根据配置或者运行时参数选择使用不同的邮件发送实现(例如SMTP、SendGrid等),而不需要在代码中直接实例化具体的邮件发送对象。

image.png

1.2、抽象工厂模式

       抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂

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

        缺点:产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。

        使用场景: 1、QQ 换皮肤,一整套一起换。 2、生成不同操作系统的程序。

        常见场景

  1. 数据库访问抽象工厂:在后端应用程序中,可能需要对不同类型的数据库进行访问,例如关系型数据库和NoSQL数据库。抽象工厂模式可以定义一个数据库访问抽象工厂接口,然后具体的数据库访问工厂实现可以分别实现该接口来创建不同类型的数据库访问对象(例如MySQL访问对象、MongoDB访问对象等)。

  2. 文件操作抽象工厂:后端应用程序中可能需要进行文件的读取、写入和操作。使用抽象工厂模式可以定义一个文件操作抽象工厂接口,具体的文件操作工厂实现可以根据需要创建不同类型的文件操作对象(例如本地文件操作对象、云存储文件操作对象等)。

  3. 缓存抽象工厂:后端应用程序中常常需要使用缓存来提高性能。抽象工厂模式可以定义一个缓存抽象工厂接口,具体的缓存工厂实现可以根据配置或者运行时参数来选择创建适合的缓存对象(例如Redis缓存对象、Memcached缓存对象等)。

  4. 队列抽象工厂:后端应用程序中可能需要使用消息队列来处理异步任务和事件驱动的逻辑。抽象工厂模式可以定义一个队列抽象工厂接口,具体的队列工厂实现可以根据需求创建不同类型的消息队列对象(例如RabbitMQ队列对象、Kafka队列对象等)。

        这些例子展示了抽象工厂模式在后端开发中的一些常见应用。抽象工厂模式可以帮助组织相关的对象家族,并提供一种灵活的方式来创建和使用这些对象,使系统更易于扩展和维护。具体的应用场景和设计取决于业务需求和系统的架构。

image.png

1.3、单例模式

   “ 单例模式 ” 案例:

    创建一个 Singleton 类,解释如下:

public class SingleObject {
 
   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}
 
   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

   从 singleton 类获取唯一的对象。

public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      //不合法的构造函数
      //编译时错误:构造函数 SingleObject() 是不可见的
      //SingleObject object = new SingleObject();
 
      //获取唯一可用的对象
      SingleObject object = SingleObject.getInstance();
 
      //显示消息
      object.showMessage();
   }
}

        至此,一个单例模式的案例完成。

        在这个示例中,SingleObject类只有一个静态私有成员变量instance,它持有SingleObject类的唯一实例。构造函数被声明为私有,这样就防止了外部对该类进行实例化

通过静态的getInstance()方法,可以获取到SingleObject类的实例,该方法返回之前创建的唯一实例。由于instance是静态的,并且在类加载时就初始化,因此在调用getInstance()方法时,总是返回同一个实例。

SingleObject类还有一个showMessage()方法,用于输出"Hello World!"的信息。

这个简单的示例展示了单例模式的基本结构,通过限制实例化和提供唯一访问点,确保了只有一个实例被创建和使用。

单例模式的几种实现方式
1.3.1 懒汉式(线程不安全)
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

        这种实现方式的一个特点是只有在需要使用单例对象时才会进行实例化,而不是在程序启动时就创建单例对象。这种延迟创建的方式可以在某些情况下节省资源,特别是在单例对象比较复杂或者初始化耗时较长时。

需要注意的是,该代码示例并不是线程安全的。在多线程环境下,可能会导致多个线程同时检测到instance为空,从而创建多个实例。为了实现线程安全的懒加载单例模式,可以使用双重检查锁(double-checked locking)或者其他线程安全的方式对getInstance()方法进行改进。

1.3.2 懒汉式-优化(线程安全)
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

        这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步,下面的案例会对此进行优化!

        优点:第一次调用才初始化,避免内存浪费。
        缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。

1.3.3 饿汉式
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}

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

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

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

1.3.4 懒汉式-优化(线程安全)

      下面是使用双重检查锁(double-checked locking)优化:

public class Singleton {
    private static volatile Singleton instance; // 使用volatile修饰,确保可见性和禁止指令重排序

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查,避免不必要的同步
            synchronized (Singleton.class) {
                if (instance == null) { // 第二次检查,确保只有一个线程创建实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

        在改进后的代码中,使用了双重检查锁机制。在第一次检查时,如果instance为空,才会进入同步块。进入同步块后,再次检查instance是否为空,以确保只有一个线程创建实例。

        使用volatile关键字修饰instance变量可以确保在多线程环境下对其的可见性和禁止指令重排序。这样可以避免在某些情况下获取到一个尚未完全初始化的实例。

        通过双重检查锁和volatile关键字的改进,可以保证在多线程环境下只有一个实例被创建,并且在需要时进行懒加载

        此案例是在单例模式 | 菜鸟教程的线程安全-lazy loading 的案例的优化方案,将锁synchronized之外先做一次判断,然后再加synchronized 锁,可以提高性能

1.3.5 登记式/静态内部类
public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

        这种方式能实现和双检锁方式一样的功效,但实现更简单。

        如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 1.3.3 的方式就显得很合理。

        可以延迟加载,只有当显示装载SingleonHolder.INSTANCE才能初始化实例(单例),从而实例化 instance。

1.3.6 枚举
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

        这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化
        这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。

单例模式 | 菜鸟教程 <= 内容出处参考

经验之谈:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式

image.png

单例模式常见的场景  
  1. 资源共享:当多个对象需要共享同一个资源时,可以使用单例模式确保只有一个实例管理该资源。例如,数据库连接池、线程池等资源管理器常常使用单例模式。

  2. 对象缓存:当需要缓存对象以提高性能时,可以使用单例模式来管理缓存对象。这样可以避免重复创建相同的对象,节省系统资源。例如,字体缓存、图片缓存等。

  3. 日志记录器:单例模式常用于实现全局的日志记录器,以便在整个应用程序中方便地进行日志记录。

  4. 配置信息管理:单例模式可以用于管理全局的配置信息对象,使得配置信息在应用程序中的访问和使用更加方便。

  5. GUI组件:在图形用户界面(GUI)开发中,某些组件通常只需要一个实例,例如对话框、打印机管理器等。

        需要注意的是,单例模式在设计时要慎重考虑,过度使用单例可能会导致代码耦合度增加、测试困难等问题。在使用单例模式时,需要确保其真正符合设计需求,并且在多线程环境下注意线程安全问题。

1.4建造者模式

        建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式

        一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。

        应用实例: 1、去塔斯汀,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐"。 2、JAVA 中的 StringBuilder。              

        优点:

  • 分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
  • 可以更好地控制构建过程,隐藏具体构建细节。
  • 代码复用性高,可以在不同的构建过程中重复使用相同的建造者。

        缺点:

  • 如果产品的属性较少,建造者模式可能会导致代码冗余。
  • 建造者模式增加了系统的类和对象数量。

        使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。

        建造者模式在创建复杂对象时非常有用,特别是当对象的构建过程涉及多个步骤或参数时。它可以提供更好的灵活性和可维护性,同时使得代码更加清晰可读。

        注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。

建造者模式常见的情况
  1. 创建复杂的配置对象:在后端应用程序中,经常需要创建具有复杂配置的对象,例如数据库连接对象、API请求对象等。使用建造者模式可以将构建过程抽象化,使得可以根据需要动态地配置对象的各个属性和选项。

  2. 构建数据库查询:当需要构建复杂的数据库查询时,可以使用建造者模式。建造者模式可以将查询的各个组成部分(如SELECT子句、WHERE条件、排序等)进行封装,并提供一种流畅的接口来构建查询,使得代码可读性更好。

  3. 创建复杂的数据结构:有时需要创建复杂的数据结构,例如树形结构、图结构等。使用建造者模式可以将创建过程分解为多个步骤,并提供一种逐步构建的方式来创建复杂的数据结构对象。

  4. 构建消息或事件对象:在消息队列、事件驱动的系统中,经常需要创建包含不同属性和选项的消息或事件对象。使用建造者模式可以封装对象的构建过程,使得可以动态地设置不同的属性和选项,便于创建和发送各种类型的消息或事件。

        总的来说,建造者模式在后端开发中适用于需要创建复杂对象或具有多个配置选项的对象的情况。它可以提供一种结构化的构建过程,并允许动态配置对象的属性和选项,从而提高代码的可读性、可维护性和灵活性。(低代码平台?)

       

image.png

1.5原型模式

        原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一

        优点: 

        1、性能提高

        2、逃避构造函数的约束。(直接拷贝了,不用通过构造函数构造)

        缺点: 

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

         2、必须实现 Cloneable 接口。

        使用场景: 

        1、资源优化场景。

        2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。

        3、性能和安全要求的场景。

        4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。

        5、一个对象多个修改者的场景(在线文档那种)。

        6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。

        7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

image.png

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值