前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >设计模式 - 单例(件)模式

设计模式 - 单例(件)模式

作者头像
子乾建建-Jeff
发布2020-06-29 15:20:36
5110
发布2020-06-29 15:20:36
举报
文章被收录于专栏:iBroProiBroPro

预计阅读时间:10 - 13 min

「南媒婆北媒婆,南北媒婆送老婆」

建建:很是羡慕宫廷剧中的后宫佳丽三千啊,可惜这个社会不会再出现这样的现象了。

子乾:可拉倒吧,你这智商是不是在治伤的过程中给致伤了。要是这样,她们宁愿当易烊千玺第 1001个 老婆,都不愿意做你老婆。

(媒婆来了)

南媒婆:建建快出来,今天我高兴,来给你说个老婆。(领来一花姑娘)

建建:哦,?,喔,谢谢阿婆。我们处下试试。

(第二天)

北媒婆:建建出来了,今天姨高兴,给你领来一老婆。(又一花姑娘)

建建:诶诶,情形不对啊。昨天一个,今天一个,这碰到一块不干仗嘛?子乾快出来,给我顶一下。

子乾:难道你要成了下一个易烊千玺,老婆天天送上门?.....

只能有一个老婆,只能有一个对象。媒婆一会创建一个对象,一会领来一个对象,违反了常理。当领来对象时,应该先看下家里是否有对象了,没有,可以领;有,则不能领!这是规矩。

唯一,仅一个对象。映射到代码里面就是该类在运行过程中只能有一个对象,别让人瞎创建,容易出事。解决这个问题,单例模式就来了。

来了来了,他来了。他来阻止你造对象来了。

Singleton Pattern:Ensure a class has only one instance, and provide a global point of access to it.

没读懂,看下面:

单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

要点:

▎某个类,可以没有实例,有的话,只能有一个。

▎必须自己创建这个实例。(不允许在其他类中创建实例)

▎必须主动向整个系统提供这个实例。

它是对象创建型模式

单例模式类图:

角色:

Singleton 单例

代码解读:

代码语言:javascript
复制
public class Singleton{
  private static Singleton instance = null;

  private Singleton(){}

  public static Singleton getInstance(){
    if (instance == null)
      instance = new Singleton();
    return instance;
  }
}

·私有静态单例对象

·私有构造方法

·公有静态方法,返回值为单例类型

a. 只可以有一个实例,将 instance 对象声明为 private、static 。并且构造方法设置为 private,其他类中无法调用,就无法生成该类对象。

其他类中无法调用该类方法,怎么来的该类对象?没有该类对象,怎么调用该类中的方法?这就是令人头疼的“鸡生蛋,蛋生鸡”的问题。

好在,static 静态方法,可以由类名直接调用!

b. 在 getInstance 方法中,进行判断实例是否为 null,等于 null 时才创建。不为 null,直接返回现有的 instance 对象。

单例应用:

来看一个实际的应用场景。

百度智能平台提供的多种智能 API 接口,在调用过程中,都会涉及到 access_token。它的有效期为 30 天

因此一旦有了该 token,后面不需要每次使用 API 时都产生一个。

但官方给的 sample 中,如果直接使用,则是每次都产生一个 token,实属浪费。人家也给提示了,建议单例使用

比如从图片中提取文字,AipOcr :

官网的 Ocr 样例代码:

代码语言:javascript
复制
public class Sample {
    //设置APPID/AK/SK
    public static final String APP_ID = "你的 App ID";
    public static final String API_KEY = "你的 Api Key";
    public static final String SECRET_KEY = "你的 Secret Key";

    public static void main(String[] args) {
        // 初始化一个AipOcr
        AipOcr client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);

        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);

        // 可选:设置代理服务器地址, http和socket二选一,或者均不设置
        client.setHttpProxy("proxy_host", proxy_port);  // 设置http代理
        client.setSocketProxy("proxy_host", proxy_port);  // 设置socket代理

        // 可选:设置log4j日志输出格式,若不设置,则使用默认配置
        // 也可以直接通过jvm启动参数设置此环境变量
        System.setProperty("aip.log4j.conf", "path/to/your/log4j.properties");

        // 调用接口
        String path = "test.jpg";
        JSONObject res = client.basicGeneral(path, new HashMap<String, String>());
        System.out.println(res.toString(2));
        
    }
}

AipOcr 在 package com.baidu.aip.ocr 包中,是个很成熟的类。

我们考虑通过继承来接盘 AipOcr 所具备的功能,并且不影响其他地方的使用。

类图:

AipOcrSubSingleton 是核心,就是我们想要的单例类。它继承了 AipOcr,该类有私有的静态的实例化对象,私有的构造方法,公有的获取实例化对象的方法。

客户端中,对此进行调用,获取实例化对象并执行其他功能。

代码:

AipOcrSubSingleton 类,核心三个要点一个不能少。

AipOcr 记得先在pom中引入依赖或者下载jar包引入进项目。

代码语言:javascript
复制
package singleton;

import com.baidu.aip.ocr.AipOcr;

public class AipOcrSubSingleton extends AipOcr {

  private static AipOcrSubSingleton clientSingleton;

  private AipOcrSubSingleton(String appId, String apiKey, String secretKey) {
    super(appId, apiKey, secretKey);
  }

  public static AipOcrSubSingleton getClientSingleton(String appId, String apiKey, String secretKey){
    if(clientSingleton == null){
      clientSingleton = new AipOcrSubSingleton(appId, apiKey, secretKey);
    }
    return clientSingleton;
  }
}

client 类(根据官方sample 类修改):

