【设计模式学习】单例模式和工厂模式

简介: 【设计模式学习】单例模式和工厂模式

一.设计模式

1.什么是设计模式

设计模式是在软件设计中反复出现的问题和解决方案的经验总结。它是对软件设计中常见问题的通用解决方案,可以帮助设计人员更高效地解决问题,并提高软件设计的质量和可维护性。设计模式提供了一种在特定情况下的解决方案,它们可以被反复使用,并且是经过验证的最佳实践。设计模式可以帮助开发人员更好地理解软件设计中的问题,并为他们提供一种标准的方法来解决这些问题。通俗易懂的一点来说:就是提高你的下限

设计模式就像是建筑行业的预制构件或者烹饪领域的配方,它为程序员在面对特定设计挑战时提供了一个经过验证、成熟且可复用的方案蓝图(都是大佬们写的绝对牛逼好用)。例如,在软件开发中,如果你需要创建多个相似对象,但又希望避免大量重复代码,你可以参考“工厂模式”来设计一个统一的创建接口;当你想要改变对象的行为,而又不修改对象本身时,可以运用“策略模式”动态地替换对象的算法。

所以,掌握和灵活运用设计模式,不仅能够提升编程效率,使得代码结构更加清晰合理,增强代码的可读性和可维护性,还能帮助团队成员之间通过共享的设计语言进行更有效的沟通与协作,从而提升整个项目的开发质量和效率。

2.设计模式的重要性

  1. 代码复用性
  • 设计模式为常见问题提供了标准化的解决方法,这些方法已经在实践中得到广泛验证,能够确保代码结构的稳定性。通过应用设计模式,开发人员可以创建模块化、可重用的组件,避免重复发明轮子,从而提高了代码的复用性。
  1. 代码可读性和可维护性
  • 使用设计模式能够使代码结构更加清晰,遵循一定的命名约定和组织原则,使得其他开发者更容易理解代码的工作机制,进而降低了维护成本。良好的设计模式应用还能减少由于代码复杂度引起的潜在错误。
  1. 系统灵活性与扩展性
  • 设计模式强调“开闭原则”,即对扩展开放,对修改关闭。这意味着设计模式鼓励构建低耦合、高内聚的系统,这样在不改动现有代码的基础上就能轻松添加新功能或适应变化的需求。
  1. 团队协作与沟通
  • 在团队开发环境中,设计模式充当了一种通用的设计语言,让开发人员能够快速准确地传达复杂的设计思想和实现策略,提高了团队成员间的沟通效率和协作水平。
  1. 软件质量提升
  • 应用设计模式通常意味着遵循最佳实践,这有助于提高软件的稳定性和健壮性,从而间接提升了软件产品的整体质量。
  1. 重构和优化
  • 设计模式提供了解决特定问题的标准途径,当需要对现有代码进行重构以改善性能或简化结构时,设计模式可作为指导方针,帮助开发人员安全高效地进行改造工作。

综上所述,设计模式不仅是提升个人编程技艺的有效工具,更是推动整个软件开发行业进步的关键要素之一。通过学习和掌握设计模式,开发人员能够构建出更加灵活、可维护、可靠且高效的软件系统。最重要的就是一句话,校招什么的常考,日常开发也用的很多

二.单例模式

1.什么是单例模式

