C#设计模式--享元模式

思考并回答以下问题:

思考并回答以下问题:

  • 享元模式不包括client共有四个类,没有再多的类了,说出是哪四个类和每个类的作用。
  • 即使森林里有100颗树实体,代码里也没有更多的类了。怎么理解?
  • 享元模式有一个工厂类,通过这个工厂维护的一个Dictionary进行相似实体类对象的增删查。怎么理解?
  • 通过公共接口的参数来传入外部状态怎么理解?
  • 我们new实例化对象A到Z的目的是什么?是为了调用他们的方法和字段。享元模式并不影响这个目的的实现,即使我们没有new类A到Z的对象,我们照样使用A到Z类要做的工作。怎么理解?
  • 享元模式属于结构型模式。为什么?
  • 客户端使用工厂类的工厂方法来创建这些大量相似的对象,不相似的地方就传参。怎么理解?

享元模式介绍

在软件开发中我们经常遇到多次使用相似或者相同对象的情况,如果每次使用这个对象都去new一个新的实例会很浪费资源。这时候很多人会想到前边介绍过的一个设计模式:原型模式,原型模式通过拷贝现有对象来生成一个新的实例,使用拷贝来替代new。原型模式可以很好的解决创建多个相同/相似实例的问题,为什么还要用享元模式呢?这是因为这两种模式的使用场景是不同的,原型模式侧重于“创建”,我们通过拷贝确确实实的创建了新的实例,它属于创建型设计模式;而享元模式侧重于“重用”,即如果有现有的实例就不去创建了,直接拿来用就行了。

下面以大头儿子家开车为例介绍享元模式的用法。我们都知道大头儿子家里有三个人,这里就不用介绍了,家里现有一辆红色车和一辆蓝色车,小头爸爸,扁头妈妈和大头儿子开车时都是用家里现有的车,而不是每次开车都要新买一辆,只有想开的车家里没有时才会去买一辆,如大头儿子想开白色的车,但家里没有白色的车,这时候才去买一辆回来。我们直接在代码中理解享元模式的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 抽象车类
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;
}
}

抽象车类Car定义了具体车共有的接口方法Use,无论什么车都是可以用来开的,具体车类RealCar实现了Use接口。我们获取Car的实例不是通过new来获取,而是通过车库CarFactory的GetCar方法来获取,在GetCar方法中获取车时,首先判断车库中是否存在我们想要的车,如果有直接拿来用,如果没有才去买(new)一辆新车。

客户端调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Program
{
static void Main(string[] args)
{
CarFactory carFactory = new CarFactory();

// 小头爸爸开蓝色的车
Car c1 = carFactory.GetCar("蓝色");
// 不一样的地方就传参
Driver d1 = new Driver("小头爸爸");
// 不需要new BlueCat("参数不同");创造100个对象
// 得到的结果都是一样的。
c1.Use(d1);

// 扁头妈妈开蓝色的车
Car c2 = carFactory.GetCar("蓝色");
Driver d2 = new Driver("扁头妈妈");
c2.Use(d2);

if (c1.Equals(c2))
{
Console.WriteLine("小头爸爸和扁头妈妈开的是同一辆车");
}

// 车库没有白色的车,就new一辆白色的车
Car c3 = carFactory.GetCar("白色");

Driver d3 = new Driver("大头儿子");
c3.Use(d3);
Console.ReadKey();
}
}

运行程序结果如下:我们可以看到小头爸爸和扁头妈妈用的是同一辆车,就是复用了一个实例。

在使用享元模式时一个最大的问题是分离出对象的外部状态和内部状态。我们把对象内部的不会受环境改变而改变的部分作为内部状态,如例子中车的颜色,车的颜色不会随着外部因素司机的不同而改变;外部状态指的是随环境改变而改变的部分,对车来说,司机就是外部状态,我们可以通过公共接口的参数来传入外部状态

小结

上边例子的类图

享元模式的使用场景:

当系统中大量使用某些相同或者相似的对象,这些对象要耗费大量的内存,并且这些对象剔除外部状态后可以通过一个对象来替代,这时可以考虑使用享元模式。在软件系统中享元模式大量用于各种池技术,如数据库连接对象池,字符串缓存池,HttpApplication池等。

享元模式的优点:

通过对象的复用减少了对象的数量,节省内存。

享元模式的缺点:

需要分离对象的外部状态和内部状态,使用不当会引起线程安全问题,提高了系统的复杂度。

其他

中间层的思考:享元模式在系统和直接使用细粒度对象之间加入了一个享元工厂,这个工厂就是中间层。系统不再直接创建和使用对象,而是由这个工厂来负责创建对象,工厂内部对同一对象只会创建一次,保证对象都是共享的。

模式迷思:享元模式会和简单工厂模式有点像,都用到了工厂的概念。其实享元模式里面就是用到了简单工厂模式来管理细粒度对象。享元模式解决的问题是对象的共享,而工厂模式解决的问题是如何封装对象的创建过程。明白它们两解决的问题,就知道它们是两种完全不一样的模式。但是它们却可以完美地结合在一起,协同解决问题。

0%