【设计模式与体系结构】结构型模式-享元模式
简介
享元模式(Flyweight Pattern)是一种用于优化创建和使用对象的结构型设计模式。享元模式以共享的方式高效地支持大量细粒度的对象的重用,它的主要目的是通过共享对象来减少内存的使用和提高性能。在很多系统软件中,会创建大量相似的对象,这些对象可能只有部分属性不同,享元模式就是为了处理这种情况而出现的。
享元模式将对象的结构分为了内部状态和外部状态,内部状态是指对象可共享的部分,它存储在享元对象内部并且不会随着环境变化而变化,内部状态是享元对象能被共享的关键,外部状态是随环境而变化的部分,不能够被不同对象共享,它通常在对象的方法调用时作为参数传入。
享元模式的角色
- 抽象享元(Flyweight)类:通常是一个接口或抽象类,在抽象享元类声明了具体享元类的公共方法,这些公共方法向外界提供了享元对象的内部状态的数据,以及供外界处理外部状态的数据。内部状态相关数据通常设计为成员变量,外部状态相关数据通常通过依赖注入的方式添加到享元类中。
- 具体享元(ConcreteFlyweight)类:实现抽象享元类,对应实例称为享元对象。具体享元类为内部状态提供了存储空间,通常可以使用单例模式来设计享元模式的内部状态,为每一个具体享元类提供一个唯一的享元对象。
- 非共享具体享元类(UnsharedConcreteFlyweight)类:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类设计为非共享具体享元类。当需要一个非具体享元类对象的时候,则直接创建对象即可。
- 享元工厂(FlyweightFactory)类:享元工厂类用于创建和管理享元对象,它面向抽象享元类编程,将各种具体享元类的对象存储在享元池中,享元池一般设计为一个键值对的集合(也可以设计为其他数据结构,根据具体需求制宜),一般结合工厂方法模式进行设计。当用户请求创建一个共享的具体享元对象时,首先会从享元池中获取,若存在则直接获取,若不存在则创建一个新的具体享元对象,并存放在享元池中。
享元模式的类型
- 单纯享元模式:所有具体享元类都是可以共享的,不存在非共享具体享元类。
- 复合享元模式:使用组合模式将单纯享元模式进行组合,形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成多个单纯享元对象,而单纯享元对象可以共享。
享元模式的优点
- 极大地减少了内存中对象的数量,降低内存消耗。这对有着大量相似对象的系统有着极其重要的意义
- 由于对象的创建和销毁减少了,因此一定程度上也能提高系统的性能,特别是创建和销毁开销较大的情况
享元模式的缺点
- 增加系统复杂性,享元模式的实现相对复杂,需要正确地划分内部状态和外部状态,并且需要一个享元工厂来管理对象(享元模式一般要配合工厂方法模式进行使用),增加了代码的复杂性和维护成本
- 外部状态的管理可能会较为复杂,因为外部状态不能共享,需要在对象使用过程中不断传递和处理,会导致代码的可维护性和可读性降低
享元模式的应用场景
- 游戏开发:游戏中有大量相似的游戏角色、道具、场景等等
- 图形系统:在很多图形绘制软件中,有很多相似的图形,如各种颜色的线条、各种形状的图形
- 文件处理系统:在文字处理器中,字符的字体、字号等属性可以作为内部状态,字符的位置可以作为外部状态。这样可以减少字符对象的创建数量,提高系统性能。
正文
许多人都玩过游戏,并且为了满足玩家的个性化需求,常常会出一些装备系统等。例如,王者荣耀里面有皮肤机制。王者荣耀里面的英雄,对于每个玩家来说,有一些基本信息都是相同的,那么就可以视为内部状态信息。但是皮肤是需要购买或者活动获取,每个玩家的拥有状态不一样,且每个人喜好的皮肤不一样,装扮状态也不一样,是会随玩家个性化需求变化而变化的,因此皮肤属于外部状态信息。下面就以王者荣耀的英雄信息作为案例,进行代码讲解。
定义一个抽象英雄类 AbstractHero.java。其中英雄的基本信息是共享的,可以定义一个英雄信息类 HeroInfo.java,但是皮肤信息是不共享的,可以简单定义一个字符串类型的 skin 记录不同玩家的英雄皮肤信息。
public abstract class AbstractHero { private HeroInfo info; private String skin = "原皮肤"; public AbstractHero(String name) { HeroFlyweightFactory heroFlyweightFactory = HeroFlyweightFactory.getInstance(); this.info = heroFlyweightFactory.getHeroInfo(name); } public HeroInfo getInfo() { return info; } public String getSkin() { return skin; } public void setSkin(String skin) { this.skin = skin; } } class HeroInfo { private String name;//名字 public HeroInfo(String name) { this.name = name; } public String getName() { return name; } }
定义一个具体英雄类 Hero.java
public class Hero extends AbstractHero { public Hero(String name) { super(name); } public void updateSkin(String newSkin) { System.out.println(getInfo().getName()+ "更换皮肤:" + getSkin() + " -> " + newSkin); setSkin(newSkin); } public void printCurrentSkin() { System.out.println(getInfo().getName() + "当前皮肤为 " + getSkin()); } }
定义一个英雄享元工厂类 HeroFlyweightFactory.java,这是享元模式的核心。
public class HeroFlyweightFactory { private static HeroFlyweightFactory instance; private Map<String, HeroInfo> heroes; private HeroFlyweightFactory() { heroes = new HashMap<String, HeroInfo>(); } //使用单例模式获取享元工厂的单例 public static HeroFlyweightFactory getInstance() { if (instance == null) { synchronized (HeroFlyweightFactory.class) { if (instance == null) { instance = new HeroFlyweightFactory(); } } } return instance; } //获取英雄:创建英雄可以采取更复杂的逻辑,配合工厂方法模式进行创建,为了编码简单,这边演示的是简单工厂方法 public HeroInfo getHeroInfo(String name) { if (heroes.containsKey(name)) {//若角色已经创建过 return heroes.get(name); } HeroInfo info = new HeroInfo(name); heroes.put(name, info); return info; } }
随后写一个客户端案例 Client.java
public class Client { public static void main(String[] args) { HeroFlyweightFactory heroFlyweightFactory = HeroFlyweightFactory.getInstance(); AbstractHero nvwa1 = new Hero("nvwa"); System.out.println(nvwa1.hashCode() + " " + nvwa1.getInfo().hashCode()); AbstractHero nvwa2 = new Hero("nvwa"); System.out.println(nvwa2.hashCode() + " " + nvwa2.getInfo().hashCode()); AbstractHero pangu = new Hero("pangu"); System.out.println(pangu.hashCode() + " " + pangu.getInfo().hashCode()); nvwa1.setSkin("尼罗河女神"); nvwa2.setSkin("遇见飞天"); System.out.println("nvwa1的皮肤是" + nvwa1.getSkin() + " nvwa2的皮肤是" + nvwa2.getSkin() + " pangu的皮肤是" + pangu.getSkin()); } }
运行效果截图如下:
合集:
结构型模式
分类:
设计模式与体系结构 / 设计模式
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 本地部署 DeepSeek:小白也能轻松搞定!
· 如何给本地部署的DeepSeek投喂数据,让他更懂你
· 从 Windows Forms 到微服务的经验教训
· 李飞飞的50美金比肩DeepSeek把CEO忽悠瘸了,倒霉的却是程序员
· 超详细,DeepSeek 接入PyCharm实现AI编程!(支持本地部署DeepSeek及官方Dee