使用C# (.NET Core) 实现单体设计模式 (Singleton Pattern)

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

因为我在给公司作内培, 因此最近每天写设计模式的文章....git

单体模式 Singleton

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

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

一段对话:设计模式

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

相关文章
相关标签/搜索