设计模式(十二)——享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)

基本介绍

核心

以共享的方式高效地支持大量细粒度对象的重用。

介绍

  1. 享元模式(Flyweight Pattern) 也叫 蝇量模式。 常用于系统底层开发,解决系统的性能问题。像数据库连接池,里面都是创建好的连接对象,在这些连接对象中有我们需要的则直接拿来用,避免重新创建,如果没有我们需要的,则创建一个。
  2. 享元模式能够解决重复对象的内存浪费的问题,当系统中有大量相似对象,需要缓冲池时。不需总是创建新对象,可以从缓冲池里拿。这样可以降低系统内存,同时提高效率。
  3. “享”就表示共享,“元”表示对象。

关键

享元对象能做到共享的关键是区分了内部状态和外部状态。

  • 内部状态:存储在享元对象内部,可以共享,不会随环境变化而改变。
  • 外部状态:对象得以依赖的一个标记,不可以共享,会随环境变化而改变。

举例:
比如围棋、五子棋、跳棋,它们都有大量的棋子对象,围棋和五子棋只有黑白两色,跳棋颜色多一点,所以棋子颜色就是棋子的内部状态;而各个棋子之间的差别就是位置的不同,当我们落子后,落子颜色是定的,但位置是变化的,所以棋子坐标就是棋子的外部状态。

享元模式的UML类图

在这里插入图片描述

角色分析

FlyweightFactory享元工厂类
创建池容器并管理享元对象, 同时提供从池中获取对象方法,享元池一般设计成键值对

FlyWeight抽象享元类
抽象的享元角色, 他是产品的抽象类(或接口), 同时定义出对象的外部状态和内部状态的接口或实现。

ConcreteFlyWeight具体享元类
具体的享元角色,为内部状态提供成员变量进行存储,是具体的产品类,实现抽象角色定义相关业务。

UnsharedConcreteFlyWeight非共享享元类
不可共享的角色,一般不会出现在享元工厂。

享元模式开发中应用的场景

  • 享元模式由于其共享的特性,可以在任何“池”中操作,比如:线程池、数据库连接池。
  • String类的设计也是享元模式

实例分析

围棋软件设计
在这里插入图片描述
围棋软件设计,主要考虑棋子的颜色和下棋的位置

传统方式实现

如果采用传统方式来实现,对每一步下的棋子,传入颜色和坐标集合。

代码实现

围棋

扫描二维码关注公众号,回复: 10272984 查看本文章
public class Chess {
    private String color;
    private int x,y;

    public Chess ( String color, int x, int y ) {
        this.color = color;
        this.x = x;
        this.y = y;
    }

    public String getColor () {
        return color;
    }

    public void setColor ( String color ) {
        this.color = color;
    }

    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;
    }
}

客户端调用

public class Client {
    public static void main ( String[] args ) {
        //每一步下棋实例化一个对象
        Chess chess1=new Chess("白色",52,32);
        Chess chess2=new Chess("黑色",15,64);
    }
}

分析

问题

棋字的结构相似,拥有颜色、横坐标、纵坐标三个属性。但每一步棋都会实例化一个对象,棋字多了后,实力对象很多,造成服务器的资源浪费。

改进
  1. 整合棋字类,共享其相关的代码和数据,对于硬盘、内存、CPU、数据库空间等服务器资源都可以达成共享,减少服务器资源。
  2. 对于代码来说,由于是一份实例,维护和扩展都更加容易
  3. 可以使用享元模式 来解决

享元模式实现

思路

围棋理论上有 361 个空位可以放棋子,每盘棋都有可能有两三百个棋子对象产生,因为内存空间有限,一台服务器很难支持更多的玩家玩围棋游戏,如果用享元模式来处理棋子,那么棋子对象就可以减少到只有两个实例,这样就很好的解决了对象的开销问题。

内部状态和外部状态

内部状态:颜色,只有两种状态(白色和黑色),不随环境改变

外部状态:位置,棋字的位置随下棋者的思维动态发生改变

UML类图

代码实现

FlyWeight抽象享元类
public interface ChessFlyWeight {
	void setColor(String c);
	String getColor();
	/** 下棋 */
	void display(Coordinate c);
}
ConcreteFlyWeight具体享元类
public class ConcreteChess implements ChessFlyWeight {

	private String color;
	
	public ConcreteChess(String color) {
		super();
		this.color = color;
	}

	@Override
	public void display(Coordinate c) {
		System.out.println("棋子颜色:"+color);
		System.out.println("棋子位置:"+c.getX()+"----"+c.getY());
	}

	@Override
	public String getColor() {
		return color;
	}

	@Override
	public void setColor(String c) {
		this.color = c;
	}
	
}
UnsharedConcreteFlyWeight非共享享元类
public class Coordinate {
	private int x,y;

	public Coordinate(int x, int y) {
		super();
		this.x = x;
		this.y = y;
	}

	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;
	}
	
}
FlyweightFactory享元工厂类
public class ChessFlyWeightFactory {
	//享元池
	private static Map<String,ChessFlyWeight> map = new HashMap<>();
	
	public static ChessFlyWeight  getChess(String color){
		
		if(map.get(color)!=null){
			return map.get(color);
		}else{
			ChessFlyWeight cfw = new ConcreteChess(color);
			map.put(color, cfw);
			return cfw;
		}
		
	}
	
}
客户端调用
public class Client {
	public static void main(String[] args) {
		ChessFlyWeight chess1 = ChessFlyWeightFactory.getChess("黑色");
		ChessFlyWeight chess2 = ChessFlyWeightFactory.getChess("黑色");
		ChessFlyWeight chess3 = ChessFlyWeightFactory.getChess("白色");
		System.out.println(chess1);
		System.out.println(chess2);
		
		System.out.println("增加外部状态的处理===========");
		chess1.display(new Coordinate(10, 10));
		chess2.display(new Coordinate(20, 20));
	}
}
结果
com.atguigu.flyweight.test.improve.ConcreteChess@2503dbd3
com.atguigu.flyweight.test.improve.ConcreteChess@2503dbd3
com.atguigu.flyweight.test.improve.ConcreteChess@4b67cf4d
增加外部状态的处理===========
棋子颜色:黑色
棋子位置:10----10
棋子颜色:黑色
?棋子位置:20----20

由此得知,当创建同一个颜色的棋字为一个对象,所以整个系统,棋字对象只有两个(黑色棋字、白色棋字)。

享元模式的注意事项和细节

  • 系统中有大量对象,这些对象消耗大量内存,并且对象的状态大部分可以外部化时,我们就可以考虑选用享元模式
  • 用唯一标识码判断,如果在内存中有,则返回这个唯一标识码所标识的对象,用HashMap/HashTable 存储
  • 使用享元模式时,注意划分内部状态和外部状态,并且需要有一个工厂类加以控制
  • 享元模式经典的应用场景是需要缓冲池的场景,比如 String 常量池、数据库连接池

优点

  • 享元模式大大减少了对象的创建。
  • 相同或相似对象内存中只存一份,极大的节约资源,提高系统性能。
  • 外部状态相对独立,不影响内部状态。

缺点

  • 需要分离出内部状态和外部状态,而外部状态具有固化特性,不应该随着内部状态的改变而改变,这是我们使用享元模式需要注意的地方。
  • 模式较复杂,使程序逻辑复杂化。
  • 为了节省内存,共享了内部状态,分离出外部状态,而读取外部状态。
    使运行时间变长。用时间换取了空间。
发布了52 篇原创文章 · 获赞 68 · 访问量 7150

猜你喜欢

转载自blog.csdn.net/qq_42937522/article/details/105053623