使用 C#/.NET Core 实现单体设计模式
本文的概念内容来自深入浅出设计模式一书
由于我在给公司做内培, 所以最近天天写设计模式的文章....
单体模式 Singleton
单体模式的目标就是只创建一个实例.
实际中有很多种对象我们可能只需要它们的一个实例, 例如: 线程池,缓存, 弹出的对话框, 用于保存设置的类, 用于logging的类, 硬件设备驱动对象等等.
一段对话:
A: 如何创建一个对象?
B: new MyObject()
A: 如果想创建另一个对象, 就再次new MyObject()?
B: 是的
A: 所以说我们有某个类, 我们就可以对它实例化很多次?
B: 是的, 但是它必须是public的类额
A: 如果不是public的呢?
B: 如果不是public的, 那么只有同一个包下的类才能对它实例化, 但是仍然可以实例化多次.
A: 嗯, 很有趣, 你只你可以这样做吗?
B: 没见过, 但是语法是没问题的, 存在即合理.
A: 它是什么意思呢?
B: 我想它不能被实例化吧, 因为它的构造函数是private的啊.
A: 那么, 有没有哪个对象可以使用这个private的构造函数呢?
B: 额, 我认为只有MyClass里面的代码可以调用这个构造函数, 但是感觉那没什么用啊.
A: 为什么没用呢?
B: 因为对类进行实例化, 就是想要用它的实例, 而这样做的话, 别的类也无法对它进行实例化啊. 这是个鸡和蛋的问题: 我可以使用MyClass里面的构造函数, 但是我无法实例化这个对象, 因为其他的类无法使用 "new MyClass()".
A: 你着确实是一种观点, 那么下面代码是什么意思呢?
B: MyClass有一个静态方法, 我们可以这样调用静态方法: MyClass.getInstance();
A: 为什么使用MyClass, 而不是某个对象的名?
B: 因为getInstance()是静态方法; 也就是说, 它是一个类方法, 你需要使用类名来调用方法.
A: 非常有趣, 那么我把实例化代码放里面呢?
B: 确实可以有这种操作...
A: 那么, 现在你认为有第二种方法来实例化对象吗?
B: MyClass.getInstance();
A: 那么你现在能写出只允许创造一个MyClass实例的代码了吗?
B: 应该行.
经典单体模式的实现
首先需要有个静态成员变量保留着实例的引用.
然后构造函数必须是私有的.
getInstance()方法可以该类进行实例化, 并且返回该实例.
另外, 该类也可以有其他方法.
里面最重要的一部分代码:
如果该实例引用为null, 那么创建一个实例, 并把这个实例赋給类的那个成员变量. 这里要注意, 如果我们永远不需要这个类的实例, 那么这个类永远也不会被实例化, 这叫做懒初始化.
如果实例引用不是null, 那么就说明之前已经创建过该类的实例了, 那么就返回之前创建的实例就行了.
一道巧克力工厂锅炉的题
先看这个类:
开始的时候, 锅炉是空的, 所以也没有煮沸.
fill()方法(填充), 填充锅炉的时候, 锅炉必须是空的, 一旦填满了, 那么empty就改为false, 表示填满了. 刚填满肯定不是煮沸状态, 所以boiled也是false.
drain()方法(抽取), 只有锅炉是满的并且煮沸之后才能抽取巧克力液体, 抽取完了, 锅炉就又空了 empty改为true.
boil()方法(煮), 煮混合液体, 要求锅炉的前提状态必须是满的 empty为false, 并且还没煮沸 boiled为false. 一旦煮沸了, 就把boiled改成true.
这个工序很好, 但是必须保证只有一个锅炉, 那么该怎么做? 请写出代码.
单体模式定义
单体模式保证一个类只有一个实例, 并提供一个全局访问该实例的方法.
类图:
其他问题
上面巧克力锅炉那道题你可能写好了, 但是可能会出现这个问题:
锅炉可能在里面有液体的情况下又进行了fill填充动作. 这是怎么回事?
是不是其他线程引起的这个问题?
我们可能有两个线程都在执行这段代码:
那么两个线程调用时是否有重叠, 代码执行是否有交错? 请看下图:
处理多线程问题
为了解决这个多线程的问题问题, 可已使用synchronized方法:
(synchronized是java里的关键字, C#的请参考下面我写的代码)
使用synchronized关键字以后, 每个线程必须等到轮到它的时候才能进入方法. 这样两个线程就不可能同时进入该方法了.
但是这种方法开销很大, 这有时会成为一个问题. 而且可能比你想的更糟糕:
只有第一次执行该方法的时候synchronized才起作用, 一旦我们设定好了成员变量那个引用到具体的实例, 以后就不需要synchronized这个方法了, 除了第一次, 以后这就是额外的开销.
还能改进多线程吗
1. 如果性能不是那么重要, 就继续使用synchronized吧. 但是要记住使用synchronized之后运行速度可能会差100倍(JVM).
2. 那就不如早点把实例给创建出来, 而不是懒创建.
例如:
使用静态的成员引用, 这样类在加载的时候就把实例创建出来了(保证在任何线程访问之前就会创建出来).
3. 使用"双重检查锁"来减少对sync的使用.
这就是首先检查实例是否被创建了, 如果没有那么进入sync块. 第一创建实例的时候时sync的, 在块里面, 再检查一次实例是否为null, 然后创建实例.
volatile关键字会保证被单体实例化的时候多线程会正确的处理uniqueInstance变量.
所以如果性能是问题, 就可以使用这个方法.
其他问题
Q: 如果我创建一个类, 里面都是静态方法和静态变量, 那么它的效果和单体模式不是一样的吗?
A: 是的, 如果你类没有其他依赖并且初始化并不复杂的话.
Q: 可以继承单体模式吗?
A: 简单的回答就是: No.
Q: 为什么单体模式比全局变量好?
A: 全局变量会污染命名空间, 当然了单体模式写不好也很烂.
总结
C# 实现
ChocolateBoiler:
namespace SingletonPattern
{
public class ChocolateBoiler
{
public bool Empty { get; private set; }
public bool Boiled { get; private set; } private static ChocolateBoiler _uniqueInstance; private ChocolateBoiler()
{
Empty = true;
Boiled = false;
} public static ChocolateBoiler GetInstance()
{
return _uniqueInstance ?? (_uniqueInstance = new ChocolateBoiler());
} public void Fill()
{
if (Empty)
{
Empty = false;
Boiled = false;
}
} public void Drain()
{
if (!Empty && Boiled)
{
Empty = true;
}
} public void Boil()
{
if (!Empty && !Boiled)
{
Boiled = true;
}
}
}
}
SynchronizedChocolateBoiler:
using System.Runtime.CompilerServices; namespace SingletonPattern
{
public class SynchronizedChocolateBoiler
{
public bool Empty { get; private set; }
public bool Boiled { get; private set; } private static SynchronizedChocolateBoiler _uniqueInstance; private SynchronizedChocolateBoiler()
{
Empty = true;
Boiled = false;
} [MethodImpl(MethodImplOptions.Synchronized)]
public static SynchronizedChocolateBoiler GetInstance()
{
return _uniqueInstance ?? (_uniqueInstance = new SynchronizedChocolateBoiler());
} public void Fill()
{
if (Empty)
{
Empty = false;
Boiled = false;
}
} public void Drain()
{
if (!Empty && Boiled)
{
Empty = true;
}
} public void Boil()
{
if (!Empty && !Boiled)
{
Boiled = true;
}
}
}
}
DoubleCheckChocolateBoiler:
namespace SingletonPattern
{
public class DoubleCheckChocolateBoiler
{
public bool Empty { get; private set; }
public bool Boiled { get; private set; } private static volatile DoubleCheckChocolateBoiler _uniqueInstance;
private static readonly object LockHelper = new object(); private DoubleCheckChocolateBoiler()
{
Empty = true;
Boiled = false;
} public static DoubleCheckChocolateBoiler GetInstance()
{
if (_uniqueInstance == null)
{
lock (LockHelper)
{
if (_uniqueInstance == null)
{
_uniqueInstance = new DoubleCheckChocolateBoiler();
}
}
}
return _uniqueInstance;
} public void Fill()
{
if (Empty)
{
Empty = false;
Boiled = false;
}
} public void Drain()
{
if (!Empty && Boiled)
{
Empty = true;
}
} public void Boil()
{
if (!Empty && !Boiled)
{
Boiled = true;
}
}
}
}
由于这里面提到了多线程, 所以我会另写一篇关于C#/.NET Core异步和多线程的文章(也会是书上的内容, 这本书叫 C# 7 in a Nutshell, 我认为这是最好的C#/.NET Core参考书, 可是没有中文的, 所以我就是做一下翻译和精简)....
这个系列的代码我放在这里了: https://github.com/solenovex/Head-First-Design-Patterns-in-CSharp
使用 C#/.NET Core 实现单体设计模式的更多相关文章
- 使用C# (.NET Core) 实现单体设计模式 (Singleton Pattern)
本文的概念内容来自深入浅出设计模式一书 由于我在给公司做内培, 所以最近天天写设计模式的文章.... 单体模式 Singleton 单体模式的目标就是只创建一个实例. 实际中有很多种对象我们可能只需要 ...
- 使用C# (.NET Core) 实现组合设计模式 (Composite Pattern)
本文的概念性内容来自深入浅出设计模式一书. 本文需结合上一篇文章(使用C# (.NET Core) 实现迭代器设计模式)一起看. 上一篇文章我们研究了多个菜单一起使用的问题. 需求变更 就当我们感觉我 ...
- php 抽象类 静态 单体设计模式
php oop----抽象类 抽象类机制使得子类可共用基类的某些信息,具体细节会留给子类,典型用在这样情形中,抽象类并不定义全部的方法,部分方法的实现推迟到子类继承抽象类时.它是介于接口和具体类间的一 ...
- C#中的单体设计模式Singleton
经典的单体设计模式的实现就是 有一个Public的类,在这个类中,有一个私有的private的构造函数. 然后有一个静态方法,这个静态方法返回这个类的实例 举个例子如下 Public class My ...
- 用C# (.NET Core) 实现迭代器设计模式
本文的概念来自深入浅出设计模式一书 项目需求 有两个饭店合并了, 它们各自有自己的菜单. 饭店合并之后要保留这两份菜单. 这两个菜单是这样的: 菜单项MenuItem的代码是这样的: 最初我们是这样设 ...
- 使用C# (.NET Core) 实现迭代器设计模式 (Iterator Pattern)
本文的概念来自深入浅出设计模式一书 项目需求 有两个饭店合并了, 它们各自有自己的菜单. 饭店合并之后要保留这两份菜单. 这两个菜单是这样的: 菜单项MenuItem的代码是这样的: 最初我们是这样设 ...
- 详解 ASP.NET Core MVC 的设计模式
MVC 是什么?它是如何工作的?我们来解剖它 在本节课中我们要讨论的内容: 什么是 MVC? 它是如何工作的? 什么是 MVC MVC 由三个基本部分组成 - 模型(Model),视图(View)和控 ...
- 使用 C# (.NET Core) 实现命令设计模式 (Command Pattern)
本文的概念内容来自深入浅出设计模式一书. 项目需求 有这样一个可编程的新型遥控器, 它有7个可编程插槽, 每个插槽可连接不同的家用电器设备. 每个插槽对应两个按钮: 开, 关(ON, OFF). 此外 ...
- 使用C# (.NET Core) 实现状态设计模式 (State Pattern)
本文的概念性内容来自深入浅出设计模式一书 项目需求 这是一个糖果机的需求图. 它有四种状态, 分别是图中的四个圆圈: No Quarter: 无硬币 Has Quater 有硬币 Gumball So ...
随机推荐
- Javascript的那些硬骨头:作用域、回调、闭包、异步……
终于到了神话破灭的时刻-- 这注定是一篇"自取其辱"的博客,飞哥,你们眼中的大神,Duang,这次脸朝下摔地上了. 故事得从这个求助开始:e.returnValue 报错:未定义, ...
- 软件测试第二周作业 WordCount
本人github地址: https://github.com/wenthehandsome23 psp阶段 预估耗时 (分钟) 实际耗时 (分钟) 计划 30 10 估计这个任务需要多少时间 20 ...
- [模拟赛] T1 无线通讯网
Description 国防部计划用无线网络连接若干个边防哨所.2种不同的通讯技术用来搭建无线网络: 每个边防哨所都要配备无线电收发器:有一些哨所还可以增配卫星电话. 任意两个配备了一条卫星电话线路的 ...
- Redis学习笔记(三)常用命令整理
Redis 常用命令 1.DEL key 删除key2.EXISTS key 检查key是否存在3.KEYS * 查看所有的key4.EXPIRE key seconds 设置key的过期时间5.TT ...
- 神奇的RAC宏
先说说RAC中必须要知道的宏 RAC(TARGET, [KEYPATH, [NIL_VALUE]]) 使用: RAC(self.outputLabel, text) = self.inputTex ...
- SSE(Server-sent events)技术在web端消息推送和实时聊天中的使用
最近在公司闲着没事研究了几天,终于搞定了SSE从理论到实际应用,中间还是有一些坑的. 1.SSE简介 SSE(Server-sent events)翻译过来为:服务器发送事件.是基于http协议,和W ...
- 透析thinkphp5升级版开发框架tpframe
这里将全面的介绍这个框架给我们开发带来的好处,让你们对它有更深层次的认识,喜欢或不喜欢的,欢迎大家前来留言讨论 一.目录层次结构 现在很多的项目,特别是大一点的项目里面,都会有很多的人参与,要进行程序 ...
- 使用 Except 和 Intersect
做了一个如下的小厕所,如果我需要得到返回是 d,f 那我需要用那组语句呢? A: ;WITH CA AS( SELECT * FROM (VALUES('a'),('b'),('c'),('d'))a ...
- SIMD---AVX系列
AVX全称Advanced Vcetor Extension,是对SSE的后续扩展,主要分为AVX.AVX2.AVX512三种.在目前常见的机器上,大多只支持到AVX系列,因此其他SIMD扩展指令我们 ...
- Beta冲刺第四天
一.昨天的困难 没有困难. 二.今天进度 1.林洋洋:修复协作详情,日程详情日程类型显示纠正 2.黄腾达:修复管理者查看协作成员可以移除自己的问题,加入登录.注册表单按回车键就可直接完成操作的功能 3 ...