【手写源码-设计模式11】-享元模式-基于打麻将场景

1:主题拆解

①基本介绍

②孔雀东南飞的来源

③打麻将场景模拟

④享元模式的优缺点

⑤适用场景

⑥应用实例

⑦享元模式和单例模式的区别

⑧享元模式扩展

2:基本介绍

         说到享元模式,第一个想到的应该就是池技术了,String常量池、数据库连接池、缓冲池等等都是享元模式的应用,所以说享元模式是池技术的重要实现方式。

        比如我们每次创建字符串对象时,都需要创建一个新的字符串对象的话,内存开销会很大,所以如果第一次创建了字符串对象“设计模式”,下次再创建相同的字符串“设计模式”时,只要把它的引用指向“设计模式”,这样就实现了“设计模式”字符串在内存中的共享。

3:孔雀东南飞的来源

        提起打麻将相信大家最感兴趣的是胡牌,赢钱。我也想哈哈。那咱们今天就来胡牌就来一把大的。先上图。

         这副牌取名为孔雀东南飞。麻将中的孔雀东南飞是指幺鸡、东风、南风。因为幺鸡形似孔雀,当碰到东风、南风时就被称为“孔雀东南飞”,属于一种胡牌的叫法。

图片仅仅用于解说不涉及赌博,还请正确对待。

1:起源1

起源于汉乐府诗《孔雀东南飞》,乐府诗发展史上的高峰之作,后人盛称它与北朝的《木兰诗》为“乐府双璧”。故事繁简剪裁得当,人物刻画栩栩如生,不仅塑造了焦刘夫妇心心相印、坚贞不屈的形象,也把焦母的顽固和刘兄的蛮横刻画得入木三分。篇尾构思了刘兰芝和焦仲卿死后双双化为鸳鸯的神话,寄托了人民群众追求恋爱自由和幸福生活的强烈愿望。

基于恋爱自由和幸福生活的向往再结合麻将的精神,于是产生了“孔雀东南飞”这一种牌型。

孔雀东南飞,是指和牌的时候,东风与南风皆须成刻子,幺鸡可以是刻子也可以只有一对。

至于其他的牌,则没有限制。毕竟幺鸡能东、南两个方向飞,这样就必须要2个以上的幺鸡,否则只能向一个方向飞,东风与南风应该相平等。

2:起源2

        孔雀东南飞,先是从日本红孔雀打法演变来的,红孔雀是一副牌里都是条子。而且都是有红色的条子,而且必须是3个3个的,如:3个幺鸡,3个五条,3个七条,3个九条。

         而孔雀东南飞,就是在红孔雀的基础上,再加上3个东风和3个南风,如:2个幺鸡,3个5条,3个7条,3个南风,3个东风,这就叫孔雀东南飞。

3:小结

我相信来源1肯定是最正宗的,恋爱自由和幸福生活的向往再结合麻将的精神才是我们中华民族前辈们智慧的结晶。

4:打麻将场景模拟

我们还记得上面的胡牌吗,3个幺鸡,2个五条,3个七条,3个东方,3个南风

现在我们开始模拟打麻将场景。

1:基础版

①麻将基类

public abstract class Base麻将
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreateTime { get; set; }
    public abstract string Get();
}

②单个麻将实例