在这里,由于是一个普通的 Java 项目,没办法实现多人多机器访问。因此,我写了一个 “第二次”调用执行的代码。

以此来验证,如果第一次识别图像 1 的access_token 和识别图像 2 的access_token 一样,证明第二次执行时,没有创建新单例对象。

代码语言:javascript
复制
package singleton;

import java.util.HashMap;
import org.json.JSONObject;

public class Client {

    //设置APPID/AK/SK
    public static final String APP_ID = "17370504";
    public static final String API_KEY = "LpZTq7KZDnDtb93Ydor1aMC7";
    public static final String SECRET_KEY = "17umB4mByrlZ3S2SXupwmavtL8pW7Xtq";

    public static void main(String[] args) {
        // 单例调用
        AipOcrSubSingleton client = AipOcrSubSingleton.getClientSingleton(APP_ID, API_KEY, SECRET_KEY);
        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);

        // 调用接口
        String path = "/Users/zfj/Desktop/1.png";
        JSONObject res = client.basicGeneral(path, new HashMap<String, String>());
        System.out.println(res.toString(2));

        //第二次
        client = AipOcrSubSingleton.getClientSingleton(APP_ID, API_KEY, SECRET_KEY);
        String path2 = "/Users/zfj/Desktop/2.png";
        JSONObject res2 = client.basicGeneral(path2, new HashMap<String, String>());
        System.out.println(res2.toString(2));

    }
}

通过 debug 跟踪,两次单例对象,access_token 一样

代码语言:javascript
复制
singleton.AipOcrSubSingleton@2ac1fdc4
access_token:(被修改,不可直接用)
24.43b64052096zfjnb723874c92b41e5a18b.zfjnb.1588237981.282335-17370504

识别结果:

项目结构:

一个包下两个类,完事。

两张原图:

程序中的 1.png

程序中的 2.png

单例模式优点:

· 提供了对唯一实例的受控访问。严格控制如何、何时访问它;

· 节约系统资源。

单例模式缺点:

· 没有抽象层,不利于扩展;

· 单例类职责过重,违背了“单一职责原则”;

· 垃圾收集器可能吃掉单例对象(Java 1.2 之前可能会,Java 1.2 之后无须担心)

即我们现在几乎不用担心。

适用环境:

· 系统只需要一个实例对象,比如电脑的注册表设置、线程池、缓存的对象,日志对象,充当打印机驱动程序的对象等等。

· 客户调用累的单个实例只允许使用一个公共访问点。

单例模式拓展:

单例模式中很大的一个坑,就是多线程问题。

当该实例对象未生成,有两个或多个线程同时访问时,可能造成单例对象不同步问题。一旦有了实例对象,就不存在该问题了。如何解决?

别怕,只需要把 getInstance() 编程同步方法即可:synchronized

代码语言:javascript
复制
public static synchronized Singleton getInstance(){}

(它的矛盾点出在创建单例对象冲突上)

但每次都同步一下,有点浪费时间。而且只是在第一次执行就可以,后面(有了实例对象)无需再执行,有它还成了累赘了。

那就通过 饿汉式单例 来解决。

饿汉式单例:

在静态初始化器中创建单例,getInstance() 直接返回单例对象。就保证了线程安全。

代码语言:javascript
复制
public class Singleton{
  private static Singleton instance = new Singleton;

  private Singleton(){}

  public static Singleton getInstance(){
    return instance;
  }
}

饿汉 就有 懒汉,之前的就是懒汉式,在第一次需要时才创建单例对象;一直没用到,一直就不会创建。

饿汉 VS 懒汉

☉饿汉式单例类在自己被加载是就将自己实例化,比懒汉式资源利用效率低;

☉饿汉式单例速度和响应时间由于懒汉式,不需要每次判断。

☉懒汉式必须在多个线程同时首次引用单例类时的访问限制问题,否则可能出错。

有没有其他方法呢?

有。双重检查锁(double-checked locking)

首先检查实例是否已经被创建,如果未被创建,则进行同步。只有第一次会同步,其他时候直接使用单例对象。

代码语言:javascript
复制
public class Singleton{
  private volatile static Singleton instance;

  private Singleton(){}

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

关键字:volatile 、synchronized()方法

volatile 确保当 instance 变量被初始化成 Singleton 实例时,多个线程正确处理 instance 变量。

getInstance() 中的同步动作,只有在 instance 未初始化时进行。一旦初始化,第一个 if 语句内容不需要执行。

注意:双重检查锁只适用于 1.4 之后的版本(不含 1.4)。

扩展思考:

借助单例类,约束其他的类。在单例类的方法中约束创建其他类的对象。

比如调用百度智能 API 时,在一个 BaseSingleton 单例类(自己创建)中,控制只生成一个 AipOcr (百度提供)对象。则不用继承 AipOcr。

这种实现不是严格的单例模式,不是该单例类的实例,但从功能上,却满足了一定要求。

这个有待商榷,期待和你交流。

致谢:

最后,亲爱的读者朋友,坚持看完不容易,篇幅长,尽量分享仔细全面。如有疑惑,欢迎与我交流。

感谢阅读,感谢陪伴。

表情包来源于网络,侵删。

「子乾建建为作者笔名」

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iBroPro 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
AI 应用产品
文字识别(Optical Character Recognition,OCR)基于腾讯优图实验室的深度学习技术,将图片上的文字内容,智能识别成为可编辑的文本。OCR 支持身份证、名片等卡证类和票据类的印刷体识别,也支持运单等手写体识别,支持提供定制化服务,可以有效地代替人工录入信息。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档