Day 10: Creational patterns - Singleton

目的

建立一个「唯一」物件,专责於服务只能单一连线的情境,例如跟资料库的沟通,同时确保全域内都可以呼叫该物件。

说明

Singleton 是相当好懂的模式,用在只能服务单一连线的情境下,避免多重连线产生时间差,导致执行完毕的结果不是我们要的,例如:

  1. 与资料库沟通的写入,时间差可能会让写入的顺序错乱。
  2. 权限的许可,时间差可能让权限判定失效。

相关作法是:

  1. 避免物件拥有建立的可能性,因此要封锁建构式。
  2. 物件一定有一个方法:getInstance(),负责回传 instance,让全域都可以呼叫并使用。
  3. 物件可能有其他方法。

Java 因为可以跑多的执行绪(Thread),所以在 getInstance() 除了原有的锁之外还要多加两道锁:

  1. 确认 instance 是否存在?
  2. 确认是不是有其他执行绪正在建立中?
  3. 要建立时,确认 instance 是否存在?

至於 JavaScript,不能使用常见的 class 写法,而是使用 IIFEs(Immediately Invoked Functions Expressions),让物件只会建立一次,没有其他建立的方法。

UML 图

Singleton Pattern UML Diagram

使用 Java 实作

Singleton: EmergencyTelephone

public class EmergencyTelephone {
    private static EmergencyTelephone instance;

    private EmergencyTelephone() {
    }

    private void useTelephone(String phoneNumber) {
        System.out.println("(号码:" + phoneNumber + ")拿起话筒");
        System.out.println("(号码:" + phoneNumber + ")输入号码");
        System.out.println("(号码:" + phoneNumber + ")等待接通");
        System.out.println("(号码:" + phoneNumber + ")确认电话已经打通");
    }

    private void tellDetails(String phoneNumber) {
        System.out.println("(号码:" + phoneNumber + ")详述情况");
        System.out.println("(号码:" + phoneNumber + ")告知地点");
        System.out.println("(号码:" + phoneNumber + ")记录指示");
    }

    private void finishTelephoneTalk(String phoneNumber) {
        System.out.println("(号码:" + phoneNumber + ")挂上电话");
        System.out.println("(号码:" + phoneNumber + ")思考指示");
        System.out.println("(号码:" + phoneNumber + ")行动");
    }

    public synchronized void execute(String phoneNumber) {
        useTelephone(phoneNumber);
        tellDetails(phoneNumber);
        finishTelephoneTalk(phoneNumber);
    }

    public static EmergencyTelephone getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new EmergencyTelephone();
                }
            }
        }

        return instance;

    }
}

进行测试

public class EmergencyTelephoneSample extends Thread {
    String phoneNumber;

    public EmergencyTelephoneSample(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    public void run() {
        EmergencyTelephone emergencyTelephone = EmergencyTelephone.getInstance();
        if (emergencyTelephone != null) {
            System.out.println("这通电话号码是:" + phoneNumber + ",这台电话的生产序号是:" + emergencyTelephone.hashCode());
            emergencyTelephone.execute(phoneNumber);
        }
    }

    public static void main(String[] args) {
        EmergencyTelephone emergencyTelephone1 = EmergencyTelephone.getInstance();
        EmergencyTelephone emergencyTelephone2 = EmergencyTelephone.getInstance();

        if (emergencyTelephone1.hashCode() == emergencyTelephone2.hashCode()) {
            System.out.println("两个是同一个物件");
        }

        Thread t1 = new EmergencyTelephoneSample("119");
        Thread t2 = new EmergencyTelephoneSample("110");
        t1.start();
        t2.start();
    }
}

使用 JavaScript 实作

Singleton: EmergencyTelephone

const EmergencyTelephone = (() => {
  let instance = null;

  function initialize() {
    let hashCode = Math.floor(Math.random() * 10000000);

    function useTelephone(phoneNumber) {
      console.log("(号码:" + phoneNumber + ")拿起话筒");
      console.log("(号码:" + phoneNumber + ")输入号码");
      console.log("(号码:" + phoneNumber + ")等待接通");
      console.log("(号码:" + phoneNumber + ")确认电话已经打通");
    }

    function tellDetails(phoneNumber) {
      console.log("(号码:" + phoneNumber + ")详述情况");
      console.log("(号码:" + phoneNumber + ")告知地点");
      console.log("(号码:" + phoneNumber + ")记录指示");
    }

    function finishTelephoneTalk(phoneNumber) {
      console.log("(号码:" + phoneNumber + ")挂上电话");
      console.log("(号码:" + phoneNumber + ")思考指示");
      console.log("(号码:" + phoneNumber + ")行动");
    }

    return {
      execute: function (phoneNumber) {
        useTelephone(phoneNumber);
        tellDetails(phoneNumber);
        finishTelephoneTalk(phoneNumber);
      },
      getHashCode: function () {
        return hashCode;
      }
    };
  }

  return {
    getInstance: function () {
      if (instance === null) {
        instance = initialize();
      }

      return instance;
    }
  }
})();

进行测试

const emergencyTelephoneSample = () => {
  const emergencyTelephone1 = EmergencyTelephone.getInstance();
  const emergencyTelephone2 = EmergencyTelephone.getInstance();

  if (emergencyTelephone1.getHashCode() === emergencyTelephone2.getHashCode()) {
    console.log("两个是同一个物件");
  } else {
    console.log("出问题,两个不是同一个物件");
  }

  console.log("进行第一通电话拨打");
  const phoneNumber1 = "119";
  console.log("这通电话号码是:" + phoneNumber1 + ",这台电话的生产序号是:" + emergencyTelephone1.getHashCode());
  emergencyTelephone1.execute(phoneNumber1);

  console.log("进行第二通电话拨打");
  const phoneNumber2 = "110";
  console.log("这通电话号码是:" + phoneNumber2 + ",这台电话的生产序号是:" + emergencyTelephone2.getHashCode());
  emergencyTelephone2.execute(phoneNumber2);
}

emergencyTelephoneSample();

总结

Singleton 是非常容易理解的模式,也是十分常见的模式。

要注意的反倒是语言特性,不同语言执行上有细节,例如执行绪的多寡,将影响 Singleton 的效果,这点唯有加强自身对该语言的熟练才能避免。

这是最後一篇 Creational patterns,明天将进入下个类别:Structural patterns 的第一个模式:Adapter 模式。


<<:  Day#10 Struct

>>:  [Tableau Public] day 25:台湾姓氏分布分析-3

NIST SP 800-53 R5的摘要

-安全和隐私控制系列(来源:NIST SP 800-53 R5) .安全和隐私控制有效性解决了正确...

Day 20 : 模型优化 - 训练後量化 Post Training Quantization

当我们训练模型需要部署在硬体较为受限的智慧型装置、IOT设备,模型运算在吃紧的硬体资源中显得笨重,...

【Day35】[演算法]-常见的演算法策略Algorithmic Patterns

分治法(Divide and conquer) 又称分而治之法,是最常被使用的策略方式,原理是将一个...

Day 01:新手视角

年中才刚攻破 JavaScript 30 挑战一天一题 JavaScript,过不久因缘际会接了一个...

Golang - html template + wkhtmltopdf生成PDF

以前很菜的时候收过这个需求 真心很讨厌PDF,因为要自己算座标和设定一堆东西 後来这个需求就被弃置了...