单例模式是一种常用的软件设计模式,它确保一个类只有一个实例(也就是在这个进程中,该类只能new出来一个对象),并提供一个全局访问点。这种模式在需要全局共享一个对象的场景中非常有用,例如:

  1. 资源管理
  • 数据库连接池:在整个应用程序运行期间,数据库连接通常是有限的宝贵资源,单例模式可以用来创建一个全局的数据库连接池,确保所有的数据库操作都共享这些连接,而不是频繁地创建和销毁连接。
  • 缓存服务:如Redis或Memcached客户端,通常作为单例实现,以便各个模块都可以共享同一份缓存连接,同时方便进行集中管理和配置。
  1. 日志服务
  • 日志系统通常会采用单例模式,确保整个应用程序只生成一个日志输出器实例,以统一格式和策略处理日志记录。
  1. 工具类
  • RestTemplateHttpClient等HTTP客户端工具类,在Spring框架中常常被设计成单例,避免每次请求都创建新的客户端实例,提高性能并便于集中配置。
  • 在前端开发中,某些全局配置或工具类也需要保证全局唯一,例如事件总线、通用函数库等。
  1. 配置管理
  • 应用程序配置读取类,可能需要在整个系统中共享一套配置,单例模式可以确保配置只加载一次并被全局复用。
  1. 多线程环境下的协调与控制
  • 单例模式可以用于充当线程间共享的协调者角色,比如线程池管理器、任务调度器等。
  1. 应用程序入口
  • 很多应用框架中的主入口类或者服务容器,为了保证整个应用生命周期内只有一个实例存在,会采用单例模式设计。
  1. 其他服务类
  • 文件系统访问类、消息队列客户端、图形用户界面组件工厂等场合,也可能用到单例模式来确保系统内只存在一个共享实例。

总之,任何需要在整个程序范围内保持唯一实例并且允许全局访问的对象,都是单例模式的适用场景 .

2.饿汉模式实现单例模式

1.Java代码

class Singleton {
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
    private Singleton() {
        
    }
}

2.代码解释

1. private static Singleton instance = new Singleton(); 这里设为静态的,就意味着instance  为类属性,只有一个,并且和整个类的生命周期相同,在类被创建的同时被创建,被销毁时同时被销毁,初始化这个类自然也就只创建一次.并且因为只要这个类被创建,就实例化了一个对象,所以也被称为饿汉模式(饿了就先吃(提前实例化)”的特点).

2.public static Singleton getInstance() 获取单例对象的方法,它对外提供了一个全局访问点,任何时候调用此方法都将得到同一个Singleton实例.

3. private Singleton() 可以看到该构造方法是被private所修饰的,也就意味着,其他类不能调用Singleton 类的构造方法,外部就不能通过new关键字实例化该类的对象,保证了单例的特性。当然如果你使用反射的话还是可以访问该方法的,

3.是否为线程安全

因为该类在被JVM创建后,由于instan是静态的也就是类属性,所以,会和该类同时被JVM创建,所以并不存在同步的问题,类加载的过程是由JVM保证线程安全的以在多线程环境下,即使多个线程同时首次访问Singleton getInstance() 方法,也只会有一个线程触发Singleton类的加载,因此它们都会得到相同的Singleton类的实例

3.懒汉模式实现单例模式

1.Java代码实现

class SingletonLazy {
    private static volatile SingletonLazy instance = null;
    
