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个线程是同时添加同一块麻将,所以都没失败,所以幺会有两个,///    但是到了后面的麻将,又冲突了/// 子线程的异常是获取不到的,除非waitallpublic 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 = "幺五七";

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

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

  1. 【设计模式-手写源码-2】-工厂方法模式-基于魔兽争霸冰封王座

    1:主题拆解 ① 对比简单工厂,建立工厂方法(FactoryMethod) ②工厂方法的优缺点和应用 ③对修改关闭对扩展开放 ④面向切面编程 2:基本介绍 定义一个用于创建对象的接口,让子类决定实例化 ...

  2. 【手写源码-设计模式6】-适配器模式-基于IPhoneX手机充电场景

    1:主题拆解 ①基本介绍 ②IPhone手机充电场景模拟 ③类适配器与对象适配器的区别 ④适配器模式的优缺点 ⑤适用场景 ⑥C#.NET中的使用场景 2:基本介绍 Adapter适配器模式,将一个类的 ...

  3. 【手写源码-设计模式15】-责任链模式-基于人事请假单工作流场景

    1:主题拆解 ①基本介绍 ②人事请假单工作流模拟 ③责任链模式的优缺点 ④适用场景 ⑤应用实例 ⑥ASP.NET 管道模型 2:基本介绍 责任链模式很像异常的捕获和处理,当一个问题发生的时候,当前对象 ...

  4. TransmittableThreadLocal的简单使用 + 手写源码

    文章目录 使用方法 原理 手写源码 之前有一篇文章谈到链路追踪场景下,需要在异步线程之间,实现跨线程的ThreadLocal传递, 简单场景可以用InheritableThreadLocal,但ITL ...

  5. 面试前端岗,你被要求手写源码了吗?

    如今前端开发的任职要求越来越高了,不仅要掌握 javascript,熟悉vue.react等各种框架,甚至连后端和 python 都得懂. 前几年只要熟练HTML.CSS.JavaScript ,靠扒 ...

  6. 前端为什么有的接口明明是成功回调却执行了.catch失败回调_前端进阶高薪必看-手写源码篇(高频技术点)...

    前言 此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈 初衷 我相信不少同学面试的时候最怕的一个环节就是手写代码 大 ...

  7. all方法 手写promise_前端进阶高薪必看手写源码篇

    前言 此系列作为笔者之前发过的前端高频面试整理的补充 会比较偏向中高前端面试问题 当然大家都是从新手一路走过来的 感兴趣的朋友们都可以看哈 初衷 我相信不少同学面试的时候最怕的一个环节就是手写代码 大 ...

  8. fortran中call的用法_手写源码系列(一)——call、apply、bind

    什么是手写源码 平时面试时经常会遇到让手写一个已有方法的实现,其实面试官是想考察你对于JS底层逻辑是否熟悉,经常面试会出的会在下面: call.apply.bind promise requireJS ...

  9. 北风设计模式课程---享元模式

    北风设计模式课程---享元模式 一.总结 一句话总结: 不仅要通过视频学,还要看别的博客里面的介绍,搜讲解,搜作用,搜实例 1.享元模式的本质是什么? 池技术:各种缓存池都是享元模式的体现 说到享元模 ...

  10. Java设计模式之享元模式(UML类图分析+代码详解)

    大家好,我是一名在算法之路上不断前进的小小程序猿!体会算法之美,领悟算法的智慧~ 希望各位博友走过路过可以给我点个免费的赞,你们的支持是我不断前进的动力!! 加油吧!未来可期!! 本文将介绍java设 ...

最新文章

  1. python北京时间代码_python代码定时同步本机的北京时间详解
  2. 两种方法判断一个字符串是否为另外一个字符串旋转之后的字符串。(C语言)...
  3. 图论--LCA--树上倍增法(在线)
  4. Java集合(8)--集合工具类Collections
  5. elementui获取所有树节点_element-ui tree获取子节点全选的父节点信息
  6. UVA - 11246 - K-Multiple Free set(容斥原理)
  7. 03-list,set,数据结构,Collections
  8. 多站点IIS的架设:端口法
  9. (四)在MLOps管道中进行持续训练
  10. docker 网桥冲突了解决
  11. Illustrator中文版教程,如何在 Illustrator 中快速连接和修剪路径?
  12. RadASM DosBox设置无法生效问题
  13. 关于SM2加密验签的操作
  14. 领导和同事嫌弃你的6个征兆,离失业不远了
  15. eclipse debug 多线程
  16. linux 下的进程创建线程,Linux线程 之 线程 线程组 进程 轻量级进程(LWP)
  17. Deepin 系统安装NVIDIA
  18. “无为而无不为”杂议
  19. 我没有基础,能学会Python吗?
  20. 编写两个过滤器,一个过滤器在 web 服务时,起用 UTF-8 编码,以处理 web 服务时的乱码问题。 另一个过滤器用来判断用户是否登录,只有登录用户方可访问/view 子目录。

热门文章

  1. 【Qt】Qt运行时Cannot retrieve debugging output错误
  2. python爬取图片源码_python爬图源码
  3. java 时区 夏令时_关于时区:如何显示Java开始夏令时后的时间差异?
  4. 数据结构课程设计:17、售票处的服务系统(***)
  5. 2020牛客寒假算法基础集训营1 G eli和字符串
  6. Android小钢琴
  7. SEO网站优化是什么
  8. 深入浅出TCP三次握手 (多图详解)
  9. Tcp三次握手、四次握手、数据传输
  10. python微信抢红包神器_用Python实现微信自动化抢红包,再也不用担心抢不到红包了...