Java设计模式-享元模式

一、简介

1.1 引入

一个类中的成员变量表明这个类所创建的对象具有的属性,而我们可能会使用程序的中某一个类创建多个对象,你们这些对象之间可能会存在一部分属性值是完全相同的。现在我们创建一个Car类,然后用这个类创建carA和carB两辆同型号的轿车他们的长宽高相同,但颜色和功率不同
在这里插入图片描述

我们创建的carA和carB两个实例对象,这两个对象长宽高完全相同,但他们占有不同的内存空间,所以我们用Car类实例化的对象越多,浪费的内存空间也就越多
因为他们有着不同的内存空间,那么他们就可能会修改自己的属性值,那么我们就不能保证相同型号的汽车的长宽高是一致的

我们能否考虑让Car类创建的所有实例对象共享相同的height,width和length属性呢?答案是可以的,我们可以将Car类在红的height,width和length封装到一个新的类中
在这里插入图片描述
这样Car类创建的若干个实例对象都只要给color和power分配不同的内存空间,让这些对象可以共享同一个carData对象
在这里插入图片描述

1.2 定义

享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复用。

  • 享元模式又称轻量级模式,它是一种对象结构型模式,一般会结合工厂模式一起使用
  • 享元模式要求能够共享的对象必须是细粒度对象
  • 系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用

外部状态和内部状态

享元模式的关键是使用一个享元对象为其他对象提供共享的状态,从而保证使用享元的对象不能更改享元的数据

