首页 > 其他分享 >我的设计模式之旅、02 单例模式(第二次更新)

我的设计模式之旅、02 单例模式(第二次更新)

时间:2022-09-19 01:22:06浏览次数:100  
标签:02 静态 模式 对象 电梯 实例 单例 设计模式

编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。

思考总结

什么是单例模式

单例模式(Singleton Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。

单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

image-20220919005657221

含义:

  • 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 职责角度看,实例化与否不应该由使用方判断,而是应该由自己来判断。将实例化判断过程迁移到GetInstance()函数。

  • Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。我们可以更改GetInstance() 函数添加条件和参数实现受控访问。

注意:

  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。
  • 通常情况下,单例类只能有一个实例。
  • 在特殊情况下,你可以随时调整限制并设定生成单例实例的数量, 只需修改获取实例方法, 即 getInstance 中的代码即可实现。

实现方式:

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new 运算符。

  • 新建一个静态构建方法作为构造函数。该函数会“偷偷”调用私有构造函数来创建对象,并将其保存在一个静态成员变量中。此后所有对于该函数的调用都将返回这一缓存对象(判断系统是否已经有这个单例,如果有则返回,如果没有则创建)

  • 检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。

  • 需要私有静态成员变量保存单例、公有静态构建方法获取单例、类的构造函数私有化保护单例实例。

应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:

  • 违反了单一职责原则。该模式同时解决了两个问题。
  • 单例模式可能掩盖不良设计,比如程序各组件之间相互了解过多等。
  • 该模式在多线程环境下需要进行特殊处理,避免多个线程多次创建单例对象。
    • getInstance() 方法中需要使用同步锁防止多线程同时进入造成 instance 被多次实例化。
  • 单例的客户端代码单元测试可能会比较困难,因为许多测试框架以基于继承的方式创建模拟对象。由于单例类的构造函数是私有的,而且绝大部分语言无法重写静态方法,所以你需要想出仔细考虑模拟单例的方法。要么干脆不编写测试代码,或者不使用单例模式。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

与其他模式的关系:

  • 外观类通常可以转换为单例类,因为在大部分情况下一个外观对象就足够了。
  • 如果你能将对象的所有共享状态简化为一个享元对象,那么享元就和单例类似了。但这两个模式有两个根本性的不同。
    • 只会有一个单例实体,但是享元类可以有多个实体,各实体的内在状态也可以不同。
    • 单例对象可以是可变的。享元对象是不可变的。
  • 抽象工厂、生成器和原型都可以用单例来实现。

双重”锁定“

原子操作,是并发编程中”最小的且不可并行化“的操作。本案例中使用原子操作配合互斥锁实现了非常高效的单例模式。互斥锁的代价比普通整数的原子读写高很多,所以在性能敏感的地方添加一个initialized标志位,通过原子检测标志位状态降低互斥锁的次数来提高性能。(也可以使用实例来进行判断,在实例未被创建的时候再加锁处理)

在互斥锁之后还要判断实例是否存在,是因为当多线程的时候,当一个线程处理完退出解锁,另一个在排队等候的线程进入后如果没有实例的判断,那么会再生成一遍实例,没有达到单例的目的。

静态初始化

C# 提供了静态初始化的方法,这种方法可以解决多线程环境下不安全的原因。

C# 给类添加sealed关键字防止子类继承产生多个单例、给静态字段添加readonly修改为只读状态,意味着只能在静态初始化期间或在类构造函数中分配变量。

这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在被第一次引用时才会自己实例化,这叫懒汉式初始化。

饿汉式初始化是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临多线程访问的安全性问题,需要做双重锁定才能保证安全。

在golang实现静态初始化,实际上只要把实例化的过程移动到当前文件的init函数中,在包被加载的过程中,会首先运行各个文件的init函数,再运行main函数。

实用类与单例类的比较

在C#经常有工具类之说,这个工具类包含许多静态方法和静态属性。但是这种实用类没有单例类的状态。实用类不能用于继承多台,而单例类虽然实例唯一,但是可以有子类来继承。实用类是一些方法属性的集合,单例是有着唯一对象的实例。

程序介绍

image-20220908221824615

本程序实现了单例模式,三个工作者需要各自找到电梯搭乘!电梯只有一个!

PS C:\Users\小能喵喵喵\Desktop\设计模式\单例模式> go run .
向海宁 正在搭乘电梯!
田海彬 正在搭乘电梯!

程序代码

singleton.go

package main

import (
	"fmt"
	"sync"
	"sync/atomic"
)

type People string

type Elevator struct {
	passengers map[People]bool
}

var (
	elevator    *Elevator
	initialized uint32
	mu          sync.Mutex
)

// 饿汉式单例
func init() {
	initialized = 1
	elevator = &Elevator{make(map[People]bool)}
}

// 乘客进电梯
func (p People) intoElevator() {
	e := ElevatorGetInstance()
	e.passengers[p] = 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, "正在搭乘电梯!")
	}
}