    public static SingletonLazy getInstance() {
        if(instance == null) {
            synchronized (SingletonLazy.class) {
                if(instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy() {
 
    }
}

2.代码解释

1. private static volatile SingletonLazy instance = null;和饿汉模式不一样的是,一开始先不实例化对象,等到调用的getInstance()时候在实例化对象,这样就提高了效率,因为在实际开发中,实例化的对象中可能会包含很多数据,如果我们像饿汉模式一样一创建类就马上实例化对象,后续不一定一开始就需要使用这个对象,就浪费系统资源了,同时为了避免内存可见性问题,和指令重排序问题这里使用关键字 volatile 修饰该属性,避免出现线程安全问题.

2. public static SingletonLazy getInstance() 我们可以看到该方法块里的实例SingletonLazy 对象使用了双重检测以及加 synchronized 锁的方法,实例化对象,并且保证只实例化一次,以及避免线程安全问题,这里博主在通过画图来解释一下这里的双重检测以及加 synchronized 锁的方法,如何保证只实例化一次,以及避免线程安全问题.(因为懒汉模式是单例模式的最为常用的模式,这里需要重点掌握)

总结来说就是:

  1. 第一次检查:在getInstance()方法的开始,首先检查instance是否已经被实例化。如果已经实例化,则直接返回该实例,这样可以避免不必要的同步操作,提高性能。
  2. 同步块:如果instance未被实例化,代码进入同步块。这里的同步操作是关键,它确保了在多线程环境下,只有一个线程能够执行同步块内的代码。
  3. 第二次检查:在同步块内部,进行第二次检查以确认instance是否仍然为null。这是必要的,因为可能在第一个线程检查instancenull并进入同步块的同时,其他线程也检查到instancenull。如果没有第二次检查,当第一个线程创建了实例并释放锁之后,其他线程可能会再次进入同步块并错误地创建新的实例。
  4. 实例化:如果第二次检查确认instance仍然为null,那么在同步块中创建单例对象,并将其实例赋值给instance
  5. 释放锁:完成实例化后,当前线程退出同步块,并释放锁,允许其他线程访getInstance()方法。

3.private SingletonLazy()  和饿汉模式一样可以看到该构造方法是被private所修饰的,也就意味着,其他类不能调用Singleton 类的构造方法,外部就不能通过new关键字实例化该类的对象,保证了单例的特性。当然如果你使用反射的话还是可以访问该方法的.

3.是否为线程安全

由于懒汉模式为了延迟实例化对象,所以在创建类的时候没有一开始就实例化对象而是在调用getInstance()方法后再实例化对象,虽然节约了系统资源,但这样也就意味着在多线程的环境下,会发生线程安全问题,在上文博主解释了使用双重检测和synchronized上锁的机制保证了线程安全问题的一方面,为什么这里要说一方面呢,因为这里还有一个隐藏的线程安全问题即指令重排序问题,这个时候Instance就需要被关键字 volatile 所修饰,使得确保变量的读写操作对所有线程都是可见的,并且防止指令重排.这里博主再通用画图的形式讲解一下指令重排序问题

1.先解释一下什么是指令重排序

在编译器优化和处理器执行过程中,为了提高性能,可能会对指令序列进行重排序。这种重排序在单线程环境下通常不会引起问题,因为每个线程看到的指令执行顺序是一致的。但在多线程环境下,如果重排序导致内存操作的顺序发生变化,就可能出现数据不一致的问题。

使用小明去菜市场买菜来模拟一下cpu执行指令的过程.由于cpu执行指令是一条一条的执行的所以

可以发现这样买的效率就十分低效,所以呢大佬们就设计的编译器或者JVM就十分智能,可以根据实际情况生成指令的排序顺序,就会和原本写的代码的指令排序顺序有所差别,就像上图的例子,妈妈给了小明一个购物清单(代码),小明(编译器或者是JVM)就根据实际情况即( 鱼-> 猪肉 - > 卷心菜 - > 虾 -> 牛肉)对购物清单(代码)的顺序做出了修改,这样就大大提高了代码的执行效率,这种重排序在单线程环境下通常不会引起问题,因为每个线程看到的指令执行顺序是一致的。但在多线程环境下,如果重排序导致内存操作的顺序发生变化,就可能出现数据不一致的问题。

以上就是指令重排序的解释,现在再解释一下饿汉模式出现指令重排序问题的解释:

总结来说就是:

1.线程1获取到锁,判断 instance 为空的,就执行 instance = new SingletonLazy(); 这个操作,

2.这行代码被执行时,它实际上包含了三个步骤:

  1. 分配对象所需的内存空间。
  2. 调用构造函数初始化对象。
  3. instance引用指向分配的内存地址

3. 由于编译器或CPU可能对这三个步骤进行重排序:

     1.分配对象所需的内存空间。

     2.将instance引用指向分配的内存地址。

     3.调用构造函数初始化对象

4.先执行了将 instance引用指向分配的内存地址,但由于构造函数还未执行完毕,此时的 instance指向的是未初始化完成的对象.

5.此时线程2执行,读取到 instance为非空由于双重检测的缘故,就直接返回instance

6.这里线程2返回的instance 对象并没有被初始化,这个时候就产生了BUG

7.所以我们就要一开始使用关键字 volatile 修饰 instance 能确保对instance的写操作(即实例化SingletonLazy对象)对所有线程立即可见,并且不会发生指令重排序,从而保证当其他线程读取到instance非空时,对应的SingletonLazy实例已经完全初始化完成.

8.上述说的场景虽然发生的概率很小,但是为了严谨性我们还是需要加入关键字 volatile 和 双重检测以及锁一起配合确保防止出现线程安全问题.

4.饿汉模式和懒汉模式的区别以及各自的优缺点和应用场景

1.饿汉模式和懒汉模式的区别

1.饿汉模式:饿汉模式在类加载时就完成了单例对象的初始化,也就是说,当类被载入到JVM时,单例就已经创建好了通常通过静态初始化块或直接在静态变量定义时初始化单例对象

2.懒汉模式是在第一次调用getInstance()方法时才进行实例化,即所谓的“懒加载”。需要处理线程安全问题,常见的做法有双重检查锁定。

2.饿汉模式和懒汉模式的优缺点

1.饿汉模式优点

  • 简单易懂,实现简单。
  • 线程安全,因为类加载过程由JVM保证是线程安全的。
  • 由于实例一开始就创建好了,所以在多线程环境下无须担心线程安全问题。

2.饿汉模式缺点

  • 单例对象在程序启动时就创建,如果该对象比较大,或者初始化过程比较耗时,那么可能会造成内存资源的浪费,特别是如果这个单例对象在整个程序运行期间都不一定会被用到的情况下。

3.懒汉模式优点

 延迟初始化,节约内存资源,尤其适合那些大型对象或者需要较多初始化操作的单例

4.懒汉模式缺点

  • 普通懒汉模式实现不具备线程安全,需要额外的同步手段,如上述的双重检查锁定。
  • 双重检查锁定增加了代码复杂度,并且在某些Java版本(旧版)或特定条件下可能受指令重排序的影响,需要使用volatile关键字确保线程安全。

3. 饿汉模式和懒汉模式的应用场景

1.饿汉模式

  • 对象创建成本低,生命周期长,且预计会被频繁使用的场景。
  • 不依赖于其他外部资源初始化的场景。

2.懒汉模式

  • 对象创建成本高,生命周期短,或者不确定是否会被使用的场景。
  • 需要在真正需要时才初始化单例对象的场景。

综上,选择饿汉模式还是懒汉模式取决于具体的使用场景,如果优先考虑内存优化和延迟初始化,可以选择懒汉模式;如果希望实现简单,对初始化时间不太敏感,或者确定单例对象必然会被使用,那么饿汉模式是一个更好的选择。

三.工厂模式

1.什么是工厂模式

工厂模式(Factory Pattern)是一种常用的创建型设计模式,它的目的是用来处理对象的创建机制。在软件开发中,对象的创建通常不是简单的实例化操作,还需要考虑诸如对象的重用、对象创建的封装、减少客户端与具体类的依赖等问题。工厂模式通过提供一个创建对象的接口,让子类决定实例化哪一个类,从而把对象的创建和使用进行分离。

1.工厂模式的分类

工厂模式主要分为以下三种类型:

  1. 简单工厂模式(Simple Factory)
  • 也称为静态工厂方法,通过一个单一的工厂类来创建不同类型的对象。
  • 工厂类通常包含多个静态方法,每个方法对应一种产品类的实例化。
  • 简单工厂模式的缺点是当产品类别增多时,工厂类的方法也会相应增多,导致难以维护。
  1. 工厂方法模式(Factory Method)
  • 定义了一个创建对象的接口,但让实现这个接口的类来决定实例化哪一个类。
  • 每个产品类都有一个对应的工厂类,负责创建该产品类的实例。
  • 工厂方法模式把产品的创建过程封装在具体的工厂类中,较好地解决了简单工厂模式的缺点。
  1. 抽象工厂模式(Abstract Factory)
  • 提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
  • 通常用于系统有多个产品族,且需要统一管理这些产品族的情况。
  • 抽象工厂模式允许客户端使用一个统一的接口来创建不同类型的产品,而不需要关心具体的实现。

2.工厂模式的应用场景

  • 当一个类不知道它所必须创建的具体类的时候。
  • 当一个类希望其子类来指定创建的对象时。
  • 当类的具体类应该由第三方来决定的时候。
  • 当系统不依赖于具体的类,而是依赖于它们共同的接口时。

3.工厂模式的优点

  • 解耦:将对象的创建和使用分离,客户端不需要知道具体的实现类,只需要知道它们的超类或接口。
  • 可扩展性:当需要添加新的产品类时,只需添加一个具体的类和对应的工厂类,而无需修改现有的代码。
  • 封装性:封装了产品的创建逻辑,客户端不需要关心产品的具体实现。
  • 代码清晰:使得客户端代码更加清晰,易于理解和维护。

4.工厂模式的缺点

  • 增加复杂性:对于简单工厂来说,可能会有大量的工厂类,增加了系统的复杂性。
  • 难以选择:对于客户端来说,可能需要从多个工厂类中选择一个合适的工厂来创建对象。

5.总结

工厂模式是一种非常实用的设计模式,它通过定义创建对象的接口,让子类决定具体要实例化的类,从而实现了对象创建和使用的分离。这种模式在实际开发中被广泛应用,特别是在需要灵活创建和管理对象的场景中。通过使用工厂模式,可以提高代码的可维护性、可扩展性和灵活性。

2.Java代码实现

1.简单工厂模式

// 简单工厂模式
interface Product {
    void show();
}
 
class ConcreteProduct1 implements Product {
    @Override
    public void show() {
        System.out.println("这是产品1");
    }
}
 
class ConcreteProduct2 implements Product {
    @Override
    public void show() {
        System.out.println("这是产品2");
    }
}
 
class SimpleFactory {
    public static Product createProduct(String type) {
        if ("product1".equals(type)) {
            return new ConcreteProduct1();
        } else if ("product2".equals(type)) {
            return new ConcreteProduct2();
        } else {
            throw new IllegalArgumentException("无法识别的产品类型");
        }
    }
}
 
// 使用
public class Main {
    public static void main(String[] args) {
        Product product1 = SimpleFactory.createProduct("product1");
        product1.show();
 
        Product product2 = SimpleFactory.createProduct("product2");
        product2.show();
    }
}

2.工厂方法模式

// 工厂方法模式
interface Product {
    void show();
}
 
class ConcreteProduct1 implements Product {
    @Override
    public void show() {
        System.out.println("这是工厂1生产的具体产品1");
    }
}
 
class ConcreteProduct2 implements Product {
    @Override
    public void show() {
        System.out.println("这是工厂2生产的具体产品2");
    }
 
    // ... 更多具体产品类
}
 
abstract class AbstractFactory {
    abstract Product createProduct();
}
 
class ConcreteFactory1 extends AbstractFactory {
    @Override
    Product createProduct() {
        return new ConcreteProduct1();
    }
}
 
class ConcreteFactory2 extends AbstractFactory {
    @Override
    Product createProduct() {
        return new ConcreteProduct2();
    }
}
 
// 使用
public class Main {
    public static void main(String[] args) {
        AbstractFactory factory1 = new ConcreteFactory1();
        Product product1 = factory1.createProduct();
        product1.show();
 
        AbstractFactory factory2 = new ConcreteFactory2();
        Product product2 = factory2.createProduct();
        product2.show();
    }
}

3.抽象工厂模式

// 抽象工厂模式
interface Color {
    void fill();
}
 
interface Shape {
    void draw();
}
 
class Red implements Color {
    @Override
    public void fill() {
        System.out.println("填充红色");
    }
}
 
class Blue implements Color {
    @Override
    public void fill() {
        System.out.println("填充蓝色");
    }
 
    // ... 更多颜色类
}
 
class Circle implements Shape {
    private Color color;
 
    public Circle(Color color) {
        this.color = color;
    }
 
    @Override
    public void draw() {
        System.out.println("绘制圆形");
        color.fill();
    }
}
 
class Square implements Shape {
    private Color color;
 
    public Square(Color color) {
        this.color = color;
    }
 
    @Override
    public void draw() {
        System.out.println("绘制正方形");
        color.fill();
    }
 
    // ... 更多形状类
}
 
interface AbstractFactory {
    Color getColor(String colorType);
    Shape getShape(String shapeType);
}
 
class ShapeFactory implements AbstractFactory {
    @Override
    public Color getColor(String colorType) {
        if ("Red".equalsIgnoreCase(colorType)) {
            return new Red();
        } else if ("Blue".equalsIgnoreCase(colorType)) {
            return new Blue();
        }
        return null;
    }
 
    @Override
    public Shape getShape(String shapeType) {
        if ("Circle".equalsIgnoreCase(shapeType)) {
            return new Circle(getColor("Red")); // 这里假设默认使用红色
        } else if ("Square".equalsIgnoreCase(shapeType)) {
            return new Square(getColor("Blue")); // 这里假设默认使用蓝色
        }
        return null;
    }
}
 
// 使用
public class Main {
    public static void main(String[] args) {
        AbstractFactory factory = new ShapeFactory();
        Shape circle = factory.getShape("Circle");
        circle.draw();
 
        Shape square = factory.getShape("Square");
        square.draw();
    }
}

以上就是关于设计模式的所有内容了,感谢你的阅读


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2天前
|
设计模式 安全 Java
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
【JAVA】Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
|
2天前
|
设计模式
【设计模式】单例模式的三种实现方式
【设计模式】单例模式的三种实现方式
7 1
|
2天前
|
设计模式 安全 Java
|
2天前
|
设计模式 存储 安全
Java 设计模式:深入单例模式的理解与应用
【4月更文挑战第27天】单例模式是一种常用的设计模式,在 Java 开发中扮演着重要角色。此模式的主要目的是保证一个类只有一个实例,并提供一个全局访问点。
21 0
|
2天前
|
设计模式 Java
Java 设计模式:工厂模式与抽象工厂模式的解析与应用
【4月更文挑战第27天】设计模式是软件开发中用于解决常见问题的典型解决方案。在 Java 中,工厂模式和抽象工厂模式是创建型模式中非常核心的模式,它们主要用于对象的创建,有助于增加程序的灵活性和扩展性。本博客将详细介绍这两种模式的概念、区别以及如何在实际项目中应用这些模式。
17 1
|
2天前
|
设计模式 安全 Java
[设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式
[设计模式Java实现附plantuml源码~创建型] 确保对象的唯一性~单例模式
|
2天前
|
设计模式 API
【设计模式】适配器和桥接器模式有什么区别
【设计模式】适配器和桥接器模式有什么区别
8 1
|
2天前
|
设计模式
【设计模式】张一鸣笔记:责任链接模式怎么用?
【设计模式】张一鸣笔记:责任链接模式怎么用?
11 1
|
2天前
|
设计模式 uml
【设计模式】建造者模式就是游戏模式吗?
【设计模式】建造者模式就是游戏模式吗?
11 0
|
2天前
|
设计模式 Java uml
【设计模式】什么是工厂方法模式?
【设计模式】什么是工厂方法模式?
7 1