设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
单例模式
> 单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点。 ![单例模式](/media/202203/2022-03-05_214059_292984.png) 单例模式(Singleton Pattern,单件模式、Singleton)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。 单例模式被公认为是 反面模式,为了获得更好的可测试性和可维护性,请使用『依赖注入模式』。 注意: 1. 单例类只能有一个实例。 2. 单例类必须自己创建自己的唯一实例。 3. 单例类必须给所有其他对象提供这一实例。 ## 目的 在应用程序调用的时候,只能获得一个对象实例。 ## 介绍 - 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。 - 主要解决:一个全局使用的类频繁地创建与销毁。 - 何时使用:当您想控制实例数目,节省系统资源的时候。 - 如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。 - 关键代码:构造函数是私有的。 - 应用实例: 1. 一个班级只有一个班主任。 2. Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。 3. 一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。 - 优点: 1. 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2. 避免对资源的多重占用(比如写文件操作)。 - 缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。 - 使用场景: 1. 要求生产唯一序列号。 2. WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3. 创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。 - 注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。 ## 结构 ![单例模式结构](/media/202203/2022-03-02_225234_070556.png) 单例 (Singleton) 类声明了一个名为 getInstance获取实例的静态方法来返回其所属类的一个相同实例。 单例的构造函数必须对客户端 (Client) 代码隐藏。 调用获取实例方法必须是获取单例对象的唯一方式。 ## 伪代码 在本例中, 数据库连接类即是一个单例。 该类不提供公有构造函数, 因此获取该对象的唯一方式是调用 获取实例方法。 该方法将缓存首次生成的对象, 并为所有后续调用返回该对象。 ```java // 数据库类会对`getInstance(获取实例)`方法进行定义以让客户端在程序各处 // 都能访问相同的数据库连接实例。 class Database is // 保存单例实例的成员变量必须被声明为静态类型。 private static field instance: Database // 单例的构造函数必须永远是私有类型,以防止使用`new`运算符直接调用构 // 造方法。 private constructor Database() is // 部分初始化代码(例如到数据库服务器的实际连接)。 // ... // 用于控制对单例实例的访问权限的静态方法。 public static method getInstance() is if (Database.instance == null) then acquireThreadLock() and then // 确保在该线程等待解锁时,其他线程没有初始化该实例。 if (Database.instance == null) then Database.instance = new Database() return Database.instance // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑。 public method query(sql) is // 比如应用的所有数据库查询请求都需要通过该方法进行。因此,你可以 // 在这里添加限流或缓冲逻辑。 // ... class Application is method main() is Database foo = Database.getInstance() foo.query("SELECT ...") // ... Database bar = Database.getInstance() bar.query("SELECT ...") // 变量 `bar` 和 `foo` 中将包含同一个对象。 ``` ## 应用场景 - 如果程序中的某个类对于所有客户端只有一个可用的实例, 可以使用单例模式。 单例模式禁止通过除特殊构建方法以外的任何方式来创建自身类的对象。 该方法可以创建一个新对象, 但如果该对象已经被创建, 则返回已有的对象。 - 如果你需要更加严格地控制全局变量, 可以使用单例模式。 单例模式与全局变量不同, 它保证类只存在一个实例。 除了单例类自己以外, 无法通过任何方式替换缓存的实例。 请注意, 你可以随时调整限制并设定生成单例实例的数量, 只需修改 获取实例方法, 即 getInstance 中的代码即可实现。 ## 实现方式 1. 在类中添加一个私有静态成员变量用于保存单例实例。 2. 声明一个公有静态构建方法用于获取单例实例。 3. 静态方法中实现"延迟初始化"。 该方法会在首次被调用时创建一个新对象, 并将其存储在静态成员变量中。 此后该方法每次被调用时都返回该实例。 4. 将类的构造函数设为私有。 类的静态方法仍能调用构造函数, 但是其他对象不能调用。 5. 检查客户端代码, 将对单例的构造函数的调用替换为对其静态构建方法的调用。 ## 优点 - 你可以保证一个类只有一个实例。 - 你获得了一个指向该实例的全局访问节点。 - 仅在首次请求单例对象时对其进行初始化。 ## 缺点 - 违反了单一职责原则。 该模式同时解决了两个问题。 - 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。 - 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。 - 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。 ## 与其他模式的关系 - 外观模式类通常可以转换为单例模式类, 因为在大部分情况下一个外观对象就足够了。 - 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例类似了。 但这两个模式有两个根本性的不同。 1. 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。 2. 单例对象可以是可变的。 享元对象是不可变的。 - 抽象工厂模式、 生成器模式和原型模式都可以用单例来实现。 ## 示例代码 ### TS ```ts /** * The Singleton class defines the `getInstance` method that lets clients access * the unique singleton instance. */ class Singleton { private static instance: Singleton; /** * The Singleton's constructor should always be private to prevent direct * construction calls with the `new` operator. */ private constructor() { } /** * The static method that controls the access to the singleton instance. * * This implementation let you subclass the Singleton class while keeping * just one instance of each subclass around. */ public static getInstance(): Singleton { if (!Singleton.instance) { Singleton.instance = new Singleton(); } return Singleton.instance; } /** * Finally, any singleton should define some business logic, which can be * executed on its instance. */ public someBusinessLogic() { // ... } } /** * The client code. */ function clientCode() { const s1 = Singleton.getInstance(); const s2 = Singleton.getInstance(); if (s1 === s2) { console.log('Singleton works, both variables contain the same instance.'); } else { console.log('Singleton failed, variables contain different instances.'); } } clientCode(); ``` 输出: ```txt Singleton works, both variables contain the same instance. ``` ### PHP ```php <?php namespace DesignPatterns\Creational\Singleton; final class Singleton { /** * @var Singleton */ private static $instance; /** * 通过懒加载获得实例(在第一次使用的时候创建) */ public static function getInstance(): Singleton { if (null === static::$instance) { static::$instance = new static(); } return static::$instance; } /** * 不允许从外部调用以防止创建多个实例 * 要使用单例,必须通过 Singleton::getInstance() 方法获取实例 */ private function __construct() { } /** * 防止实例被克隆(这会创建实例的副本) */ private function __clone() { } /** * 防止反序列化(这将创建它的副本) */ private function __wakeup() { } } ``` ## 多例模式(Multiton) 多例模式被公认为是反面模式,为了获得更好的可测试性和可维护性,请使用『依赖注入模式』。 ## 目的 多例模式是指存在一个类有多个相同实例,而且该实例都是该类本身。这个类叫做多例类。 多例模式的特点是: - 多例类可以有多个实例。 - 多例类必须自己创建、管理自己的实例,并向外界提供自己的实例。 - 多例模式实际上就是单例模式的推广。 ## 举例 - 2 个数据库连接器,比如一个是 MySQL ,另一个是 SQLite - 多个记录器(一个用于记录调试消息,一个用于记录错误) ## 示例代码 ### PHP ```php <?php namespace DesignPatterns\Creational\Multiton; final class Multiton { const INSTANCE_1 = '1'; const INSTANCE_2 = '2'; /** * @var 实例数组 */ private static $instances = []; /** * 这里私有方法阻止用户随意的创建该对象实例 */ private function __construct() { } public static function getInstance(string $instanceName): Multiton { if (!isset(self::$instances[$instanceName])) { self::$instances[$instanceName] = new self(); } return self::$instances[$instanceName]; } /** * 该私有对象阻止实例被克隆 */ private function __clone() { } /** * 该私有方法阻止实例被序列化 */ private function __wakeup() { } } ```
追风者
2022年3月20日 21:32
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码