// 由于Golang不支持静态方法、静态字段,所以使用纯函数替代面向对象中的静态方法
// 乘客想知道电梯在哪,单例模式
func ElevatorGetInstance() *Elevator {
	if atomic.LoadUint32(&initialized) == 1 {
		return elevator
	}
	mu.Lock()
	defer mu.Unlock()
	// ^ 懒汉式单例
	if elevator == nil {
		elevator = &Elevator{make(map[People]bool)}
		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
    }
}

所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。

参考资料

  • 《Go语言核心编程》李文塔
  • 《Go语言高级编程》柴树彬、曹春辉
  • 《大话设计模式》程杰
  • 《深入设计模式》亚历山大·什韦茨
  • 单例模式 | 菜鸟教程

标签:02,静态,模式,对象,电梯,实例,单例,设计模式
From: https://www.cnblogs.com/linxiaoxu/p/16672173.html

相关文章

  • 2022 Jiangsu Collegiate Programming Contest
    A.PENTAKILL!把每个一个人的击杀序列分开,判断是否有连续五个不同的击杀就好#include<bits/stdc++.h>usingnamespacestd;map<string,vector<string>>st;int......
  • 我的设计模式之旅、12 原型模式
    编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。思考总结思考问题如果没有原型模式,当我们复制复杂对象,在新建相同类的对象,遍历原始对象中的所有成员变......
  • 「CSP-S 2022」初赛解析
    前言存疑点待补。有问题欢迎指出。想要题目部分源码请私信。有笨蛋连续\(2\)年第一题都错。乐。考前看了一眼一考就忘。如果不出意外的话,这是我最后一次更新初赛解析......
  • 博文目录(最新更新2022-09-19)
    python入门系列【python】python保姆级教学1-python安装【python】python保姆级教学2-anaconda安装与使用......
  • 【java8新特性】02:常见的函数式接口
    Jdk8提供的函数式接口都在java.util.function包下,Jdk8的函数式类型的接口都有@FunctionInterface注解所标注,但实际上即使没有该注解标注的有且只有一个抽象方法的接口,都可......
  • 2022 CSP-S 游记
    2022CSP-S游记看神仙们都写游记了我也来写写初赛Day0模拟赛又挂大分/fnrp--Day1上午vp了一下2019CSP-S的初赛题花了1h拿了58分觉得还行然后背了会......
  • 我的设计模式之旅、11 生成器(建造者)模式
    编程旅途是漫长遥远的,在不同时刻有不同的感悟,本文会一直更新下去。思考总结思考问题没有生成器模式的情况下在构建不同形式的复杂对象时的问题:如果为每种可能的对象都......
  • 20201302姬正坤第十章学习笔记
    第三周学习笔记第十章第十章的主要内容是研究sh编程。对于sh编程的介绍分为以下几个方面:sh脚本与C程序sh脚本的编写sh控制语句sh汉书知识点归纳:经过一整章的......
  • 2022-9-18 #29 愿灰飞烟灭 我的每个昨天
    CSP-S初赛,新赛季第一战。打的一般吧,虽说检查了很多遍但还是出了很多计算错误。。。大端小端是什么......
  • 2022ICPC网络赛 D Find the Number(子集生成)
    DFindtheNumber(子集生成)题目:​ 定义一个01串为好串:其二进制表示的后缀0和其二进制表示中1的个数相同。给出2e5次询问,若\([l,r]\)内存在一个整数的二进制串为好串的话,......