c#享元模式详解
基本介绍:
享元模式的定义:运用共享技术有效地支持大量细粒度的对象重复使用。适用于大量小粒度的对象造成的运行效率和内存使用效率低下的情况。
“享元”顾名思义,“享”共享的意思,“元”单元,最小对象,零部件的意思。
即从字面意思不难看出,该模式旨在共享一些零部件供其使用。
想要实现对这些零部件的重复使用,那必然需要一个类来统筹安排,负责它们的创建、使用和维护。
如果想要实现一个零部件的使用,可以使用单例模式,所以享元模式其实也可以看做是单例模式的复数模式。
它们都是在一个固定类中对对象进行创建和维护。
举例说明:
比如五子棋游戏,构成游戏的组件,无非就是无数个黑棋和白棋。
黑棋和白棋就可以看做是两个最小的单元,在对战过程中就是在重复的创建这些单元。
如果是一般模式一盘棋局必然要创建N多个黑棋和白棋的对象,如果是一盘游戏还可以凑合使用。
大家想象一下,如果是个游戏平台,可以同时开展1000盘这样的棋局,那必然需要创建N*1000个黑白棋子对象。
其实这些对象都是重复的,只有很少一部分属性(状态)不同而已,相同的是棋子本身,不同的是棋子的颜色,比如黑棋和白棋之分。
另外该棋子在哪个棋盘、在哪个棋盘坐标就属于不可被共享的部分了,这部分内容就是非共享的。
既然弄清楚了相同的部分和不同部分,我们就可以把相同的部分进行共享,对象个数从原来的N*1000个对象降到了2个对象。
基本结构:
通过例子也不难看出,享元模式创建的对象存在两个状态:
内部状态:可以被共享的状态。存储在享元信息内部,并且不会随环境的改变而改变。在这里指的是棋子本身,它们不会随着棋局和选手的变化而变化。
外部状态:不可被共享的状态。随环境的改变而改变。在这里指的是黑白棋子颜色之分。
这里容易让人产生误区,容易把棋子所处坐标和棋盘等归在外部状态,这就大错特错了。
能共享的都是最基本的单元,而棋子的坐标和棋盘是不断变化的,随着棋局的不同而不同,这部分是不可以被共享的,也不能被共享。
享元模式的主要有以下角色:
抽象享元角色(Flyweight):通常是一个接口或抽象类,声明了具体享元类公共的方法,这些方法可以为外界提供内部状态和设置外部状态。
具体享元角色(Concrete Flyweight):实现了抽象享元类,在该类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
非享元角色 (Unsharable Flyweight):并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。本实例中,棋盘类就是非享元部分。
享元工厂角色(Flyweight Factory):负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
优缺点:
优点:降低了系统中对象的数量,从而降低了系统中细粒度对象给内存带来的压力。享元模式中的外部状态相对独立,且不影响内部状态。
缺点:为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,这使得程序的逻辑更复杂,使系统复杂化。
具体实例:
-
抽象享元角色
1 /// <summary> 2 /// 享元抽象类 3 /// </summary> 4 public abstract class ChessPieces 5 { 6 public string Colour; 7 public ChessPieces(string strColour) 8 { 9 Colour = strColour; 10 } 11 12 13 /// <summary> 14 /// 落子规则 15 /// </summary> 16 /// <param name="chessBoard">棋盘信息</param> 17 public void MoveInChess(ChessBoard chessBoard) 18 { 19 //这里可以将棋盘信息通过参数方式注入 20 } 21 22 //还可以设置一些 棋子的规则等等 23 }
享元抽象类主要是规范具体享元类供其继承,并提供其共有的属性和方法。
本实例只是最简单的使用构造函数参数注入方式,将棋子外部状态(颜色)更新。
而内部状态,比如棋子的具体下棋规则等等,可以在此类中声明。
比如此实例MoveInChess方法就是通过参数注入的形式将棋盘信息注入到内容,从而落实落子的具体规则。
规则是通用的,可以共享的,所以可以是内部状态的一部分。
- 具体享元角色
1 /// <summary> 2 /// 白棋 3 /// </summary> 4 public class BlackPieces : ChessPieces 5 { 6 public BlackPieces(string strColour) : base(strColour) 7 { 8 9 } 10 } 11 12 /// <summary> 13 /// 黑棋 14 /// </summary> 15 public class WhitePieces : ChessPieces 16 { 17 public WhitePieces(string strColour) : base(strColour) 18 { 19 20 } 21 }
继承自抽象享元类,具体实现其方法。
此实例只是通过构造函数简单的将棋子分成黑白棋子,实际中会有各种属性或方法需要在此类中实现。
- 享元工厂角色
/// <summary> /// 棋子工厂 /// </summary> public class WuziqiFactory { // 单例模式工厂 private static WuziqiFactory wuziqiFactory; // 缓存存放共享对象 private static Dictionary<string, ChessPieces> keyValuePairs = new Dictionary<string, ChessPieces>(); // 私有化构造方法 private WuziqiFactory() { if (keyValuePairs.Count == 0) { keyValuePairs.Add("Black", new BlackPieces("Black")); keyValuePairs.Add("White", new WhitePieces("White")); } } // 获得单例工厂 public static WuziqiFactory GetInstance { get { if (wuziqiFactory == null) { wuziqiFactory = new WuziqiFactory(); } return wuziqiFactory; } } // 获取棋子 public ChessPieces GetChessPieces(String type) { if (keyValuePairs.ContainsKey(type)) { return keyValuePairs[type]; } return null; } }
此实例使用单例模式创建工厂对象,保证整个项目生命周期内只存在一个棋子工厂对象,并使用GetInstance进行返回具体对象。这里可以加上锁防止并发等问题。
另外工厂的构造函数对可共享对象进行了缓存,使用GetChessPieces获取棋子对象时,可保证其不重复创建。保证整个项目生命周期内只存在黑白两个棋子对象。
- 非享元角色
1 /// <summary> 2 /// 棋盘 3 /// </summary> 4 public class ChessBoard 5 { 6 //棋盘编号 7 public int ChessBoardId { get; set; } 8 9 10 //黑方棋手 11 //白方棋手 12 //棋盘棋子布局等等属性 13 14 /// <summary> 15 /// 初始化棋盘 16 /// </summary> 17 public ChessBoard() 18 { 19 //可以通过构造函数 初始化棋盘基础属性 20 } 21 }
非享元部分,也就是不能共享的部分。
棋盘的编号、棋盘对局双方信息和棋盘落子情况等都是每个棋盘独有的,不可共享。
至于棋盘的落子和整体维护可以通过参数注入等形式交给棋子共享对象进行研判和操作,或者直接在棋盘类中进行声明都可以,这就看具体制定的规则了。
-
客户端
1 /// <summary> 2 /// 客户端 3 /// </summary> 4 class Client 5 { 6 static void Main(string[] args) 7 { 8 //创建棋盘 通过构造函数或者函数参数等形式初始化棋手、棋盘信息 9 ChessBoard chessBoard = new ChessBoard(); 10 //获取黑方棋子1 11 ChessPieces blackPieces1 = WuziqiFactory.GetInstance.GetChessPieces("Black"); 12 Console.WriteLine("棋子:" + blackPieces1.Colour); 13 //获取白方棋子1 14 ChessPieces whitePieces1 = WuziqiFactory.GetInstance.GetChessPieces("White"); 15 Console.WriteLine("棋子:" + whitePieces1.Colour); 16 17 //判断两个棋子是否是同一个对象 18 Console.WriteLine("判断两个不同颜色的棋子是否是同一个对象" + blackPieces1.Equals(whitePieces1) + "\r\n"); 19 20 //获取黑方棋子2 21 ChessPieces blackPieces2 = WuziqiFactory.GetInstance.GetChessPieces("Black"); 22 Console.WriteLine("棋子:" + blackPieces2.Colour); 23 //获取白方棋子2 24 ChessPieces whitePieces2 = WuziqiFactory.GetInstance.GetChessPieces("White"); 25 Console.WriteLine("棋子:" + whitePieces2.Colour); 26 27 //判断同一个颜色的两个棋子是否是同一个对象 28 Console.WriteLine("判断两个不同颜色的棋子是否是同一个对象" + blackPieces1.Equals(blackPieces2) + "\r\n"); 29 30 Console.ReadKey(); 31 } 32 }
总结:
实现享元工厂类时使用单例模式和简单工厂模式,确保对象的唯一性,并提供方法向客户端返回享元对象。