【Android】创建型设计模式—单例模式、工厂模式、建造者模式

单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。

单例模式类图:
<<<<>> Singleton instance : Singleton +Singleton() +getInstance() : Singleton +doSomething()

单例模式有多种实现方式,下面我们详细介绍几种常见的实现方式:

1. 懒汉式(Lazy Initialization)

懒汉式单例模式(Lazy Singleton)是一种延迟实例化的方式,即在首次使用该类时才会创建实例。

代码示例(线程不安全):

java 复制代码
public class Singleton {
    private static Singleton instance;

    // 私有构造函数,避免外部实例化
    private Singleton() {}

    // 获取实例的方法
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

优点:

  • 延迟实例化,直到第一次使用时才创建实例。
  • 节省内存,如果没有使用这个单例,实例不会被创建。

缺点:

  • 在多线程环境下,可能会出现多个线程同时进入 if (instance == null) 语句块,导致创建多个实例。需要考虑线程安全问题。

2. 线程安全的懒汉式(双重检查锁定,Double-Checked Locking)

为了避免线程不安全问题,懒汉式可以通过双重检查锁定来保证线程安全。

代码示例:

java 复制代码
public class Singleton {
    private static volatile Singleton instance;

    // 私有构造函数,避免外部实例化
    private Singleton() {}

    // 获取实例的方法
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

关键点:

  • 双重检查 :第一次检查 instance == null 用来减少不必要的同步开销;第二次检查是在同步块内,保证在实例创建时只有一个线程可以创建。
  • volatile 关键字 :确保多线程环境下对 instance 的访问是可见的,并且防止由于 JIT 编译器优化等原因造成的问题。

优点:

  • 保证线程安全,并且提高了性能。只有第一次实例化时需要同步,后续调用不再需要加锁。

缺点:

  • 代码比较复杂,理解起来需要更多的思考。

3. 饿汉式(Eager Initialization)

饿汉式单例模式在类加载时就创建实例,这种方式不需要考虑线程安全问题,因为实例在类加载时就已经被创建。

代码示例:

java 复制代码
public class Singleton {
    // 静态初始化时就创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,避免外部实例化
    private Singleton() {}

    // 获取实例的方法
    public static Singleton getInstance() {
        return instance;
    }
}

优点:

  • 简单,容易理解。
  • 线程安全:类加载时已经完成初始化,且 instance 是静态常量,JVM 保证线程安全。

缺点:

  • 浪费内存:即使实例可能永远不被使用,类加载时实例就会创建。
  • 不支持延迟初始化:如果类加载时并不需要这个单例对象,就会造成不必要的内存开销。

4. 静态内部类式(Bill Pugh Singleton)

静态内部类式单例模式是一种懒加载的单例实现方式,它结合了懒汉式和饿汉式的优点。使用静态内部类时,实例化的过程是延迟的,但又能够避免线程安全问题。

代码示例:

java 复制代码
public class Singleton {
    // 静态内部类,它在第一次使用时被加载
    private static class SingletonHelper {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 私有构造函数,避免外部实例化
    private Singleton() {}

    // 获取实例的方法
    public static Singleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

关键点:

  • 静态内部类SingletonHelper 是一个静态内部类,它只有在 getInstance() 被调用时才会被加载,因此实现了懒加载。
  • 线程安全SingletonHelper.INSTANCE 只会在类加载时初始化一次,而且类加载是线程安全的,因此不需要显式的同步控制。

优点:

  • 线程安全。
  • 延迟加载:实例会在第一次使用时才会被创建。
  • 相对于双重检查锁定,代码更加简洁。
  • 不会造成内存浪费,因为静态内部类只有在需要时才会被加载。

缺点:

  • 无明显缺点,适用于绝大多数场景。

5. 枚举式(Enum Singleton)

枚举式单例模式是最简单、最安全的实现方式。Java 的枚举类型天然就是单例的,JVM 保证枚举实例的创建是线程安全的,而且枚举不允许被反射破坏单例。

代码示例:

java 复制代码
public enum Singleton {
    INSTANCE;

    public void doSomething() {
        System.out.println("Singleton with Enum");
    }
}

优点:

  • 简洁 :单例实例是由 INSTANCE 常量代表的,代码非常简洁。
  • 线程安全:Java 枚举类型在类加载时保证了线程安全。
  • 防止反射攻击:枚举类型无法通过反射进行实例化,因此避免了反射破解单例的风险。
  • 防止序列化破坏:枚举本身能够防止序列化导致的实例重复创建。

缺点:

