使用 C#/.NET Core 实现单体设计模式

本文的概念内容来自深入浅出设计模式一书

由于我在给公司做内培, 所以最近天天写设计模式的文章....

单体模式 Singleton

单体模式的目标就是只创建一个实例.

实际中有很多种对象我们可能只需要它们的一个实例, 例如: 线程池,缓存, 弹出的对话框, 用于保存设置的类, 用于logging的类, 硬件设备驱动对象等等.

一段对话:

A: 如何创建一个对象?

B: new MyObject()

A: 如果想创建另一个对象, 就再次new MyObject()?

B: 是的

A: 所以说我们有某个类, 我们就可以对它实例化很多次?

B: 是的, 但是它必须是public的类额

A: 如果不是public的呢?

B: 如果不是public的, 那么只有同一个包下的类才能对它实例化, 但是仍然可以实例化多次.

A: 嗯, 很有趣, 你只你可以这样做吗?

640?wx_fmt=png&wxfrom=5&wx_lazy=1

B: 没见过, 但是语法是没问题的, 存在即合理.

A: 它是什么意思呢?

B: 我想它不能被实例化吧, 因为它的构造函数是private的啊.

A: 那么, 有没有哪个对象可以使用这个private的构造函数呢?

B: 额, 我认为只有MyClass里面的代码可以调用这个构造函数, 但是感觉那没什么用啊.

A: 为什么没用呢?

B: 因为对类进行实例化, 就是想要用它的实例, 而这样做的话, 别的类也无法对它进行实例化啊. 这是个鸡和蛋的问题: 我可以使用MyClass里面的构造函数, 但是我无法实例化这个对象, 因为其他的类无法使用 "new MyClass()".

A: 你着确实是一种观点, 那么下面代码是什么意思呢?

640?wx_fmt=png

B: MyClass有一个静态方法, 我们可以这样调用静态方法: MyClass.getInstance();

A: 为什么使用MyClass, 而不是某个对象的名?

B: 因为getInstance()是静态方法; 也就是说, 它是一个类方法, 你需要使用类名来调用方法.

A: 非常有趣, 那么我把实例化代码放里面呢?

640?wx_fmt=png

B: 确实可以有这种操作...

A: 那么, 现在你认为有第二种方法来实例化对象吗?

B: MyClass.getInstance();

A: 那么你现在能写出只允许创造一个MyClass实例的代码了吗?

B: 应该行.

 

经典单体模式的实现

640?wx_fmt=png

首先需要有个静态成员变量保留着实例的引用.

然后构造函数必须是私有的.

getInstance()方法可以该类进行实例化, 并且返回该实例.

另外, 该类也可以有其他方法.

里面最重要的一部分代码:
640?wx_fmt=png

如果该实例引用为null, 那么创建一个实例, 并把这个实例赋給类的那个成员变量. 这里要注意, 如果我们永远不需要这个类的实例, 那么这个类永远也不会被实例化, 这叫做懒初始化.

如果实例引用不是null, 那么就说明之前已经创建过该类的实例了, 那么就返回之前创建的实例就行了.

 

一道巧克力工厂锅炉的题

先看这个类:

640?wx_fmt=png

开始的时候, 锅炉是空的, 所以也没有煮沸.

fill()方法(填充), 填充锅炉的时候, 锅炉必须是空的, 一旦填满了, 那么empty就改为false, 表示填满了. 刚填满肯定不是煮沸状态, 所以boiled也是false.

drain()方法(抽取), 只有锅炉是满的并且煮沸之后才能抽取巧克力液体, 抽取完了, 锅炉就又空了 empty改为true.

boil()方法(煮), 煮混合液体, 要求锅炉的前提状态必须是满的 empty为false, 并且还没煮沸 boiled为false. 一旦煮沸了, 就把boiled改成true.

这个工序很好, 但是必须保证只有一个锅炉, 那么该怎么做? 请写出代码.

单体模式定义

单体模式保证一个类只有一个实例, 并提供一个全局访问该实例的方法.

类图:

640?wx_fmt=png

 

其他问题

上面巧克力锅炉那道题你可能写好了, 但是可能会出现这个问题:

锅炉可能在里面有液体的情况下又进行了fill填充动作. 这是怎么回事?

是不是其他线程引起的这个问题?

我们可能有两个线程都在执行这段代码:

640?wx_fmt=png

那么两个线程调用时是否有重叠, 代码执行是否有交错?  请看下图:

640?wx_fmt=png

处理多线程问题

为了解决这个多线程的问题问题, 可已使用synchronized方法:

640?wx_fmt=png