public class 幺 : Base麻将
{
    public 幺()
    {
        long lResult = 0;
        for (int i = 0; i < 1000000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(1500);
        this.Name = this.GetType().Name;
        Console.WriteLine("{0}被构造....", this.Name);            
    }
    public override string Get()
    {
        return this.Name;
    }
}
public class 东 : Base麻将
{
    public 东()
    {
        long lResult = 0;
        for (int i = 0; i < 1000000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(1500);
        this.Name = this.GetType().Name;
        Console.WriteLine("{0}被构造....", this.Name);
    }
    public override string Get()
    {
        return this.Name;
    }
}
public class 南 : Base麻将
{
    public 南()
    {
        long lResult = 0;
        for (int i = 0; i < 1000000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(1500);
        this.Name = this.GetType().Name;
        Console.WriteLine("{0}被构造....", this.Name);
    }
    public override string Get()
    {
        return this.Name;
    }
}
public class 五 : Base麻将
{
    public 五()
    {
        long lResult = 0;
        for (int i = 0; i < 1000000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(1500);
        this.Name = this.GetType().Name;
        Console.WriteLine("{0}被构造....", this.Name);
    }
    public override string Get()
    {
        return this.Name;
    }
}
public class 七 : Base麻将
{
    public 七()
    {
        long lResult = 0;
        for (int i = 0; i < 1000000; i++)
        {
            lResult += i;
        }
        Thread.Sleep(1500);
        this.Name = this.GetType().Name;
        Console.WriteLine("{0}被构造....", this.Name);
    }
    public override string Get()
    {
        return this.Name;
    }
}

③上端调用

Base麻将 幺鸡1 = new 幺();
Base麻将 幺鸡2 = new 幺();
Base麻将 幺鸡3 = new 幺();
Base麻将 五条1 = new 五();
Base麻将 五条2 = new 五();
Base麻将 七条1 = new 七();
Base麻将 七条2 = new 七();
Base麻将 七条3 = new 七();
Base麻将 东风1 = new 东();
Base麻将 东风2 = new 东();
Base麻将 东风3 = new 东();
Base麻将 南风1 = new 南();
Base麻将 南风2 = new 南();
Base麻将 南风3 = new 南();
var hupai = $"{幺鸡1.Get()}{幺鸡2.Get()}{幺鸡3.Get()}{五条1.Get()}{五条2.Get()}{七条1.Get()}{七条2.Get()}{七条3.Get()}{东风1.Get()}{东风2.Get()}{东风3.Get()}{南风1.Get()}{南风2.Get()}{南风3.Get()}";
Console.WriteLine(hupai);

④执行结果

分析:当前实现了一个基本版的孔雀东南飞。其中每一块麻将在构造的时候都实例化了一次,并且特别耗时。很显然这种方式构造了很多重复的对象,造成的资源的浪费

2:对象重用

①上端调用

Base麻将 幺鸡1 = new 幺();              
Base麻将 五条1 = new 五();              
Base麻将 七条1 = new 七();             
Base麻将 东风1 = new 东();               
Base麻将 南风1 = new 南();               
var hupai = $"{幺鸡1.Get()}{幺鸡1.Get()}{幺鸡1.Get()}{五条1.Get()}{五条1.Get()}{七条1.Get()}{七条1.Get()}{七条1.Get()}{东风1.Get()}{东风1.Get()}{东风1.Get()}{南风1.Get()}{南风1.Get()}{南风1.Get()}";
Console.WriteLine(hupai);

②执行结果

分析:我们把相同的对象重用之后,明显执行的时间比以前较少了很多,避免了很多重复对象的浪费。但是如果其他方法--其他的类---其他类库---其他线程,都需要重用对象,来提升下性能,该如何处理呢?

3:享元模式版

①享元工厂类

public class FlyweightFactory
{
    //准备个容器,数据复用
    private static Dictionary<麻将Type, Base麻将> FlyweightFactoryDictionary = new Dictionary<麻将Type, Base麻将>();
    private static object FlyweightFactory_Lock = new object();


