前言:  听说懂设计模式的Developer工资会高很多?最近面试也被问到熟悉设计模式有哪些?于是便有此文。

语言背景:PHP、Java

定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

类型:创建类模式

类图:

类图知识点:

1.类图分为三部分,依次是类名、属性、方法

2.以<<开头和以>>结尾的为注释信息

3.修饰符+代表public,-代表private,#代表protected,什么都没有代表包可见。

4.带下划线的属性或方法代表是静态的。

5.对类图中对象的关系不熟悉的朋友可以参考文章:设计模式中类的关系

单例模式应该是23种设计模式中最简单的一种模式了。

它有以下几个要素(特点):

  • 私有的构造方法(只能有一个实例)。
  • 指向自己实例的私有静态引用(必须自行创建这个实例)。
  • 以自己实例为返回值的静态的公有的方法(必须给其他对象提供这一实例)。

单例模式根据实例化对象时机的不同分为两种:

一种是饿汉式单例,一种是懒汉式单例。

饿汉式单例在单例类被加载时候,就实例化一个对象交给自己的引用;

而懒汉式在调用取得实例方法的时候才会实例化对象。

PHP版本代码如下:

懒汉模式:

<?phpclass Db {//静态变量保存全局实例private static $_instance = null;//私有构造函数,防止外界实例化对象private function __construct() {}//私有克隆函数,防止外办克隆对象private function __clone() {}//静态方法,单例统一访问入口public static function getInstance() {if (is_null(self::$_instance) || isset(self::$_instance)) {self::$_instance = new self ();}return self::$_instance;}private function getDbLink() {return new  mysqli ( "localhost" ,  "my_user" ,  "my_password" ,  "world" );} }

饿汉模式:

<?phpclass Db {//静态变量保存全局实例private static $_instance = new self ();//私有构造函数,防止外界实例化对象private function __construct() {}//私有克隆函数,防止外办克隆对象private function __clone() {}//静态方法,单例统一访问入口public static function getInstance() {return self::$_instance;}private function getDbLink() {return new  mysqli ( "localhost" ,  "my_user" ,  "my_password" ,  "world" );} }

在讲为什么要使用单例模式之前,我们先回顾一下以往使用DB的方式。

在以往的PHP4旧版本项目开发中,没使用单例模式前的情况如下:

<?php//初始化一个数据库句柄
$db_link = mysql_connect('YOUR_DB_ADDRESS','YOUR_DB_USER','YOUR_DB_PASS') or die("Database error");
mysql_select_db('YOUR_DB', $db_link); $result = mysql_query("set names 'utf8'"); //执行一次db查询
$query = "select * from YOUR_DB_TABLE";
$result = mysql_query($query); //关闭DB链接
mysql_close($db_link);

这种面向过程开发的代码,DB句柄变量完全暴露在外,有被修改的可能。

那么我们这样写:

db.php

<?php$db = null;
function get_db_link(){global $db;//初始化一个数据库句柄$db_link = mysql_connect('YOUR_DB_ADDRESS','YOUR_DB_USER','YOUR_DB_PASS') or die("Database error");mysql_select_db('YOUR_DB', $db_link); $result = mysql_query("set names 'utf8'");
}function close_db(){//关闭DB链接mysql_close($db_link);
}

index.php

<?phprequire_once 'db.php';get_db_link();//执行一次db查询
$query = "select * from YOUR_DB_TABLE";
$result = mysql_query($query); //关闭DB链接
close_db();

OK,这回没有显式的在上下文中看到DB相关的变量了吧? 但还是有被修改的可能。如我们的程序员小A ,他没有查看db.php上下文的情况下:

<?phprequire_once 'db.php';get_db_link();//这里,DB的链接句柄就被覆盖了,下面的代码都会导致报错!
$db = '123456'; //执行一次db查询
$query = "select * from YOUR_DB_TABLE";
$result = mysql_query($query); //关闭DB链接
close_db();

上面的程序例子,我们了解到面向过程开发哪怕你用函数进行了包装,还是无法杜绝被修改的可能。

自 PHP 5 起完全重写了对象模型以得到更佳性能和更多特性。这是自 PHP 4 以来的最大变化。PHP 5 具有完整的对象模型。

于是从PHP5开始完整的支持OOP概念编程了。我们用面向对象的方式再写一版本。代码如下:

<?phpclass User {//...public function getOne($id){$dbh = new PDO($YOUR_DB_DSN, 'YOUR_DB_USER','YOUR_DB_PASS', null);$result = $dbh->query('SELECT * FROM user WHERE id=' . $id);$user = [];//......return $user;}
}$users = [];
$user_obj = new User();
for($i=0;$i<100;$++){$users[] = $user_obj->getOne();
}
var_dump($users);

这样写代码没问题没毛病,DB句柄也是局部变量,外部无法访问。

但是问题来了,上面代码进行了100次PDO连数据库,数据库在本地情况下,性能还好说。但一旦跨机房了呢?

应用程序端需要进行100次的建立DB链接,这带来了不必要的通信开销。而且在并发情况下,会有很多客户端保持对DB的链接,但数据库的链接资源是有限的。这种编程方式是不可取的。

所以我们要使用单例模式,把DB链接保存在最少。这样就在程序上下文中能减少不必要的建立DB链接请求。

为什么要使用单例模式?

一个主要应用场合就是应用程序与数据库打交道的场景,在一个应用中会存在大量的数据库操作,针对数据库句柄连接数据库的行为,使用单例模式可以避免大量的new操作。因为每一次new操作都会消耗系统和内存的资源。

于是我们有了下面的单例模式:

Db.php:这是一个单例模式

<?phpclass DB{//静态变量保存全局实例private static $_db = null;//私有构造函数,防止外界实例化对象private function __construct() {}//私有克隆函数,防止外办克隆对象private function __clone() {}//静态方法,单例统一访问入口public static function getInstance() {if (is_null (self::$_instance) || isset(self::$_instance)) {self::$_db = new PDO($YOUR_DB_DSN, 'YOUR_DB_USER','YOUR_DB_PASS', null);}return self::$_db;}}

User.php

<?phprequire_once  'Db.php';class User {//...public function getOne($id){//$dbh = new PDO($YOUR_DB_DSN, 'YOUR_DB_USER','YOUR_DB_PASS', null);$result = Db::getInstance()->query('SELECT * FROM user WHERE id=' . $id);$user = [];//......return $user;}
}$users = [];
$user_obj = new User();
for($i=0;$i<100;$++){$users[] = $user_obj->getOne();
}
var_dump($users);

现在的代码,就不会进行建立100次DB链接请求了,整个程序上下文就只进行了一个耗时的DB链接。而这就是单例模式的优势。

单例模式的优点:

  • 在内存(程序上下文)中只有一个对象,节省内存空间。
  • 避免频繁的创建销毁对象,可以提高性能。
  • 避免对共享资源的多重占用。
  • 可以全局访问。

适用场景:由于单例模式的以上优点,所以是编程中用的比较多的一种设计模式。

  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
  • 有状态的工具类对象。
  • 频繁访问数据库或文件或其他资源的对象。
  • 以及其他没用过的所有要求只有一个对象的场景。

单例模式注意事项:

  • 只能使用单例类提供的方法得到单例对象,不要使用反射,否则将会实例化一个新对象。(php还需要确认)
  • 不要做断开单例类对象与类中静态引用的危险操作。
  • 多线程使用单例使用共享资源时,注意线程安全问题。(php中没有该顾虑,Java中会有)

总结:

得到一个规律就是,设计模式是解决OOP编程概念衍生出来的一种概念,不是为了解决面向过程的。

换句百度百科说的: 设计模式(英语 design pattern)是对面向对象设计中反复出现的问题的解决方案。

所以设计模式必然有各种设计原则,延伸阅读 设计模式--六大原则与三种类型。

=============================================完结撒花=============================================

扩展阅读:

写到这就完了吗?不,我们还没有考虑多线程情况下的问题呢!

上面绿字说明,在多线程情况下,单例模式是会有问题的。于是乎为了解决多线程情况下的单例使用共享资源的问题。

Java中有7中模式的单例写法,并不是茴的多种写法那么回事,而是每一种写法都有各自的优势。No B B, Show me code !  以下是Java代码:

第一种(懒汉,线程不安全):

public class Singleton {  //静态变量保存全局实例private static Singleton instance;  //私有构造函数,防止外界实例化对象private Singleton (){}  //静态方法,单例统一访问入口public static Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }
}

这种模式实现了懒加载,但在多线程情况下,红色部分代码多次执行,这就没有达到采用单例模式的优点: 在内存中只有一个对象,节省内存空间。避免频繁的创建对象。

那如何避免多次执行呢? 对,Java中提供了synchronized关键字,它就是一把锁,可以修饰在方法上,代码块上。代码如下:

第二种(懒汉,线程安全)

public class Singleton {  private static Singleton instance;  private Singleton (){}  //获得当前类的类锁public static synchronized Singleton getInstance() {  if (instance == null) {  instance = new Singleton();  }  return instance;  }
}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的懒加载效果,但是遗憾的是,效率很低,99%情况下不需要同步。

既然使用到了synchronized关键字来加锁,从而实现线程安全。那么我们知道synchronized是可以给代码块加锁的,我们调整一下上面的代码:

第三种(懒汉,变种双重校验锁线程安全)

public class Singleton {  //注意这里使用volatile关键字的内存屏障,来达到禁止指令重排序优化,使得线程间变量修改可见。private volatile static Singleton singleton;  private Singleton (){}  public static Singleton getSingleton() {  if (singleton == null) {//获得当前对象的类锁synchronized (Singleton.class) {  if (singleton == null) {  singleton = new Singleton();  }}  } return singleton;  }
}

这个是第二种方式的升级版,俗称双重检查锁定。注意红色部分关键字volatile,延伸阅读(双重检查锁失效是因为对象的初始化并非原子操作?   、如何正确地写出单例模式  )。

在JDK1.5之后,双重检查锁定才能够正常达到单例效果。关于synchronized锁的区别,请看:透彻理解 Java synchronized 对象锁和类锁的区别

第四种(饿汉,线程安全)

public class Singleton {  //静态变量保存全局实例,该静态实例在该类的初始化阶段被实例化,Java中的静态变量、静态方法与静态代码块详解与初始化顺序private static Singleton instance = new Singleton();  private Singleton (){}  public static Singleton getInstance() {  return instance;  }
}

这种方式基于Java的 classloder 机制避免了多线程的同步问题,不过,instance在类装载时就实例化了(Java中的静态变量、静态方法与静态代码块详解与初始化顺序)。

虽然导致类装载的原因有很多种,在单例模式中大多数都是调用getInstance方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance显然没有达到懒加载的效果。

第五种(饿汉,变种,线程安全)

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

表面上看起来代码组织形式有差别,其实跟第三种方式差不多,都是在类初始化即实例化instance(Java中的静态变量、静态方法与静态代码块详解与初始化顺序)。

六种(懒汉,静态内部类,线程安全)

public class Singleton {  private Singleton (){}  public static final Singleton getInstance() {  return SingletonHolder.INSTANCE;  }//内部静态类//Java机制规定,内部类 LazyHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了延迟加载效果),//而且其加载过程是线程安全的(实现线程安全)private static class SingletonHolder {  private static final Singleton INSTANCE = new Singleton();  }
}

这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,它跟第四种和第五种方式不同的是(很细微的差别):

第四种和第五种方式是只要Singleton类被装载了,那么instance就会被实例化(没有达到懒加载效果),而这种方式是Singleton类被装载了,instance不一定被初始化。

因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

想象一下,如果实例化instance很消耗资源,我想让他延迟加载,另外一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。

这个时候,这种方式相比第四和第五种方式就显得很合理。

第七种(枚举,线程安全)

class Resource{}public enum Singleton {INSTANCE;private Resource instance;Singleton() {instance = new Resource();}//外部程序直接用 Singleton.INSTANCE.getInstance() 获取单例实例;public Resource getInstance() {return instance;}
}

获取资源的方式很简单,只要 Singleton.INSTANCE.getInstance() 即可获得所要实例。扩展阅读,Java 利用枚举实现单例模式。

枚举模式对比第三种饿汉模式,相同之处就是没有实现懒加载。不同之处就是一个提供的是静态方法,一个是公有方法。单例的实例引用一个是私有静态变量,一个是私有变量。

这种方式是Effective Java作者Josh Bloch 提倡的方式,书中说: 单元素的枚举类型已经成为实现Singleton的最佳方法。

总结

1.一般来说,单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举。第一种方法不算正确的写法(Java多线程情况下),剩下都是线程安全的实现。

就日常Java编程而言,一般情况下直接使用饿汉式就好了。

如果明确要求要懒加载(实例化单例对象比较消耗资源)应该倾向于使用静态内部类方式,

如果涉及到反序列化创建对象时可以试着使用枚举的方式来实现单例。

2.通过Java单例模式的七种实现来说,对比与PHP编程,真是方式丰富很多很多。

这也可能就是Javaer鄙视PHPer的原因之一吧。

可以说这也是我为何要把Java当作第二语言的原因,PHP没有介入到多线程或协程领域,少了很多丰富的应用层面的数据结构、同步锁 和 一些编程理论知识!

3.学习编程的道路还任重而道远啊!

如写的不好,欢迎拍砖!

PS:

设计模式--六大原则与三种类型

单例模式的七种写法

如何正确地写出单例模式

Java 利用枚举实现单例模式

Java中的静态变量、静态方法与静态代码块详解与初始化顺序

双重检查锁失效是因为对象的初始化并非原子操作?

透彻理解 Java synchronized 对象锁和类锁的区别

转载于:https://www.cnblogs.com/phpdragon/p/8302571.html

23种设计模式[1]:单例模式相关推荐

  1. java 23种设计模式 04 单例模式

    java 23种设计模式 04 单例模式 一.什么是单例模式 单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象.也就是说,在整个程序空间中,该类只存在一个实例对象.   ...

  2. 23种设计模式之单例模式、工厂模式、原型模式、建造者模式

    系列文章目录 第一章:程序设计原则-单一职责.接口隔离.依赖倒置.里式替换 第二章:程序设计原则-开闭原则.迪米特法则.合成复用原则 文章目录 系列文章目录 一.设计模式简单介绍 1.1.什么是设计模 ...

  3. Java面试23种设计模式之单例模式的8种实现方式

    单例模式8中实现方式 1.单例模式介绍 2.单例模式的八种方式 3.饿汉式(静态常量),这种单例模式可用,可能造成内存浪费. 4.饿汉式(静态代码块),这种单例模式可用,可能造成内存浪费. 5.懒汉式 ...

  4. 《23种设计模式之单例模式(4种实现)》

    说在前头:本人为大二在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,能力有限,文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正.若 ...

  5. 23种设计模式之单例模式

    单例模式 单例模式(Singleton Pattern)是一个比较简单的模式,其定义为:Ensure a class has only one instance,and provide a globa ...

  6. JAVA设计模式总结之23种设计模式

    一.什么是设计模式                                                                                           ...

  7. 23种设计模式之模板方法

    23种设计模式总篇:chenmingyu.top/design/ 模板方法 模板方法属于行为型模式 定义:定义一个操作中的算法的框架,而将一些步骤延迟到子类中.使得子类可以不改变一个算法的结构即可重定 ...

  8. JAVA设计模式总结之23种设计模式(重点!!!)

    JAVA设计模式总结之23种设计模式: 一.什么是设计模式 设计模式遵循的原则有6个: 二.设计模式的三个分类 三.各分类中模式的关键点 四.概说23种设计模式 1.单例模式(Singleton) 2 ...

  9. java 的23种设计模式 单例模式

    23种设计模式友情链接: 点击打开链接 单例模式: A.饿汉式单例模式 具体步骤: 1.声明一个私有的静态的最终的本类类型的对象并实例化 private static final Person ins ...

  10. 23种设计模式之《单例模式》

    什么是单例模式 单例模式是23种设计模式中最简单和易用的模式.在某些情境下,如在一个上市公司中,有很多不同级别的员工,但是公司的CEO或者CTO都是只有一个的,CEO或者CTO在公司里就要求是一个单例 ...

最新文章

  1. 链表问题15——将搜索二叉树转换成双向链表
  2. tensorflow 集成开发环境 (IDE)
  3. android 3.0+百度地图api地图如何移动到指定的经纬度处
  4. P2340 奶牛会展(状压dp)
  5. python毕业设计开题报告-基于python爬虫的影评情感分析研究开题报告
  6. java 优先队列从小到大,优先队列(Java)
  7. leetcode-67-二进制求和
  8. Tensorflow 迁移学习 识别中国军网、中国军视网Logo水印
  9. iOS开发字符串倒序,倒序单词字母,不倒序单词位置
  10. Atitit 状态码专题 目录 1. FTP 1 1.1. 1xx - 肯定的初步答复 1 1.2. 2xx - 肯定的完成答复 1 1.3. 3xx - 肯定的中间答复 2 1.4. 4xx -
  11. Dreamweaver的jquery插件(用vs2005弄jquery的人有福了)
  12. MAC 迅雷最新版无限重启BUG的解决方法
  13. GLASS 产品使用(一)
  14. 计算机桌面性能3.3,显卡天梯图2018年3月最新版 三月桌面显卡性能排行 (全文)
  15. python提取一句话中的数字_从Python的字符串列表中提取数字
  16. CUDA +cnn安装
  17. python实训报告5000字_实训总结5000字
  18. Codeforces 645B Mischievous Mess Makers【逆序数】
  19. 找到一个在线网站截图的网站
  20. 免费卫星图像下载网站

热门文章

  1. 根据数组中的某个键值大小进行排序,仅支持二维数组
  2. PAT:1032. Sharing (25) AC
  3. 一个基于WF的业务流程平台
  4. ios 内存管理的理解(二)ARC概念及原理
  5. HDOJ 2018 母牛的故事
  6. 【转载】这次拆库 应是微服务化的拆分方式
  7. avalon2框架应用注意事项
  8. 选课 topsort
  9. MIFARE系列6《射频卡与读写器的通讯》
  10. POJ 1149(最大流)