享元:享元对象其实就是一个共享对象,"引入"中所提到的CarData类的实例就是一个享元对象

  • 内部状态:内部状态指的是对象共享出来的信息,由享元对象所维护,不会随环境的改变而改变(不变的、可以在许多其他对象中重复使用的数据的成员变量
  • 外部状态:外部状态随环境改变而改变、不可以共享的状态。外部状态在需要使用时通过客户端传入享元对象。外部状态必须由客户端保存。( 包含每个对象各自不同的数据的成员变量

二、模式原理

2.1 模式组成

  • Flyweight(抽象享元)
    一个接口或抽象类,声明了具体享元类的公共方法。这些方法既可以向外界提供享元对象的内部数据(内部状态),也可以通过将外部数据作为参数传递给内部数据
  • ConcreteFlyweight(具体享元)
    实现抽象享元类,该类的实例称为享元对象,简称享元。具体享元类的成员变量就是享元对象的内部状态
  • UnsharedConcreteFlyweight(非共享的具体享元)
    是不可共享的外部状态,它以参数的形式注入具体享元的相关方法
  • FlyweightFactory(享元工厂)
    享元工厂是一个类,该类负责创建和管理享元对象各种类型的具体享元对象存储在一个享元池中,当用户请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象

2.2 UML类图

在这里插入图片描述

三、实例

3.1 实例概况

以围棋为例,围棋分为黑子和白子,黑子和白字之间有着诸多相似的属性,除去颜色外,每颗棋子不同的是在棋盘上的位置。因此,我们可以分析出围棋对象的内部状态和外部状态
内部状态:比如说,棋子的颜色,大小,质量(近似相等),制造商等等
外部状态:棋子在棋盘中的位置

3.2 步骤

  • 步骤一:创建抽象享元类,定义抽象方法
//抽象享元:这是一个抽象类
public abstract class Flyweight {

	// 定义了一个抽象方法,用来输出棋子的颜色和坐标
	public abstract void show(Location loc);

}
  • 步骤二:定义两个具体享元类,重写父类的抽象方法
//具体享元类:白棋
public class WhiteChessman extends Flyweight {

	public String color = "white";

	public String getColor() {
		return color;
	}

	// 重写父类的抽象方法,将外部状态Location对象传给内部对象,一起输出
	public void show(Location loc) {
		// TODO Auto-generated method stub
		System.out.println(getColor() + ":" + loc.getLoc());
	}

}
//具体享元类:黑棋
public class BlackChessman extends Flyweight {
	public String color = "black";

	public String getColor() {
		return color;
	}

	// 重写父类的抽象方法,将外部状态Location对象传给内部对象,一起输出
	public void show(Location loc) {
		// TODO Auto-generated method stub
		System.out.println(getColor() + ":" + loc.getLoc());
	}

}
  • 步骤三:创建非共享的具体享元类,设置棋子的坐标
//非共享的具体享元类:设定棋子的坐标
public class Location {
	public int x;
	public int y;

	// 设定坐标
	public Location(int x, int y) {
		setX(x);
		setY(y);
	}

	// 获取棋子的坐标
	public String getLoc() {
		return "x:" + getX() + ",y:" + getY();
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
}
  • 步骤四:创建享元池,向池中添加两个具体享元实例
import java.util.HashMap;

//享元工厂了
public class FlyweightFactory {
	// HashMap用来存放不同的具体享元实例,每个具体享元实例只有一个,可以和单例模式配合使用
	public HashMap<String, Flyweight> flyweights = new HashMap<>();

	public FlyweightFactory() {
		// 直接向HashMap中添加两个具体享元实例
		flyweights.put("white", new WhiteChessman());
		flyweights.put("black", new BlackChessman());

	}

	// 根据传入的color,在HashMap中匹配对应颜色的棋子
	public Flyweight getFlyweight(String color) {

		return flyweights.get(color);
	}
}
  • 步骤五:调用享元工厂生产白棋和黑棋,不论生成多少白棋,在没有设置坐标前都是一样的
public class Client {
	public static void main(String[] args) {
		
		//生成一个享元工厂
		FlyweightFactory factory = new FlyweightFactory();
		
		
		//获取白棋
		Flyweight white1 = factory.getFlyweight("white");
		Flyweight white2 = factory.getFlyweight("white");
		white1.show(new Location(1,2));	//给白棋传一个坐标
		white2.show(new Location(2,4));	//给白棋传一个坐标
		//每个白棋如果除去坐标以后,他们是完全相同的
		System.out.println(white1.hashCode() == white2.hashCode());

		System.out.println("--------------");
		//获取黑棋
		Flyweight black1 = factory.getFlyweight("black");
		Flyweight black2 = factory.getFlyweight("black");
		black1.show(new Location(1,2));
		black2.show(new Location(3,5));
		//每个黑棋如果除去坐标以后,他们是完全相同的
		System.out.println(black1.hashCode() == black2.hashCode());
		
		System.out.println("--------------");
		//黑棋和白棋是不同的
		System.out.println(white1.hashCode() == black1.hashCode());

	}
}
//执行结果
white:x:1,y:2
white:x:2,y:4
true
--------------
black:x:1,y:2
black:x:3,y:5
true
--------------
false

3.3 UML类图

在这里插入图片描述

四、优缺点

4.1 优点

  • 使用享元可以节省内存的开销,每个具体享元类只需创建一个实例
  • 享元模式的具体享元可以使用方法的参数接受外部状态中的数据,但外部状态数据不会干扰到享元中的内部数据,使得享元对象可以在不同的环境中被共享

4.2 缺点

  • 享元模式使得系统变得复杂,因为需要分离出内部状态和外部状态,这使得程序的逻辑复杂化
  • 为了使对象可以共享,享元模式需要将享元对象的部分状态外部化,而读取外部状态将使得运行时间变长

五、应用场景

  • 一个系统中有大量的对象,这些对象之间部分属性本质上是相同的,这时可以用享元封装相同的部分
  • 对象的大部分状态都可以变为外部状态,可以将这些外部状态传入享元中
  • 在使用享元模式时需要维护一个存储享元对象的享元池,而这需要耗费一定的资源,因此,应当在需要多次重复使用享元对象时才值得使用享元模式

六、与其他模式的关系

与其他模式的关系

  • 你可以使用享元模式实现组合模式树的共享叶节点以节省内存
  • 享元展示了如何生成大量的小型对象, 外观模式则展示了如何用一个对象来代表整个子系统
  • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元就和单例模式类似了。 但这两个模式有两个根本性的不同
    • 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同
    • 单例对象可以是可变的。 享元对象是不可变的
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Java设计模式是一组经过实践验证的面向对象设计原则和模式,可以帮助开发人员解决常见的软件设计问题。下面是常见的23种设计模式: 1. 创建型模式(Creational Patterns): - 工厂方法模式(Factory Method Pattern) - 抽象工厂模式(Abstract Factory Pattern) - 单例模式(Singleton Pattern) - 原型模式(Prototype Pattern) - 建造者模式(Builder Pattern) 2. 结构型模式(Structural Patterns): - 适配器模式(Adapter Pattern) - 桥接模式(Bridge Pattern) - 组合模式(Composite Pattern) - 装饰器模式(Decorator Pattern) - 外观模式(Facade Pattern) - 享元模式(Flyweight Pattern) - 代理模式(Proxy Pattern) 3. 行为型模式(Behavioral Patterns): - 责任链模式(Chain of Responsibility Pattern) - 命令模式(Command Pattern) - 解释器模式(Interpreter Pattern) - 迭代器模式(Iterator Pattern) - 中介者模式(Mediator Pattern) - 备忘录模式(Memento Pattern) - 观察者模式(Observer Pattern) - 状态模式(State Pattern) - 策略模式(Strategy Pattern) - 模板方法模式(Template Method Pattern) - 访问者模式(Visitor Pattern) 4. 并发型模式(Concurrency Patterns): - 保护性暂停模式(Guarded Suspension Pattern) - 生产者-消费者模式(Producer-Consumer Pattern) - 读写锁模式(Read-Write Lock Pattern) - 信号量模式(Semaphore Pattern) - 线程池模式(Thread Pool Pattern) 这些设计模式可以根据问题的特点和需求来选择使用,它们提供了一些可复用的解决方案,有助于开发高质量、可维护且易于扩展的软件系统。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值