【设计模式】享元
享元模式介绍
享元模式主要在于共享通用对象,减少内存的使用,提升系统的访问效率。而这部分共享对象通常比较耗费内存或者需要查询大量接口或者使用数据库资源,因此统一抽离作为共享对象使用。
在使用此模式过程中,需要使用享元工厂来进行管理这部分独立的对象和共享的对象,避免出现线程安全的问题。
享元模式设计的思想:减少内存的使用提升效率,和之前学习的原型模式通过克隆对象的方式生成复杂对象,减少远程系统的调用。
享元与不可变性
在使用享元模式时,享元对象可在不同情景中是使用,必须确保其状态不可被修改。也就是说享元对象只能由构造函数进行一次性初始化,它不能对其他对象公开其设置器或共有成员变量。
享元工厂
为了更方便的访问各种享元,可以创建一个工厂方法来管理已有享元对象的缓存池。
工厂方法从客户端处接收目标享元对象的内在状态作为参数,如果能提前在缓存池中找到目标享元,则直接返回。如果没有找到,会自动创建一个享元对象,并将其添加到缓存池中。
享元模式的结构
-
享元模式只是一种优化,主要应用于与大量类似对象同时占用内存相关的内存消耗问题时使用。
-
享元 类包含原始对象中部分能在多个对象中共享的状态。
-
情景类 包含原始对象中各不相同的外在状态。情景与享元对象组合在一起就能表示原始对象的全部状态。
-
客户端 负责计算或存储享元的外在状态。
-
享元工厂 会对已有享元的缓存池进行管理。
有了工厂后,客户端无需直接创建享元,它们只需调用工厂并向其传递目标享元的一些内在状态即可。工厂会根据参数在之前已创建的享元中进行查找,如果找到满足的直接返回,若没有则进行创建新享元。
仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。
- 程序需要生产数量巨大的相似对象
- 这将耗尽目标设备的所有内存
- 对象中包含可抽取且能在多个对象间共享的重复状态
实现方式
1、将需要改写为享元的类成员变量拆分为两个部分
- 内在状态 : 包含不变的,可在许多对象中重复使用的数据的成员变量
- 外在状态 : 包含每个对象各自不同的情景数据的成员变量
2、保留类中表示内在状态的成员变量,并将其属性设置为不可修改。(这些不变的变量只能通过构造函数进行初始化操作)
3、找到所有使用外在状态成员变量的方法,为在方法中所有的每个成员变量新建一个参数,并使用该参数代替成员变量
4、你可以有选择地创建工厂类来管理享元缓存池,它负责在新建享元时检查已有的享元。如果选择使用工厂,客户端就只能通过工厂来请求享元,它们需要将享元的内在状态作为参数传递给工厂
5、客户端必须存储和计算外在状态的数值,因为只有这样才能调用享元对象的方法。外在状态和引用享元的成员变量可以移动到单独的情景类中。
优点: 如果程序有很多相似的对象,那么可以节省大量的内存。
缺点: 可能牺牲执行速度来换取内存、代码会变的更加复杂。
享元展示了如何生成大量的小型对象,外观模式则展示了如何用一个对象来代表整个子系统。
Demo
/// <summary>
/// 享元
/// </summary>
public class Flyweight
{
private Car _sharedState;
public Flyweight(Car car)
{
this._sharedState = car;
}
public void Operation(Car uniqueState)
{
string s = JsonConvert.SerializeObject(this._sharedState);
string u = JsonConvert.SerializeObject(uniqueState);
Console.WriteLine("Flyweight:Displaying shared "+s+" and unque "+u+" state");
}
}
/// <summary>
/// 享元工厂
/// 思路:提前在缓存池缓存对象,取值时先判断缓存池中取,如没有则创建,同时加入缓存池。
/// </summary>
public class FlyweightFactory
{
private List<Tuple<Flyweight,string>> flyweights=new List<Tuple<Flyweight,string>>();
public FlyweightFactory(params Car[] args)
{
foreach (var elem in args)
{
flyweights.Add(new Tuple<Flyweight,string>(new Flyweight(elem),this.getKey(elem)));
}
}
public string getKey(Car key)
{
List<string> elements = new List<string>();
elements.Add(key.Model);
elements.Add(key.Color);
elements.Add(key.Company);
if (key.Owner!=null&& key.Number!=null)
{
elements.Add(key.Number);
elements.Add(key.Owner);
}
elements.Sort();
return string.Join("_",elements);
}
public Flyweight GetFlyweight(Car sharedState)
{
string key = this.getKey(sharedState);
if (flyweights.Where(t=>t.Item2==key).Count()!=0)
{
Console.WriteLine("在享元工厂中,缓存中没有数据");
this.flyweights.Add(new Tuple<Flyweight,string>(new Flyweight(sharedState),key));
}
else
{
Console.WriteLine("缓冲池中有...");
}
return this.flyweights.Where(t => t.Item2 == key).FirstOrDefault().Item1;
}
public void listFlyweights()
{
var count = flyweights.Count;
foreach (var item in flyweights)
{
Console.WriteLine(item.Item2);
}
}
}
public class Car
{
public string Owner { get; set; }
public string Number { get; set; }
public string Company { get; set; }
public string Model { get; set; }
public string Color { get; set; }
}
static void Main(string[] args)
{
var factory = new FlyweightFactory(
new Car { Company = "Chevrolet", Model = "Camaro2018", Color = "pink" },
new Car { Company = "Mercedes Benz", Model = "C300", Color = "black" },
new Car { Company = "Mercedes Benz", Model = "C500", Color = "red" },
new Car { Company = "BMW", Model = "M5", Color = "red" },
new Car { Company = "BMW", Model = "X6", Color = "white" }
);
factory.listFlyweights();
addCarToPoliceDatabase(factory, new Car {
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "M5",
Color = "red"
});
addCarToPoliceDatabase(factory, new Car
{
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "X1",
Color = "red"
});
factory.listFlyweights();
Console.ReadKey();
}
static void addCarToPoliceDatabase(FlyweightFactory factory, Car car)
{
Console.WriteLine("添加一个新Car");
var flyweight = factory.GetFlyweight(new Car
{
Color = car.Color,
Model = car.Model,
Company = car.Company
});
flyweight.Operation(car);
}
对于享元工厂需要特意留意,它是先检索缓存池中的数据总情况,发现不是要找的,那么就新创建对象。
小寄语
人生短暂,我不想去追求自己看不见的,我只想抓住我能看的见的。
我是阿辉,感谢您的阅读,如果对你有帮助,麻烦点赞、转发 谢谢。
但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.