C#设计模式(13)——享元模式
1.享元模式介绍
在软件开发中我们经常遇到多次使用相似或者相同对象的情况,如果每次使用这个对象都去new一个新的实例会很浪费资源。这时候很多人会想到前边介绍过的一个设计模式:原型模式,原型模式通过拷贝现有对象来生成一个新的实例,使用拷贝来替代new。原型模式可以很好的解决创建多个相同/相似实例的问题,为什么还要用享元模式呢?这是因为这两种模式的使用场景是不同的,原型模式侧重于”创建“,我们通过拷贝确确实实的创建了新的实例,它属于创建型设计模式;而享元模式侧重于“重用”,即如果有现有的实例就不去创建了,直接拿来用就行了。
下面以大头儿子家开车为例介绍享元模式的用法。我们都知道大头儿子家里有三个人,这里就不用介绍了,家里现有一辆红色车和一辆蓝色车,小头爸爸,扁头妈妈和大头儿子开车时都是用家里现有的车,而不是每次开车都要新买一辆,只有想开的车家里没有时才会去买一辆,如大头儿子想开白色的车,但家里没有白色的车,这时候才去买一辆回来。我们直接在代码中理解享元模式的用法:
抽象车类Car定义了具体车共有的接口方法Use,无论什么车都就是可以用来开的,具体车类RealCar实现了Use接口。我们获取Car的实例不是通过new来获取,而是通过车库CarFactory的GetCar方法来获取,在GetCar方法中获取车时,首先判断车库中是否存在我们想要的车,如果有直接拿来用,如果没有才去买(new)一辆新车。
///抽象车类 public abstract class Car { //开车 public abstract void Use(Driver d); } /// <summary> /// 具体的车类 /// </summary> public class RealCar : Car { //颜色 public string Color { get; set; } public RealCar(string color) { this.Color = color; } //开车 public override void Use(Driver d) { Console.WriteLine($"{d.Name}开{this.Color}的车"); } } /// <summary> /// 车库 /// </summary> public class CarFactory { private Dictionary<string, Car> carPool=new Dictionary<string, Car>(); //初始的时候,只有红色和绿色两辆汽车 public CarFactory() { carPool.Add("红色", new RealCar("红色")); carPool.Add("绿色", new RealCar("蓝色")); } //获取汽车 public Car GetCar(string key) { //如果车库有就用车库里的车,车库没有就买一个(new一个) if (!carPool.ContainsKey(key)) { carPool.Add(key, new RealCar(key)); } return carPool[key]; } } /// <summary> /// 司机类 /// </summary> public class Driver { public string Name { get; set; } public Driver(string name) { this.Name = name; } }
客户端调用:
class Program { static void Main(string[] args) { CarFactory carFactory = new CarFactory(); //小头爸爸开蓝色的车 Driver d1 = new Driver("小头爸爸"); Car c1=carFactory.GetCar("蓝色"); c1.Use(d1); //扁头妈妈开蓝色的车 Driver d2 = new Driver("扁头妈妈"); Car c2 = carFactory.GetCar("蓝色"); c2.Use(d2); if (c1.Equals(c2)) { Console.WriteLine("小头爸爸和扁头妈妈开的是同一辆车"); } //车库没有白色的车,就new一辆白色的车 Driver d3 = new Driver("大头儿子"); Car c3 = carFactory.GetCar("白色"); c3.Use(d3); Console.ReadKey(); } }
运行程序结果如下:我们可以看到小头爸爸和扁头妈妈用的是同一辆车,就是复用了一个实例。
在使用享元模式时一个最大的问题是分离出对象的外部状态和内部状态。我们把对象内部的不会受环境改变而改变的部分作为内部状态,如例子中车的颜色,车的颜色不会随着外部因素司机的不同而改变;外部状态指的是随环境改变而改变的部分,对车来说,司机就是外部状态,我们可以通过公共接口的参数来传入外部状态。
2.小结
上边例子的类图
享元模式的使用场景:
当系统中大量使用某些相同或者相似的对象,这些对象要耗费大量的内存,并且这些对象剔除外部状态后可以通过一个对象来替代,这时可以考虑使用享元模式。在软件系统中享元模式大量用于各种池技术,如数据库连接对象池,字符串缓存池,HttpApplication池等。
享元模式的优点:
通过对象的复用减少了对象的数量,节省内存。
享元模式的缺点:
需要分离对象的外部状态和内部状态,使用不当会引起线程安全问题,提高了系统的复杂度。