(synchronized是java里的关键字, C#的请参考下面我写的代码)

使用synchronized关键字以后, 每个线程必须等到轮到它的时候才能进入方法. 这样两个线程就不可能同时进入该方法了.

但是这种方法开销很大, 这有时会成为一个问题. 而且可能比你想的更糟糕:

只有第一次执行该方法的时候synchronized才起作用, 一旦我们设定好了成员变量那个引用到具体的实例, 以后就不需要synchronized这个方法了, 除了第一次, 以后这就是额外的开销.

还能改进多线程吗

1. 如果性能不是那么重要, 就继续使用synchronized吧. 但是要记住使用synchronized之后运行速度可能会差100倍(JVM).

2. 那就不如早点把实例给创建出来, 而不是懒创建.

例如:

640?wx_fmt=png

使用静态的成员引用, 这样类在加载的时候就把实例创建出来了(保证在任何线程访问之前就会创建出来).

3. 使用"双重检查锁"来减少对sync的使用.

640?wx_fmt=png

这就是首先检查实例是否被创建了, 如果没有那么进入sync块. 第一创建实例的时候时sync的, 在块里面, 再检查一次实例是否为null, 然后创建实例.

volatile关键字会保证被单体实例化的时候多线程会正确的处理uniqueInstance变量.

所以如果性能是问题, 就可以使用这个方法.

其他问题

Q: 如果我创建一个类, 里面都是静态方法和静态变量, 那么它的效果和单体模式不是一样的吗?

A: 是的, 如果你类没有其他依赖并且初始化并不复杂的话.

Q: 可以继承单体模式吗?

A: 简单的回答就是: No.

Q: 为什么单体模式比全局变量好?

A: 全局变量会污染命名空间, 当然了单体模式写不好也很烂.

总结

640?wx_fmt=png

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;

            }

        }

    }

}

这个系列的代码我放在这里了: https://github.com/solenovex/Head-First-Design-Patterns-in-CSharp

相关文章:

原文地址 http://www.cnblogs.com/cgzl/p/8793447.html


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/321595.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux下离线安装gcc

有时服务无法连接网络,此时可以使用源码包的方式安装gcc工具 1.上传安装包并解压 创建目录 cd /home/software/ mkdir gcc_bak 上传到此目录下 tar -zxvf gcc_rpm.tar.gz 2.安装gcc 进入到解压后的目录,执行命令 rpm -Uvh *.rpm --nodeps --…

P3462-[POI2007]ODW-Weights【贪心】

正题 题目链接:https://www.luogu.com.cn/problem/P3462 题目大意 nnn个容器容量不同,mmm个物品,对于一两个物品i,ji,ji,j,若wi≤wjw_i\leq w_jwi​≤wj​那么有wi∣wjw_i|w_jwi​∣wj​。 求能够放下的最多物品。 解题思路 显然就是一个进…

用C#(.NET Core) 实现简单工厂和工厂方法设计模式

本文源自深入浅出设计模式. 只不过我是使用C#/.NET Core实现的例子.前言当你看见new这个关键字的时候, 就应该想到它是具体的实现.这就是一个具体的类, 为了更灵活, 我们应该使用的是接口(interface).有时候, 你可能会写出这样的代码:这里有多个具体的类被实例化了, 是根据不同…

1、oracle数据库简介

1.ORACLE数据库简介 数据库指的是存储和管理数据的仓库,是一种文件集合(包括数据文件、临时文件、日志文件和控制文件),我们一般所说的数据库指的是数据库管理系统,一种用于操作数据库的软件,简称DBMS。OR…

U102488-傻叉题【dp】

前言 %\%%一下出题人BPMBPMBPM 正题 题目链接:https://www.luogu.com.cn/problem/U102488 题目大意 nnn个数,选取两组(不一定要全用上)使得他们的和相等且最大。 解题思路 考虑dpdpdp,fi,jf_{i,j}fi,j​表示到第iii个,差值为jjj(这里的差值…

TypeScript 2.8引入条件类型

最新发布的TypeScript 2.8包含了若干主要特性和一些问题修复,其中最为重要的是新增了条件类型,开发人员可以根据其他类型的特征为变量选择适当的类型。条件类型最适合与泛型组合在一起使用。如果一个框架总是重复相同的选择性代码,就会变得很…

2、oracle数据库的用户和权限

ORACLE用户和权限 每个用户都有一个默认表空间和一个临时表空间,因此操作用户的顺序一般是先创建表空间,然后创建用户,之后赋权。 1.创建表空间 1.1 创建临时表空间 create temporary tablespace test_tmp tempfile /home/oracle/aracle…

jzoj6451-[2020.01.19NOIP提高组]不幸运数字【记忆化搜索,数位dp,高精度】

正题 题目链接:https://jzoj.net/senior/#main/show/6451 题目大意 给出a,ba,ba,b,求[a,b][a,b][a,b]这个区间中有多少数字包含444。 解题思路 用fi,j,kf_{i,j,k}fi,j,k​表示到第iii位,是否含有444,前面的位是否都到达上限。 然后dfsdfs…

