享元模式
本篇博客将介绍享元模式,当系统中存在大量相同或者相似的对象时,我们就可以考虑使用享元模式。享元模式通过共享技术实现相同或者相似的细粒度对象的复用,从而节约内存空间,提高系统性能。在享元模式中会提供一个享元池用于存储已经创建好的享元对象。
模式分类
结构型设计模式。
模式产生的原因
如果一个软件系统在运行时所创建的相同或者相似的对象数量太多,将导致运行代价过高,带来系统资源的浪费、性能下降等问题。例如在一个文本字符串中存在许多相同的字符,如果每一个字符我们都用一个单独的对象来表示,这将会占用许多不必要的内存空间,如何避免系统中出现大量相同或者相似的对象,同时又不影响客户端程序对这些对象进行操作,享元模式就是为解决这一问题诞生的。
在享元模式中,存储这些享元对象的地方称为享元池,享元对象可以做到共享的关键原因在于享元对象区分了内部状态和外部状态。
内部状态就是存储在享元对象内部并且不会随着外部环境的变化而变化的状态,内部状态可以共享。
外部状态就是会随着环境的变化而变化,不能共享的状态,通常一个外部状态和其他外部状态是相互独立的。
模式类图
由上图可知经典享元模式主要有3个对象构成:
FlyWeight(抽象享元类):
通常是一个抽象类或者接口,在抽象享元类中声明了具体享元类中的公共方法,这些方法可以为外界提供内部状态和设置外部状态。
ConcreteFlyWeight(具体享元类):
实现了抽象享元类的类,通常可以结合单例模式来设计具体享元类,为每一个享元类提供唯一的享元对象。,当然也并不是所有的享元类都需要共享。
FlyWeightFactory(享元工厂):
享元工厂主要负责保存和创建享元对象,所有创建好的享元对象都保存在享元工厂中的享元池中。
代码实现
字体FlyWeight抽象类:
using System;
namespace FlyWeight.FlyWeight.Question5
{
public class FontFlyWeight
{
//享元对象标识,用于作为存入键值对中的键值
public const string NAME = "";
private char fontContent;
public char FontContent
{
get => fontContent;
set => fontContent = value;
}
public FontFlyWeight(char font)
{
fontContent = font;
}
public string SetColor(string color)
{
Console.WriteLine($"将当前字体{FontContent}的字体颜色设置为{color}");
return color;
}
public string SetSize(string size)
{
Console.WriteLine($"将当前字体{FontContent}的字体大小设置为{size}");
return size;
}
}
}
字体享元工厂类:
using System.Collections.Generic;
namespace FlyWeight.FlyWeight.Question5
{
public class FontFactory
{
private static FontFactory m_instance;
public static FontFactory GetInstance
{
get
{
if (m_instance == null)
{
m_instance = new FontFactory();
}
return m_instance;
}
}
private FontFactory()
{
AddFont(FontA.NAME, new FontA('a'));
AddFont(FontB.NAME, new FontB('b'));
}
private Dictionary<string, FontFlyWeight> m_fontDic =
new Dictionary<string, FontFlyWeight>();
public void AddFont(string fontName, FontFlyWeight font)
{
if (m_fontDic.ContainsKey(fontName))
{
m_fontDic[fontName] = font;
}
else
{
m_fontDic.Add(fontName, font);
}
}
public FontFlyWeight FindFont(string fontName)
{
if (m_fontDic.ContainsKey(fontName))
{
return m_fontDic[fontName];
}
return null;
}
public void RemoveFont(string fontName)
{
if (m_fontDic.ContainsKey(fontName))
{
m_fontDic.Remove(fontName);
}
}
}
}
字体享元实例:
namespace FlyWeight.FlyWeight.Question5
{
public class FontB : FontFlyWeight
{
public new const string NAME = "FontB";
public FontB(char font) : base(font)
{
}
}
}
namespace FlyWeight.FlyWeight.Question5
{
public class FontA : FontFlyWeight
{
public new const string NAME = "FontA";
public FontA(char font) : base(font)
{
}
}
}
Program运行:其中的a和b的内存地址是同一个。
using FlyWeight.FlyWeight.Question4;
using FlyWeight.FlyWeight.Question5;
namespace FlyWeight
{
internal class Program
{
public static void Main(string[] args)
{
WebEquipmentFactory.GetInstance.AddWebEquipment(Switch.NAME, new Switch(Switch.NAME, 1));
WebEquipmentFactory.GetInstance.FindWebEquipment(Switch.NAME).SetPort(pc1, new Port());
WebEquipmentFactory.GetInstance.FindWebEquipment(Switch.NAME).SetPort(pc2, new Port());
FontFlyWeight a, b;
a = FontFactory.GetInstance.FindFont(FontA.NAME);
a.SetColor("red");
a.SetSize("80px");
b = FontFactory.GetInstance.FindFont(FontB.NAME);
b.SetColor("blue");
b.SetSize("80px");
}
}
}
享元模式的总结
享元模式的优点:
- 享元模式而可以减少内存中对象的数量,是的相同或者相似的对象在内存中只保存一份。节约系统资源,提升系统性能。
- 在享元模式中,外部状态相对独立,而且不会影响到内部状态,从而可以使享元对象可以在不同的环境中共享。
享元模式的缺点:
- 享元模式会使系统变得复杂,需要区分内部状态和外部状态,使得程序的逻辑复杂化。
- 为了使对象可以共享,享元模式需要将对象的部分状态外部化,而读取外部状态需要额外的时间。