设计模式(Design Pattern)是前辈们对代码开发经验的总结,是解决特定问题的一系列套路。它不是语法规定,而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案
1.单例模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
1.1单例模式的定义和特点
单例(Singleton)模式的定义:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点。
1.2单例模式的优点和缺点
单例模式的优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用。
- 单例模式设置全局访问点,可以优化和共享资源的访问。
单例模式的缺点:
- 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
1.3单例模式的应用场景
对于Java来说,单例模式可以保证在一个 JVM 中只存在单一实例。单例模式的应用场景主要有以下几个方面。
- 需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
- 某类只要求生成一个对象的时候,如一个班中的班长、每个人的身份证号等。
- 某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
- 某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、网络连接池等。
- 频繁访问数据库或文件的对象。
- 对于一些控制硬件级别的操作,或者从系统上来讲应当是单一控制逻辑的操作,如果有多个实例,则系统会完全乱套。
- 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。
1.4单例模式的结构和实现
常,普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。
1.4.1单例模式的结构
单例模式的主要角色如下。
- 单例类:包含一个实例且能自行创建这个实例的类。
- 访问类:使用单例的类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gNA5TwKu-1651211028051)(D:\cache\images\3-1Q1131K441K2.gif)]
1.4.2单例模式的实现
第 1 种:懒汉式单例
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。代码如下:
class Singleton{
private String str;
private static volatile Singleton singleton;//第二层锁,volatile关键字禁止指令重排
private Singleton(){
str="hello";
}
public String getStr() {
return str;
}
public static Singleton getInstance(){
if(singleton==null){//第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入
synchronized (Singleton.class){//第一层锁,保证只有一个线程进入
//双重检查,防止多个线程同时进入第一层检查(因单例模式只允许存在一个对象,故在创建对象之前无引用指向对象,所有线程均可进入第一层检查)
//当某一线程获得锁创建一个Singleton对象时,即已有引用指向对象,singleton不为空,从而保证只会创建一个对象
//假设没有第二层检查,那么第一个线程创建完对象释放锁后,后面进入对象也会创建对象,会产生多个对象
if(singleton==null){//第二层检查
//volatile关键字作用为禁止指令重排,保证返回Singleton对象一定在创建对象后
singleton=new Singleton();
//singleton=new Singleton语句为非原子性,实际上会执行以下内容:
//(1)在堆上开辟空间;(2)属性初始化;(3)引用指向对象
//假设以上三个内容为三条单独指令,因指令重排可能会导致执行顺序为1->3->2(正常为1->2->3),当单例模式中存在普通变量需要在构造方法中进行初始化操作时,单线程情况下,顺序重排没有影响;但在多线程情况下,假如线程1执行singleton=new Singleton()语句时先1再3,由于系统调度线程2的原因没来得及执行步骤2,但此时已有引用指向对象也就是singleton!=null,故线程2在第一次检查时不满足条件直接返回singleton,此时singleton为null(即str值为null)
//volatile关键字可保证singleton=new Singleton()语句执行顺序为123,因其为非原子性依旧可能存在系统调度问题(即执行步骤时被打断),但能确保的是只要singleton!=0,就表明一定执行了属性初始化操作;而若在步骤3之前被打断,此时singleton依旧为null,其他线程可进入第一层检查向下执行创建对象
}
}
}
return singleton;
}
}
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
第 2 种:饿汉式单例
该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
2.简单工厂模式
在日常开发中,凡是需要生成复杂对象的地方,都可以尝试考虑使用工厂模式来代替。
工厂模式的定义:定义一个创建产品对象的工厂接口,将产品对象的实际创建工作推迟到具体子工厂类当中。这满足创建型模式中所要求的“创建与使用相分离”的特点。
按实际业务场景划分,工厂模式有 3 种不同的实现方式,分别是简单工厂模式、工厂方法模式和抽象工厂模式。
我们把被创建的对象称为“产品”,把创建产品的对象称为“工厂”。如果要创建的产品不多,只要一个工厂类就可以完成,这种模式叫“简单工厂模式”。
2.1工厂模式的优点
解耦:将对象的创建和使用进行分离
可复用:对于创建过程比较复杂且在很多地方都使用到的对象,通过工厂模式可以提高对象创建的代码的复用性。
降低成本:由于复杂对象通过工厂进行统一管理,所以只需要修改工厂内部的对象创建过程即可维护对象,从而达到降低成本的目的
2.2简单工厂模式
简单工厂模式每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度,违背了“开闭原则”。
优点:
- 工厂类包含必要的逻辑判断,可以决定在什么时候创建哪一个产品的实例。客户端可以免除直接创建产品对象的职责,很方便的创建出相应的产品。工厂和产品的职责区分明确。
- 客户端无需知道所创建具体产品的类名,只需知道参数即可。
- 也可以引入配置文件,在不修改客户端代码的情况下更换和添加新的具体产品类。
缺点:
- 简单工厂模式的工厂类单一,负责所有产品的创建,职责过重,一旦异常,整个系统将受影响。且工厂类代码会非常臃肿,违背高聚合原则。
- 使用简单工厂模式会增加系统中类的个数(引入新的工厂类),增加系统的复杂度和理解难度
- 系统扩展困难,一旦增加新产品不得不修改工厂逻辑,在产品类型较多时,可能造成逻辑过于复杂
- 简单工厂模式使用了 static 工厂方法,造成工厂角色无法形成基于继承的等级结构。
应用场景
对于产品种类相对较少的情况,考虑使用简单工厂模式。使用简单工厂模式的客户端只需要传入工厂类的参数,不需要关心如何创建对象的逻辑,可以很方便地创建所需产品。
代码实现
假设我们有产品电脑,通过工厂模式来创建具体的电脑对象。
首先先定义具体类(即产品),包括
/**
* 电脑:开机,关机
*/
public interface Computer {
void powerOn();
void powerOff();
}
华为电脑:实现了Computer接口
public class HuaweiComputer implements Computer {
public void powerOn() {
System.err.println("Huawei Computer power on");
}
public void powerOff() {
System.err.println("Huawei Computer power off");
}
}
小米电脑:实现了Computer接口
public class XiaomiComputer implements Computer {
public void powerOn() {
System.err.println("Xiaomi Computer power on");
}
public void powerOff() {
System.err.println("Xiaomi Computer power off");
}
}
工厂类,根据类型创建相应的对象:
public class ComputerFactory {
public Computer create(String type){
if (type == null || type.length() == 0){
return null;
}
if ("Huawei".equalsIgnoreCase(type)) {
return new HuaweiComputer();
} else if ("Xiaomi".equalsIgnoreCase(type)) {
return new XiaomiComputer();
}
return null;
}
}
Client客户端:
public class Client {
public static void main(String[] args) {
Computer huawei = new ComputerFactory().create("Huawei");
huawei.powerOn();
huawei.powerOff();
Computer xiaomi = new ComputerFactory().create("Xiaomi");
xiaomi.powerOn();
xiaomi.powerOff();
}
}
// 结果
Huawei Computer power on
Huawei Computer power off
Xiaomi Computer power on
Xiaomi Computer power off
2.3工厂方法模式
提供一个用于创建对象的接口(工厂接口),让其实现类(工厂实现类)决定实例化哪一个类(产品类),并且由该实现类创建对应类的实例。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7DSBogQl-1651211028055)(D:\cache\images\webp.webp)]
适用场景
- 消费者不关心它所要创建对象的类(产品类)的时候。
- 消费者知道它所要创建对象的类(产品类),但不关心如何创建的时候。
模式要素
- 提供一个产品类的接口。产品类均要实现这个接口(也可以是abstract类,即抽象产品)。
- 提供一个工厂类的接口。工厂类均要实现这个接口(即抽象工厂)。
- 由工厂实现类创建产品类的实例。工厂实现类应有一个方法,用来实例化产品类。
代码实现
public abstract class ComputerFactory {
protected abstract Computer create();
}
public class HuaweiComputerFactory extends ComputerFactory {
protected Computer create() {
return new HuaweiComputer();
}
}
public class XiaomiComputerFactory extends ComputerFactory {
protected Computer create() {
return new XiaomiComputer();
}
}
public class Client {
public static void main(String[] args) {
Computer computer = new HuaweiComputerFactory().create();
computer.powerOn();
computer.powerOff();
Computer computer1 = new XiaomiComputerFactory().create();
computer1.powerOn();
computer1.powerOff();
}
}
// 结果
Huawei Computer power on2.
Huawei Computer power off
Xiaomi Computer power on
Xiaomi Computer power off
2.4抽象工厂模式
抽象工厂模式(Abstract Factory Pattern),是围绕一个超级工厂创建其他工厂。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
优点:
当一个产品族中的多个对象,被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:
产品族扩展非常困难,要增加一个系列的某一产品,既要在抽象的 Creator 里加代码,又要在具体的里面加代码。
代码实现
/**
* 电脑:开机,关机
*/
public interface Computer {
void powerOn();
void powerOff();
}
public abstract class DesktopComputer implements Computer {
public abstract void powerOn();
public abstract void powerOff();
}
public class HuaweiDesktopComputer extends DesktopComputer {
@Override
public void powerOn() {
System.err.println("HuaweiDesktopComputer power on");
}
@Override
public void powerOff() {
System.err.println("HuaweiDesktopComputer power off");
}
}
public class XiaomiDesktopComputer extends DesktopComputer {
@Override
public void powerOn() {
System.err.println("XiaomiDesktopComputer power on");
}
@Override
public void powerOff() {
System.err.println("XiaomiDesktopComputer power off");
}
}
public abstract class NoteBookComputer implements Computer {
public abstract void powerOn();
public abstract void powerOff();
}
public class HuaweiNoteBookComputer extends NoteBookComputer {
@Override
public void powerOn() {
System.err.println("HuaweiNoteBookComputer power on");
}
@Override
public void powerOff() {
System.err.println("HuaweiNoteBookComputer power off");
}
}
public class XiaomiNoteBookComputer extends NoteBookComputer {
@Override
public void powerOn() {
System.err.println("XiaomiNoteBookComputer power on");
}
@Override
public void powerOff() {
System.err.println("XiaomiNoteBookComputer power off");
}
}
抽象工厂类,定义了生产台式电脑和笔记本电脑的抽象方法,由不同品牌的具体工厂类实现:
public abstract class AbstractComputerFactory {
public abstract Computer createDesktopComputer();
public abstract Computer createNoteBookComputer();
}
public class HuaweiComputerFactory extends AbstractComputerFactory {
public Computer createDesktopComputer() {
return new HuaweiDesktopComputer();
}
public Computer createNoteBookComputer() {
return new HuaweiNoteBookComputer();
}
}
public class XiaomiComputerFactory extends AbstractComputerFactory {
public Computer createDesktopComputer() {
return new XiaomiDesktopComputer();
}
public Computer createNoteBookComputer() {
return new XiaomiNoteBookComputer();
}
}
public class Client {
public static void main(String[] args) {
HuaweiComputerFactory huaweiComputerFactory = new HuaweiComputerFactory();
Computer desktopComputer = huaweiComputerFactory.createDesktopComputer();
desktopComputer.powerOn();
desktopComputer.powerOff();
Computer noteBookComputer = huaweiComputerFactory.createNoteBookComputer();
noteBookComputer.powerOn();
noteBookComputer.powerOff();
XiaomiComputerFactory XiaomiComputerFactory = new XiaomiComputerFactory();
Computer desktopComputer1 = XiaomiComputerFactory.createDesktopComputer();
desktopComputer1.powerOn();
desktopComputer1.powerOff();
Computer noteBookComputer1 = XiaomiComputerFactory.createNoteBookComputer();
noteBookComputer1.powerOn();
noteBookComputer1.powerOff();
}
}
// 结果
HuaweiDesktopComputer power on
HuaweiDesktopComputer power off
HuaweiNoteBookComputer power on
HuaweiNoteBookComputer power off
XiaomiDesktopComputer power on
XiaomiDesktopComputer power off
XiaomiNoteBookComputer power on
XiaomiNoteBookComputer power off
工厂方法模式:① 一个抽象产品类,可以派生出多个具体产品类。
② 一个抽象工厂类,可以派生出多个具体工厂类。
③ 每个具体工厂类只能创建一个具体产品类的实例。
抽象工厂模式:① 多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。
② 一个抽象工厂类,可以派生出多个具体工厂类。
③ 每个具体工厂类可以创建多个具体产品类的实例。
区别:① 工厂方法模式只有一个抽象产品类,而抽象工厂模式有多个。
② 工厂方法模式的具体工厂类只能创建一个具体产品类的实例,而抽象工厂模式可以创建多个。