1.说明
前面5篇文章讲解了设计模式的语法、面向对象分析、原则、代码编写、类图表示法,从本文开始讲述23种设计模式。
后面会按照创建型、结构型、行为型的顺序来写
1.创建型5个:单例模式、简单工厂、工厂模式、建造者模式、原型模式 ,主要解决“对象的创建”问题
2.结构型7:代理模式、桥接模式、装饰器模式、适配器模式、门面模式 、组合模式、享元模式,主要解决“类或对象的组合或组装”问题
3.行为型11:观察者模式、模板模式、策略模式、职责链模式、状态模式、迭代器模式、访问者模式、备忘录模式、命令模式、解释器模式、中介模式,主要解决“类或对象之间的交互”问题
有7个模式并不太常用,他们分别是:组合模式、享元模式、状态模式、访问者模式、命令模式、解释器模式、中介模式,所以常用的设计模式16个。
每篇文章尽量都会有类图、定义、分析、使用场景、实现、代码、扩展等信息。之所以包含这些信息,因为对于很多人来说,你问他个具体的设计原则、思想、模式的原理和实现,他都能回答得头头是道,但是,在实际的项目开发中,写出来的代码质量还是很差。这种情况出现的原因还是,相关的知识点都过于抽象,通俗点讲就是有点“假大空”,不够具体、不太能落地,所以导致理论和实践容易脱节。
2.定义
2.1单例模式
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
UML类图:链接为https://www.processon.com/view/link/6080def6079129456d4beecf
2.2分析
通过分析能够看出以下几点:
1.单例模式能够保证类只有一个实例,就是类图里的instance
2.有方法能够让外部访问到该实例,就是类图里的GetInstance
3.外部无法创建实例,是因为构造函数为私有函数,外部无法访问,自然也就无法生成实例
3.使用场景
单例模式理解相对简单,一般在哪些场景下我们会用到单例模式呢?
1.多线程情况下会导致资源访问冲突
- 如项目需要写Log,而且Log一般会写入同一个文件。如果存在多个Log对象,即使Log有对象级别的锁,但在多线程下完全无用,日志仍然会乱序。解决这个问题我们可以使用类级别锁、分布式锁,但是都相对麻烦一些。如果使用单例模式,则Log只需保持对象级别锁就可以解决资源访问冲突
2.需要保证全局唯一的类
- 比如配置类,这种只应该存在一份
4.代码
单例模式的实现的时候需要考虑如下问题:
1.对象创建时线程安全问题
2.是否支持延迟加载
3.getInstance()性能是否高
根据语言不同,实现方式也不一样,一般有饿汉式、懒汉式、双重检测、静态内部类、枚举等。无论使用哪种方式,核心目的都是为了只会生成一个实例。
这里多少解释一下饿汉式和懒汉式。饿汉式可以简单的理解为实例提前创建好了,getInstance只是获取实例返回。懒汉式是调用getInstance的时候,getInstance负责生成唯一实例。两者各有优缺点,饿汉式不必考虑线程安全问题,实例生成的成本放在项目启动时,但不支持延迟加载;懒汉式需要考虑线程安全问题,支持延迟加载。
具体实现以前在文章Go单例实现方案中写过,实现方式比较简单
1 | /** |
1 | func main() { |
关于代码,此处说明几点:
1.使用sync.Once.Do,轻松解决线程安全问题,确保只会有一个实例
2.singleTon类首字母需要小写,这样能够保证非design的包无法创建单例类。如下图所示,main包无法获取到singleTon类
3.此处的实现方式是懒汉式,如果想使用饿汉式,可以使用init或者项目启动初始化时直接调用,具体实现大家可自己完成
4.具体代码可以查看:https://github.com/shidawuhen/asap/blob/master/controller/design/6single.go
5.总结
单例模式作为简单、常用的模式是一定需要掌握的。但单例模式也有一些缺点,如在继承、多态方面能力会弱一些,是否使用单例模式需要依据具体情况而定。另外单例模式也可以扩展到集群环境下、可以扩展为多例模式,这些内容大家有兴趣可以研究一下。