我的设计模式之旅 ② 单例模式
一个菜鸟的设计模式之旅,文章可能会有不对的地方,恳请大佬指出错误。编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。
程序介绍
https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/pic/2022/09/20220908221826_image-20220908221824615.png
本程序实现了单例模式,三个工作者需要各自找到电梯搭乘!电梯只有一个!
PS C:\Users\小能喵喵喵\Desktop\设计模式\单例模式> go run .
向海宁 正在搭乘电梯!
田海彬 正在搭乘电梯!程序代码
singleton.go
package main
import (
"fmt"
"sync"
"sync/atomic"
)
type people string
type elevator struct {
passengers mapbool
}
var (
Elevator *elevator
initialized uint32
mu sync.Mutex
)
// 饿汉式单例
func init() {
initialized = 1
Elevator = &elevator{make(mapbool)}
}
// 乘客进电梯
func (p people) intoElevator() {
e := ElevatorGetInstance()
e.passengers = true
}
// 乘客出电梯
func (p people) outElevator() {
e := ElevatorGetInstance()
delete(e.passengers, p)
}
// 乘客按下电梯楼层按钮
func (p people) pressButton() {
e := ElevatorGetInstance()
e.start()
}
// 电梯开始运作
func (e *elevator) start() {
for k := range e.passengers {
fmt.Println(k, "正在搭乘电梯!")
}
}
// 乘客想知道电梯在哪,单例模式
func ElevatorGetInstance() *elevator {
if atomic.LoadUint32(&initialized) == 1 {
return Elevator
}
mu.Lock()
defer mu.Unlock()
// ^ 懒汉式单例
if Elevator == nil {
Elevator = &elevator{make(mapbool)}
atomic.StoreUint32(&initialized, 1)
}
return Elevator
}main.go
package main
// 单例模式
// by 小能喵喵喵 2022年9月8日
func main() {
var workerA, workerB, workerC people = "陈冰", "向海宁", "田海彬"
workerA.intoElevator()
workerB.intoElevator()
workerC.intoElevator()
// workerA 发现自己电梯坐错了
workerA.outElevator()
// workerB 按下了电梯按钮
workerB.pressButton()
}Console
PS C:\Users\小能喵喵喵\Desktop\设计模式\单例模式> go run .
向海宁 正在搭乘电梯!
田海彬 正在搭乘电梯!补充C#单线程单例实现
class Singleton
{
private static Singleton instance;
private Singleton() //使用 private 字段外界无法使用new手动创建实例,只能通过静态方法创建
{
}
public static Singleton GetInstance()
{
if(instance == null)
{
instance = new Singleton();
}
return instance
}
}所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。
思考总结
什么是单例模式
单例模式(Singleton Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(由于Golang不支持静态方法、静态字段,所以使用纯函数替代面向对象中的静态方法,即上面程序的ElevatorGetInstance()函数)
职责角度看,实例化与否不应该由使用方判断,而是应该由自己来判断。将实例化判断过程迁移到GetInstance()函数。
在C#中的单例模式,Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。当然在golang的实现中,我们也可以更改ElevatorGetInstance() 函数添加条件和参数实现受控访问。
注意:
[*]单例类只能有一个实例。
[*]单例类必须自己创建自己的唯一实例。
[*]单例类必须给所有其他对象提供这一实例。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。(c#)
应用实例:
[*]1、一个班级只有一个班主任。
[*]2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
[*]3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。
优点:
[*]1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
[*]2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
[*]1、要求生产唯一序列号。
[*]2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
[*]3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
注意事项:getInstance() 方法中需要使用同步锁防止多线程同时进入造成 instance 被多次实例化。
双重”锁定“
原子操作,是并发编程中”最小的且不可并行化“的操作。本案例中使用原子操作配合互斥锁实现了非常高效的单例模式。互斥锁的代价比普通整数的原子读写高很多,所以在性能敏感的地方添加一个initialized标志位,通过原子检测标志位状态降低互斥锁的次数来提高性能。(也可以使用实例来进行判断,在实例未被创建的时候再加锁处理)
在互斥锁之后还要判断实例是否存在,是因为当多线程的时候,当一个线程处理完退出解锁,另一个在排队等候的线程进入后如果没有实例的判断,那么会再生成一遍实例,没有达到单例的目的。
静态初始化
C# 提供了静态初始化的方法,这种方法可以解决多线程环境下不安全的原因。
C# 给类添加sealed关键字防止子类继承产生多个单例、给静态字段添加readonly修改为只读状态,意味着只能在静态初始化期间或在类构造函数中分配变量。
这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在被第一次引用时才会自己实例化,这叫懒汉式初始化。
饿汉式初始化是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临多线程访问的安全性问题,需要做双重锁定才能保证安全。
在golang实现静态初始化,实际上只要把实例化的过程移动到当前文件的init函数中,在包被加载的过程中,会首先运行各个文件的init函数,再运行main函数。
实用类与单例类的比较
在C#经常有工具类之说,这个工具类包含许多静态方法和静态属性。但是这种实用类没有单例类的状态。实用类不能用于继承多台,而单例类虽然实例唯一,但是可以有子类来继承。实用类是一些方法属性的集合,单例是有着唯一对象的实例。
参考资料
[*]《Go语言核心编程》李文塔
[*]《Go语言高级编程》柴树彬、曹春辉
[*]《大话设计模式》程杰
[*]单例模式 | 菜鸟教程
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]