    /// 多个线程同时执行,字典为空,所以会同时构造多次
    /// 最后只打印了一个Eleven,因为其他线程挂掉了--因为字典添加时异常了
    /// 2个相同麻将属于小概率事件,前2个线程是同时添加同一块麻将,所以都没失败,所以幺会有两个,
    ///    但是到了后面的麻将,又冲突了
    /// 子线程的异常是获取不到的,除非waitall
    public static Base麻将 Creat麻将(麻将Type type)
    {
            if (!FlyweightFactoryDictionary.ContainsKey(type))
            //是为了优化性能,避免对象已经被初始化后,再次请求还需要等待锁
            {
                lock (FlyweightFactory_Lock)//Monitor.Enter,保证方法体只有一个线程可以进入
                {
                    if (!FlyweightFactoryDictionary.ContainsKey(type))
                    {
                        Base麻将 word = null;
                        switch (type)
                        {
                            case 麻将Type.幺:
                                word = new 幺();
                                break;
                            case 麻将Type.五:
                                word = new 五();
                                break;
                            case 麻将Type.七:
                                word = new 七();
                                break;
                            case 麻将Type.东:
                                word = new 东();
                                break;
                            case 麻将Type.南:
                                word = new 南();
                                break;
                            default:
                                break;
                        }
                        FlyweightFactoryDictionary.Add(type, word);
                    }
                }
            }
            return FlyweightFactoryDictionary[type];
        }
}

public enum 麻将Type
{
    幺,
    五,
    七,
    东,
    南
}

②上端调用,多线程方式

for (int i = 0; i < 5; i++)
{
    Task.Run(() =>//近乎于同时启动5个线程完成计算
    {
        Base麻将 幺鸡1 = FlyweightFactory.Creat麻将(麻将Type.幺);
        Base麻将 五条1 = FlyweightFactory.Creat麻将(麻将Type.五);
        Base麻将 七条1 = FlyweightFactory.Creat麻将(麻将Type.七);
        Base麻将 东风1 = FlyweightFactory.Creat麻将(麻将Type.东);
        Base麻将 南风1 = FlyweightFactory.Creat麻将(麻将Type.南);
        var hupai = $"{幺鸡1.Get()}{幺鸡1.Get()}{幺鸡1.Get()}{五条1.Get()}{五条1.Get()}{七条1.Get()}{七条1.Get()}{七条1.Get()}{东风1.Get()}{东风1.Get()}{东风1.Get()}{南风1.Get()}{南风1.Get()}{南风1.Get()}";
        Console.WriteLine(hupai);
    });
}

分析:统一入口--想控制全局的东西,添加重用逻辑--静态字典缓存,基于这两点,就实现了不同类,不同类库,不同线程的对象重用。

享元模式为了解决对象的复用问题,提供第三方的管理,能完成对象的复用。

1:还可以自行实例化对象,不像单例是强制保证的 。

2:享元工厂也可以初始化多个对象----其他地方需要使用对象可以找我拿(修改个状态)---用完之后再放回来(状态改回来)----避免重复的创建和销毁对象,尤其对于非托管资源---池化资源管理。

5:享元模式的优缺点

1:优点

降低内存消耗:享元模式可以极大地减少内存中对象的数量,使得相同或相似对象在内存中只保存一份,从而节约系统资源,提供系统性能。

外部状态独立:享元模式外部状态相对独立,不会影响到内部状态,从而使得享元对象可以在不同环境中被共享。

2:缺点

增加复杂度:享元模式使得系统变复杂,需要分离出内部状态以及外部状态,使得程序逻辑复杂化。

运行时间变长:为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态使得运行时间变长。

6:适用场景

一个系统有大量相似或相同对象,造成大量内存浪费。

对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。

由于需要维护享元池,造成一定的资源开销,因此在需要真正多次重复使用享元对象时才值得使用享元模式。

7:应用实例

string常量池

数据库连接池

8:享元模式和单例模式的区别

享元模式可以再次创建对象,也可以取缓存对象;单例模式则是严格控制单个进程中只有一个实例对象。

享元模式可以通过自己控制对外部的单例,也可以在需要时创建更多的对象;单例模式是自身控制,需要增加不属于该对象本身的逻辑。

两者都可以实现节省对象创建的时间, threadPool线程池、数据库连接池都有使用享元模式。

9:享元模式扩展

1:输出什么?

Base麻将 幺1 = FlyweightFactory.Creat麻将(麻将Type.幺);
Base麻将 幺2 = new 幺();
Base麻将 幺3 = new 幺();
Base麻将 幺4 = FlyweightFactory.Creat麻将(麻将Type.幺);
Console.WriteLine(object.ReferenceEquals(幺1, 幺2));
Console.WriteLine(object.ReferenceEquals(幺1, 幺3));
Console.WriteLine(object.ReferenceEquals(幺1, 幺4));

分析:幺属于引用类型  new个对象等于去内存开辟一块儿空间,变量保存的是个引用,指向该内存     object.ReferenceEquals专门比较引用地址。

由于幺1与幺4是基于享元模式,因此是同一个对象,但是幺2与幺3是新实例。

2:输出什么?

string 麻将Open = "幺五七";
string 麻将Vip = "幺五七";
Console.WriteLine($"{object.ReferenceEquals(麻将Open, 麻将Vip)}");
Console.WriteLine($"{object.ReferenceEquals(麻将Open, new OtherClass().麻将Other)}");

public class OtherClass
{
   public string 麻将Other = "幺五七";
}

分析:string类型在内存分配时,使用了享元模式,所以是T。

只要是同一个进程,分配Eleven都是同一个内存地址的。

3:输出什么?

string 麻将Open = "幺五七";
string 麻将Format = string.Format("幺五{0}", "七");
string lastPart = "七";
string 麻将Combination = "幺五" + lastPart;
Console.WriteLine($"{object.ReferenceEquals(麻将Open, 麻将Format)}");
Console.WriteLine($"{object.ReferenceEquals(麻将Open, 麻将Combination)}");
Console.WriteLine($"{object.ReferenceEquals(麻将Format, 麻将Combination)}");

分析:分配“幺五七”都是同一个内存地址的,但是这两个都不是分配“幺五七”,而是部分分配,虽然最终结果是“幺五七”,但是没法重用。

4:输出什么?

public class OtherClass
{
   public string 麻将Other = "幺五七";
   public string 麻将Plus = "幺五" + "七";
}
string 麻将Plus = "幺五" + "七";
string 麻将Open = "幺五七";
Console.WriteLine($"{object.ReferenceEquals(麻将Open, 麻将Plus)}");
Console.WriteLine($"{object.ReferenceEquals(麻将Open, new OtherClass().麻将Plus)}");
Console.WriteLine($"{object.ReferenceEquals(麻将Open, new OtherClass().麻将Plus)}");

分析:编译器优化,string 麻将Plus = "幺五" + "七"; 等同于string 麻将Plus = "幺五七";

还有哪些比较好玩的场景,欢迎提出来一起研究讨论留言哦,还有别去“孔雀东南飞”了,好好学习进步!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不要迷恋发哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值