  • 不常见的实现方式,对于不了解枚举的开发者可能会产生困惑。

对比

特性 懒汉式(Lazy Initialization) 线程安全的懒汉式(双重检查锁定) 饿汉式(Eager Initialization) 静态内部类式(Bill Pugh Singleton) 枚举式(Enum Singleton)
实例化时机 延迟实例化,首次调用时才创建 延迟实例化,首次调用时才创建,但保证线程安全 类加载时就创建实例 延迟实例化,静态内部类加载时创建 类加载时创建(JVM保证线程安全和单例性)
线程安全 否,可能出现并发问题(需要手动同步) 是,使用双重检查锁定(synchronized)保证线程安全 是,由于类加载时创建,因此天然线程安全 是,JVM保证静态内部类加载时线程安全 是,JVM保证枚举类型线程安全和单例性
性能 较低(每次访问都需要判断 null 较高(只有第一次需要同步) 较高(不需要同步) 较高(避免了每次判断 null 最高,JVM直接管理,几乎无性能损耗
实现复杂度 简单,易于理解 稍复杂,需要额外的同步机制 简单,直接实现 稍复杂,涉及到静态内部类 非常简单,直接使用枚举实现
内存消耗 可能浪费内存(在未使用时实例仍然存在) 不浪费内存,只在需要时实例化 可能浪费内存(实例化时就创建,不管是否使用) 不浪费内存,实例化仅在第一次使用时创建 不浪费内存,枚举实例只在类加载时创建
防止反射攻击 否,反射可以创建多个实例 否,反射可以创建多个实例 否,反射可以创建多个实例 是,反射无法破坏单例 是,枚举类的反射破坏会抛出异常
防止序列化破坏 否,需要手动处理 否,需要手动处理 否,需要手动处理 是,JVM管理序列化,确保不会破坏单例 是,JVM保证序列化时保持唯一性
推荐场景 简单场景,单线程应用 线程安全需求较高,但性能要求不极端的场景 单线程应用或对性能要求较高的场景 需要延迟加载并且希望避免同步开销的场景 高度推荐,适用于大多数单例场景
常见问题 可能出现并发问题,需要同步 代码较复杂,性能相对稍差 不支持延迟加载,类加载时会立即初始化 稍复杂,理解需要一定的知识 极为简洁,但不适用于需要多个实例化参数的情况

总结

  • 懒汉式适用于需要延迟实例化的场景,但需要注意线程安全问题。
  • 双重检查锁定懒汉式是懒汉式的改进,能够保证线程安全并提高性能。
  • 饿汉式不需要担心线程安全问题,但存在内存浪费的风险。
  • 静态内部类式是较为推荐的懒加载方式,线程安全且高效。
  • 枚举式是最简洁、最安全的实现方式,JVM保证线程安全和防止反射攻击,是最推荐的实现方式。

工厂模式

简单工厂模式

简单工厂模式(也称为静态工厂方法模式)是创建型设计模式之一,它提供了一个类来负责创建实例化对象的工作,客户端只需要传入相关的参数,而不需要关心对象的创建过程。
Creates Extends IProduct +method() Product +method() Factory +createProduct()

  • Factory:工厂类,这是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。
  • IProduct:抽象产品类,这是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
  • Product:具体产品类,这是简单工厂模式的创建目标。

简单实现

这里我们用生产计算机来举例,假设有一个计算机的代工生产商,它目前已经可以代工生产联想计算机了。随着业务的拓展,这个代工生产商还要生产惠普和华硕的计算机。这样我们就需要用一个单独的类来专门生产计算机,这就用到了简单工厂模式。下面我们来实现简单工厂模式。

(1)抽象产品类

java 复制代码
public abstract class Computer {
    public abstract void start();
}

(2)具体产品类

我们创建多个品牌的计算机,都继承自己父类Computer,并且实现父类的start()方法:

java 复制代码
public class LenovoComputer extends Computer{
    @Override
    public void start() {
        System.out.println("联想计算机");
    }
}
public class HpComputer extends Computer{
    @Override
    public void start() {
        System.out.println("惠普计算机");
    }
}
public class AsusComputer extends Computer{
    @Override
    public void start() {
        System.out.println("华硕计算机");
    }
}

(3)工厂类

下来创建一个工厂类,提供一个静态方法createComputer去生产计算机:

java 复制代码
public class ComputerFactory {
    public static Computer createComputer(String type){
        Computer mComputer = null;
        switch (type) {
            case "lenovo":
                mComputer = new LenovoComputer();
                break;
            case "hp":
                mComputer = new HpComputer();
                break;
            case "asus":
                mComputer = new HpComputer();
                break;
        }
        return mComputer;
    }
}

(4)客户端调用工厂类

java 复制代码
public class CreatComputer {
    public static void main(String[] args) {
        ComputerFactory.createComputer("hp").start();
    }
}

优缺点

优点:

  1. 客户端与产品解耦:客户端代码不需要知道具体的产品类,只需要知道如何调用工厂来获取产品。
  2. 易于扩展 :如果要添加新的产品类型,只需要在工厂类中增加新的 if-else 分支或修改创建产品的逻辑即可,客户端无需修改。
  3. 集中管理产品创建:产品的创建逻辑都集中在工厂类中,便于管理和修改。

缺点:

  1. 违反开闭原则 :每当需要增加新的产品类型时,都需要修改工厂类的 createProduct() 方法,违反了"对扩展开放,对修改封闭"的设计原则。
  2. 工厂类职责过重:如果产品种类很多,工厂类的逻辑可能变得非常庞大,影响维护性。
  3. 不利于产品的复杂多样化 :如果产品种类非常多且产品之间有较复杂的差异,简单的 if-else 分支可能使得工厂类显得臃肿和不可维护。

使用场景

简单工厂模式适用于以下场景:

  • 产品种类较少且变化不大的情况:如果需要创建的产品类型固定且不经常变动,使用简单工厂模式非常适合。
  • 客户端需要创建多个类型的对象:当客户端需要通过不同的输入来创建不同类型的对象时,工厂模式可以有效地隐藏对象的创建逻辑。
  • 控制产品创建逻辑:当需要集中管理和控制产品的创建过程时,简单工厂模式提供了很好的解决方案。

工厂方法模式

工厂方法模式(Factory Method Pattern) 是一种创建型设计模式,用于定义一个创建对象的接口,让子类决定实例化哪一个类。它通过将对象的创建委托给子类,从而实现了代码的解耦,使得代码更加灵活和易于扩展。

工厂方法模式有如下角色:

  • 产品接口(Product):定义产品的公共接口。
  • 具体产品(ConcreteProduct):实现产品接口的具体类。
  • 工厂方法(Creator):声明创建产品对象的工厂方法。
  • 具体工厂(ConcreteCreator):实现工厂方法,创建并返回具体的产品对象。

extends extends creates Product +method() +createProduct() ConcreteProduct +method() +createProduct() Factory +createProduct() ConcreteFactory +createProduct()

简单实现

(1)创建抽象工厂

java 复制代码
public abstract class ComputerFactory {
    public abstract <T extends Computer> T createComputer(Class<T> clz);
}

(2)创建具体工厂

广达代工厂是一个具体的工厂,其继承自抽象工厂,通过反射来生产不同厂家的计算机:

java 复制代码
public class GDComputerFactor extends ComputerFactory{
    @Override
    public <T extends Computer> T createComputer(Class<T> clz) {
        Computer computer = null;
        String classname = clz.getName();
        try {
            computer = (Computer) Class.forName(classname).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) computer;
    }
}

(3)客户端调用

客户端创建了GDComputerFactor,并分别生产了联想计算机、惠普计算机和华硕计算机:

java 复制代码
public class Client {
    public static void main(String[] args) {
        ComputerFactory computerFactory = new GDComputerFactor();
        LenovoComputer mLenovoComputer = computerFactory.createComputer(LenovoComputer.class);
        mLenovoComputer.start();
        HpComputer mHpComputer = computerFactory.createComputer(HpComputer.class);
        mHpComputer.start();
        AsusComputer mAsusComputer = computerFactory.createComputer(AsusComputer.class);
        mAsusComputer.start();
    }
}

比较

特点 简单工厂模式 工厂方法模式
创建方式 使用静态方法通过条件判断创建不同的产品对象。 通过定义一个抽象工厂接口,具体工厂类实现该接口来创建产品。
工厂类数量 只有一个工厂类,负责所有产品的创建。 有多个工厂类,每个具体工厂类负责创建一种产品。
可扩展性 不符合开闭原则,增加新产品需要修改工厂类代码。 符合开闭原则,新增产品时只需添加新的具体工厂类,而无需修改现有代码。
耦合性 客户端代码直接依赖于工厂方法,需要知道产品的种类。 客户端代码依赖于抽象工厂接口,具体的工厂类是透明的。
复杂度 简单,适用于产品种类较少的情况。 相对复杂,适用于产品种类较多或者希望扩展的情况。
优点 实现简单,适合产品种类较少的情况。 符合开闭原则,容易扩展,灵活性更强。
缺点 不符合开闭原则,增加产品时需要修改工厂类代码。 增加了系统的复杂性,需要创建多个工厂类。

建造者模式

建造者模式(Builder Pattern) 是一种 创建型设计模式,它允许通过一步一步地构建复杂对象,而无需指定对象的具体构造过程。建造者模式关注的是对象的构建过程,将对象的构建和表示分离开来,使得同样的构建过程可以创建不同类型的对象。

例如,我们要"DIY"一台台式计算机。我们找到"DIY"商家。这时我们可以要求这台计算机的CPU、主板或者其他部件都是什么牌子的、什么配置的,这些部件可以是我们根据自己的需求来定制的。但是这些部件组装成计算机的过程是一样的,我们无须知道这些部件是怎样组装成计算机的,我们只需要提供相关部件的牌子和配置就可以了。
uses implements builds Director +construct() Builder +buildPart() ConcreteBuilder +buildPart() Product

建造者模式通常包含以下几个角色:

  • 产品(Product):即最终要创建的复杂对象。
  • 抽象建造者(Builder):提供构建对象的抽象接口,定义如何构建产品的各个部分。
  • 具体建造者(ConcreteBuilder) :实现 Builder 接口,负责具体产品的构建。
  • 指挥者(Director):负责安排建造的顺序和调用建造者的方法,指导建造者如何构建产品。

简单实现

(1)创建产品类

我要组装一台计算机,计算机被抽象为Computer类,(本例假设)它有3个部件:CPU主板和内存,并在里面提供了3个方法分别用来设置CPU、主板和内存:

java 复制代码
public class Computer {
    private String mCpu;
    private String mMainboard;
    private String mRam;

    public void setmCpu(String mCpu) {
        this.mCpu = mCpu;
    }

    public void setmMainboard(String mMainboard) {
        this.mMainboard = mMainboard;
    }

    public void setmRam(String mRam) {
        this.mRam = mRam;
    }
}

(2)创建Builder类,规范产品的组建

商家组装计算机有一套组装方法的模板,就是一个抽象的Builder 类,其里面提供了安装CPU、主板和内存的方法,以及组装成计算机的create方法,如下所示:

java 复制代码
public abstract class Builder {
    public abstract void buildCpu(String cpu);
    public abstract void buildMainboard(String mainboard);
    public abstract void buildRam(String ram);
    public abstract Computer create();
}

商家实现了抽象的Builder类,MoonComputerBuilder类用于组装计算机:

java 复制代码
public class MoonComputerBuilder extends Builder{

    private Computer mComputer = new Computer();
    
    @Override
    public void buildCpu(String cpu) {
        mComputer.setmCpu(cpu);
    }

    @Override
    public void buildMainboard(String mainboard) {
        mComputer.setmMainboard(mainboard);
    }

    @Override
    public void buildRam(String ram) {
        mComputer.setmRam(ram);
    }

    @Override
    public Computer create() {
        return mComputer;
    }
}

(3)用导演类来统一组装过程

商家的导演类用来规范组装计算机的流程:先安装主板,再安装CPU,最后安装内存并组装成计算机:

java 复制代码
public class Diretor {
    Builder mBuild = null;

    public Diretor(Builder mBuild) {
        this.mBuild = mBuild;
    }
    public Computer createComputer(String cpu, String mainboard, String ram) {
        this.mBuild.buildMainboard(mainboard);
        this.mBuild.buildCpu(cpu);
        this.mBuild.buildRam(ram);
        return this.mBuild.create();
    }
}

(4)客户端调用导演类

最后商家用导演类组装计算机。我们只需要提供自己想要的CPU、主板和内存就可以了至于商家怎样组装计算机我们无须知道。具体代码如下所示:

java 复制代码
public class CreateComputer {
    public static void main(String[] args) {
        Builder mBuilder = new MoonComputerBuilder();
        Diretor mDiretor = new Diretor(mBuilder);
        mDiretor.createComputer("i5","8G","1T");
    }
}

优缺点

优点

  • 解耦复杂对象的构建过程和表示:客户端不需要知道构建的细节,可以专注于产品的组装。
  • 代码可读性和可维护性高:将复杂对象的构建过程拆分成多个步骤,使得代码结构更加清晰。
  • 支持不同产品的变体:同样的建造过程可以用于构建不同的产品对象。

缺点

  • 产生多余的 Build 对象以及导演类

使用场景

  • 创建一个复杂对象时,其构建过程应该独立于该对象的组成部分,并且可以允许被构建成不同的表示。
  • 当一个对象的构建过程独立于其组成部分,并且可以组合成不同的方式时,可以使用建造者模式。

已经到底啦!!

相关推荐
m0_7482522327 分钟前
万字详解 MySQL MGR 高可用集群搭建
android·mysql·adb
SoulKuyan35 分钟前
Android系统默认开启adb root模式
android·adb
咖啡の猫1 小时前
原型模式详解与实践
设计模式·原型模式
臣妾写不来啊1 小时前
创建型模式5.单例模式
单例模式
0wioiw03 小时前
逆向安卓抓包
android·linux·运维
zhangjiaofa3 小时前
深入理解 Android 中的 KeyguardManager
android
-代号95273 小时前
云计算中的可用性SLA
android·java·云计算
青岚岁叶4 小时前
设计模式——泛型单例类
单例模式·设计模式·unity3d
m0_748230444 小时前
眼见不一定为实之MySQL中的不可见字符
android·数据库·mysql
_可乐无糖5 小时前
深入理解 pytest_runtest_makereport:如何在 pytest 中自定义测试报告
android·ui·ios·自动化·pytest