简易理解设计模式之:享元模式——五子棋游戏例子
介绍:
享元模式是一种结构型模式。它的定义为:使用共享对象可有效地支持大量的细粒度的对象。
两个概念
• 内部状态:不会随环境的改变而改变,可以共享的相同内容
• 外部状态:随着环境的改变而改变,不能够共享的内容
上图所示,Hello World!
字符串就是内部状态,而外部状态就是各种颜色。客户端通过设置不同的外部状态可以使相同的对象具有不同特征。
通俗点讲,一个类有两个属性(成员变量),颜色和文本。文本的属性是固定好的永恒不变的,就相当于内部状态,颜色属性是可以变的,需要从调用的地方设置的,就是外部状态。
实际使用上,能够共享的内部状态是有限的,内部状态比较少的对象也称为细粒度对象。所以享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。
类图:
享元模式UML类图Flyweight(抽象享元角色):抽象类或接口,Flyweight可以接受并作用于外部状态。
ConcreteFlyweight(具体享元角色):实现Flyweight接口,并为内部状态增加存储空间。
UnSharedConcreteFlyweight(不可共享的享元角色):指那些不需要共享的Flyweight子类。不可共享的享元角色不需要从工厂中创建,直接new即可。
FlyweightFactory(享元工厂):提供一个已创建的Flyweight对象或创建一个Flyweight对象(如果不存在的话)。
用法:
• 系统中存在大量的相似对象
• 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关
• 需要缓冲池的对象
个人理解:
此模式核心是享元工厂类中用户需要对象时,首先从享元池中获取,如果享元池中不存在,则创建一个新的享元对象返回给用户。用于解决内存中对象太多时导致卡死的问题。
例子:
此模式的用法实在太多了。缓存的设计、池技术的设计不多不少都涉及到享元模式。例如Java中的String字符串常量池采用了享元模式,防止字符串对象的重复创建。
五子棋游戏设计例子
设计思路:在五子棋游戏的设计上,一个棋盘会有几十个黑棋和白棋,黑棋和白棋各自的图片资源(内部状态)是相同的,只是位置不同(外部状态),原则上可以用享元模式来解决这个问题。
1、创建抽象享元角色
public abstract class Chess {
protected int x;
protected int y;
public abstract void play(int x,int y);
}
这个角色接受和作用于外部状态,简单来说外部状态(位置x,y)是由客户端(Client)进行设置的。
2、创建白棋和黑棋(具体享元角色)
public class WhiteChess extends Chess {
@Override
public void play(int x, int y) {
this.x = x;
this.y = y;
System.out.println("○:(" + this.x + "," + this.y + ")");
}
}
public class BlackChess extends Chess {
@Override
public void play(int x, int y) {
this.x = x;
this.y = y;
System.out.println("●:(" + this.x + "," + this.y + ")");
}
}
继承Chess类,并为内部状态增加存储空间。说明一下,可以共享的内容就是"●"和"○",不管棋盘上有多少个棋子,这个属性是稳定不变的。
3、创建享元工厂
public class ChessFactory {
private static Chess whiteChess;
private static Chess blackChess;
public static Chess getChess(int key) {
if (key == 0) {
if (whiteChess == null) {
System.out.println("=====创建白棋对象======");
whiteChess = new WhiteChess();
}
return whiteChess;
} else {
if (blackChess == null) {
System.out.println("=====创建黑棋对象======");
blackChess = new BlackChess();
}
return blackChess;
}
}
}
享元工厂的职责就是处理生成对象。可以看到,有缓存的时候就读缓存的数据,没有的话就直接new一个对象出来。读到这里享元工厂的结构很容易联想到:单例模式和简单工厂模式,下面过一下它们的区别:
• 单例模式保证一个类只能有一个对象实例,避免系统中创建太多重复的类,目的是提供一个全局性的接口;而享元模式的享元工厂类可以有多个对象实例,将多个对象的访问统一管理,减少不会要额内存消耗,目的是处理对象。
• 简单工厂模式,创建对象方式都是通过new去创建,而享元模式如果对象存在就读缓存,不存在才new一下。
4、测试与实现
public class Client {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Chess c = ChessFactory.getChess(i % 2);
c.play(i, new Random().nextInt(15));
}
}
}
用随机数模拟一下生成。
=====创建白棋对象======
○:(0,5)
=====创建黑棋对象======
●:(1,12)
○:(2,5)
●:(3,6)
○:(4,14)
●:(5,8)
○:(6,7)
●:(7,12)
○:(8,7)
●:(9,0)
总结:
• 此模式也比较简单,也容易上手。通俗一点讲。其实就是把一个类的属性拆分成两部分:内部状态和外部状态,然后用缓存的方式去生成对象
• 实际上分离出内部状态和外部状态的过程逻辑可能复杂,设计上要慎重考虑
• UnsharedConcreteFlyweight角色在整个过程都不参与,其实可以忽略这个角色
感谢您的阅读~
推荐阅读
基础篇:
设计模式前篇之——UML类图必会知识点
设计模式前篇之——一起过一下面向对象的概念
创建型模式:
简易理解设计模式之:简单工厂模式——来试试接入支付功能
简易理解设计模式之:工厂方法模式——数据存储例子
简易理解设计模式之:抽象工厂模式——更换数据库例子
简易理解设计模式之:建造者模式——学习使用“链式调用”
简易理解设计模式之:原型模式——深、浅拷贝的概念
简易理解设计模式之:单例模式——单例模式的几种常用写法
结构型模式:
简易理解设计模式之:适配器模式——Android列表视图控件设计方式
简易理解设计模式之:桥接模式——穿衣服经典案例2
简易理解设计模式之:组合模式——实现View中的树状结构
简易理解设计模式之:装饰模式——穿衣服经典案例
简易理解设计模式之:外观模式——第三方SDK的帮助类
简易理解设计模式之:享元模式——五子棋游戏例子
简易理解设计模式之:代理模式——iOS视图控件设计方式
行为型模式:
简易理解设计模式之:策略模式——优化一下支付功能
简易理解设计模式之:模板方法模式——Android中的BaseActivity基类
简易理解设计模式之:观察者模式——监听与回调
简易理解设计模式之:状态模式——优化登录操作
简易理解设计模式之:备忘录模式——Word文档的工作原理
简易理解设计模式之:迭代器模式——遍历对象的好帮手
简易理解设计模式之:命令模式——实现命令的参数化配置
简易理解设计模式之:责任链模式——OA中请假流程示例
简易理解设计模式之:中介者模式——多人聊天室例子
简易理解设计模式之:解释器模式——语言和文法
简易理解设计模式之:访问者模式——员工考核例子