用分布式缓存提升ASP.NET Core性能

得益于纯净、轻量化并且跨平台支持的特性,ASP.NET Core作为热门Web应用开发框架,其高性能传输和负载均衡的支持已广受青睐。实际上,10-20台Web服务器还是轻松驾驭的。有了多服务器负载的支持,使得Web应用层在业务增长时随时采用水…

3、oracle数据库的语法基础

ORACLE语法基础 ORACLE的语法基础主要包括数据类型和SQL语法。 1.数据类型 1.1字符型 用于存储文本的数据类型 CHAR(固定长度字符串1-2000个字节) VARCHAR2(可变长度字符串,字符串1-4000个字节) LONG(更…

123记住密码设置

1、打开123所在安装目录 2、进入config目录 3、找到.ovpn文件 4、找到auth-user-pass,没有的话在最后填写即可 在.ovpn同级目录下创建一个.txt文件,然后在这里设置一下 例如:auth-user-pass pass.txt 5、创建.txt文件,填写用户名…

Visual Studio 2017 15.7预览版发布

Visual Studio 2017已经发布一年多了,微软一直持续定期推出更新。第7个预览版也已发布,这一版本继续带来大量的改进。首先是增加了对TypeScript 2.8的支持。TypeScript 2.8带来了条件类型、JSX Pragma和映射类型标识符的可控性。VS2017的其他改进还包括在…

P3377-[模板]左偏树(可并堆)

正题 题目链接:https://www.luogu.com.cn/problem/P3377 题目大意 开始时nnn个只有一个数的集合&#xff0c;要求支持 合并两个集合查询一个集合中的最小值并删除 解题思路 左偏树就是维护一个满足以下性质的树 对于valxval_xvalx​有valx<vallsxval_x<val_{ls_x}va…

4、oracle数据库的查询基础

ORACLE查询基础 介绍oracle下数据的查询时用到的一些运算符、关键字和函数。 1.运算符 在进行数据查询时&#xff0c;有的时候不只是查询字段值&#xff0c;还需要做一些数据处理的操作&#xff0c;这时候就用到了运算符 -- 算术运算符 - * / -- 连接运算符 || -- 比较运算…

2018年4月更新70多个公司dnc招聘职位

2018年4月更新70多个公司dnc招聘职位请在本页回复&#xff0c;补充dnc招聘信息、公司案例dnc简介dnc .NET Core、dotnet Core简写dnc是微软新一代主力编程平台&#xff0c;开源、免费、跨平台、轻量级、高性能&#xff0c;可部署到Linux、Docker、k8s等环境&#xff0c;适合开…

P1552-[APIO2012]派遣【左偏树】

正题 题目链接:https://www.luogu.com.cn/problem/P1552 题目大意 一个nnn个点森林&#xff0c;每个点有价值和代价&#xff0c;选择一个点并在这个点的子树中选择一些点使得。 选择的点数∗该点的价值选择的点数*该点的价值选择的点数∗该点的价值最大且选择的点的代价之和不…

5、oracle下数据完整性约束

ORACLE下数据完整性约束 为了保证数据的完整性和结构的正确性&#xff0c;oracle也有很多约束条件。 1.数据完整性 为了保证数据的完整性&#xff0c;一般对数据列&#xff08;字段&#xff09;进行如下约束&#xff0c;主键约束&#xff08;PRIMARY KEY&#xff09;、唯一键…

把旧系统迁移到.Net Core 2.0 日记(1) - Startup.cs 解析

因为自己到开发电脑转到Mac Air&#xff0c;之前的Webform/MVC应用在Mac 跑不起来&#xff0c;而且.Net Core 2.0 已经比较稳定了。1. 为什么会有跨平台的.Net Core 近年来&#xff0c;我们已经进入云计算时代&#xff0c;在云平台的PaSS和SaSS上也是发生了大幅度的进化&#x…

P3261-[JLOI2015]城池攻占【左偏树】

正题 题目链接:https://www.luogu.com.cn/problem/P3261 题目大意 nnn个点的树&#xff0c;每个节点有一个防御值和一个攻击后的影响(让你的伤害加上一个数或者乘上一个数) 然后mmm个骑士&#xff0c;给定初始攻击点和初始伤害&#xff0c;不停往上走&#xff0c;遇到防御小于…

6、oracle数据库下查询操作

ORACLE下查询操作 针对数据库操作最多的就是数据查询&#xff0c;这里分享一个我常用的方法&#xff0c;看到需求后&#xff0c;先确定查询范围&#xff0c;就是需要查询哪些表&#xff1b;之后确定查询条件&#xff1b;最后写出查询的字段。 ORACLE中之前说过有两个伪列ROWI…