• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【设计模式与体系结构】结构型模式-享元模式

简介

享元模式(Flyweight Pattern)是一种用于优化创建和使用对象的结构型设计模式。享元模式以共享的方式高效地支持大量细粒度的对象的重用,它的主要目的是通过共享对象来减少内存的使用和提高性能。在很多系统软件中,会创建大量相似的对象,这些对象可能只有部分属性不同,享元模式就是为了处理这种情况而出现的。

享元模式将对象的结构分为了内部状态和外部状态,内部状态是指对象可共享的部分,它存储在享元对象内部并且不会随着环境变化而变化,内部状态是享元对象能被共享的关键,外部状态是随环境而变化的部分,不能够被不同对象共享,它通常在对象的方法调用时作为参数传入。

享元模式的角色

  • 抽象享元(Flyweight)类:通常是一个接口或抽象类,在抽象享元类声明了具体享元类的公共方法,这些公共方法向外界提供了享元对象的内部状态的数据,以及供外界处理外部状态的数据。内部状态相关数据通常设计为成员变量,外部状态相关数据通常通过依赖注入的方式添加到享元类中。
  • 具体享元(ConcreteFlyweight)类:实现抽象享元类,对应实例称为享元对象。具体享元类为内部状态提供了存储空间,通常可以使用单例模式来设计享元模式的内部状态,为每一个具体享元类提供一个唯一的享元对象。
  • 非共享具体享元类(UnsharedConcreteFlyweight)类:并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类设计为非共享具体享元类。当需要一个非具体享元类对象的时候,则直接创建对象即可。
  • 享元工厂(FlyweightFactory)类:享元工厂类用于创建和管理享元对象,它面向抽象享元类编程,将各种具体享元类的对象存储在享元池中,享元池一般设计为一个键值对的集合(也可以设计为其他数据结构,根据具体需求制宜),一般结合工厂方法模式进行设计。当用户请求创建一个共享的具体享元对象时,首先会从享元池中获取,若存在则直接获取,若不存在则创建一个新的具体享元对象,并存放在享元池中。

享元模式的类型

  1. 单纯享元模式:所有具体享元类都是可以共享的,不存在非共享具体享元类。
  2. 复合享元模式:使用组合模式将单纯享元模式进行组合,形成复合享元对象,这样的复合享元对象本身不能共享,但是它们可以分解成多个单纯享元对象,而单纯享元对象可以共享。

享元模式的优点

  1. 极大地减少了内存中对象的数量,降低内存消耗。这对有着大量相似对象的系统有着极其重要的意义
  2. 由于对象的创建和销毁减少了,因此一定程度上也能提高系统的性能,特别是创建和销毁开销较大的情况

享元模式的缺点

  1. 增加系统复杂性,享元模式的实现相对复杂,需要正确地划分内部状态和外部状态,并且需要一个享元工厂来管理对象(享元模式一般要配合工厂方法模式进行使用),增加了代码的复杂性和维护成本
  2. 外部状态的管理可能会较为复杂,因为外部状态不能共享,需要在对象使用过程中不断传递和处理,会导致代码的可维护性和可读性降低

享元模式的应用场景

  1. 游戏开发:游戏中有大量相似的游戏角色、道具、场景等等
  2. 图形系统:在很多图形绘制软件中,有很多相似的图形,如各种颜色的线条、各种形状的图形
  3. 文件处理系统:在文字处理器中,字符的字体、字号等属性可以作为内部状态,字符的位置可以作为外部状态。这样可以减少字符对象的创建数量,提高系统性能。

正文

许多人都玩过游戏,并且为了满足玩家的个性化需求,常常会出一些装备系统等。例如,王者荣耀里面有皮肤机制。王者荣耀里面的英雄,对于每个玩家来说,有一些基本信息都是相同的,那么就可以视为内部状态信息。但是皮肤是需要购买或者活动获取,每个玩家的拥有状态不一样,且每个人喜好的皮肤不一样,装扮状态也不一样,是会随玩家个性化需求变化而变化的,因此皮肤属于外部状态信息。下面就以王者荣耀的英雄信息作为案例,进行代码讲解。

定义一个抽象英雄类 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());
	}
}

运行效果截图如下:

posted on 2025-01-21 21:19  RomanLin  阅读(32)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3