背景
面向对象可以非常方便的解决一些扩展性的问题,但是在这个过程中系统务必会产生一些类或者对象,如果系统中存在对象的个数过多时,将会导致系统的性能下降。对于这样的问题解决最简单直接的办法就是减少系统中对象的个数。
享元模式提供了一种解决方案,使用共享技术实现相同或者相似对象的重用。也就是说实现相同或者相似对象的代码共享。
定义
享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 也就是运行共享技术有效地支持大量细粒度对象的复用。系统使用少量对象,而且这些都比较相似,状态变化小,可以实现对象的多次复用。
在了解享元模式之前我们先要了解两个概念:内部状态、外部状态。
- 内部状态:在享元对象内部不随外界环境改变而改变的共享部分。
- 外部状态:随着环境的改变而改变,不能够共享的状态就是外部状态。
模式结构
抽象享元角色:
public abstract class FlyWeight { //内部状态 private String innerState; //外部状态 private String outerState; public FlyWeight(String innerState) { this.innerState = innerState; } public abstract void operate(); public String getOuterState() { return outerState; } public void setOuterState(String outerState) { this.outerState = outerState; } } 复制代码
具体享元角色:
public class ConcreteFlyWeight1 extends FlyWeight { public ConcreteFlyWeight1(String innerState) { super(innerState); } @Override public void operate() { System.out.println("具体享元角色1"); } } 复制代码
享元工厂:
public class FlyWeightFactory { private static Map<String, FlyWeight> pool = new HashMap<String, FlyWeight>(); public static FlyWeight getFlyweight(String innerState) { if(pool.get(innerState) != null) { return pool.get(innerState); } else { FlyWeight weight = new ConcreteFlyWeight1(innerState); pool.put(innerState, weight); return weight; } } } 复制代码
模式实现
场景:围棋的棋子, 如果用编程去实现的话,势必要new出很多实例,整盘棋结束时估计内存也快爆了。如果使用享元模式,把围棋的共性共性出来,一些外部状态则外部动态控制,那么这个效率才是最优的。比如共享的应该是这个棋子的形状、大小、图片,外部动态修改的应该是棋子的位置。那么,只要定义两个类,黑棋和白棋,而且工厂中或说整个系统中也就只维护两个对象,一个是黑棋一个是白棋。
外部状态UnSharedConcreteFlyWeight非共享享元类:
public class Coordinate { private int x, y; public Coordinate(int x, int y) { 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; } } 复制代码
棋子享元类:
public interface ChessFlyWeight { void setColor(String c); String getColor(); void display(Coordinate coordinate); } 复制代码
具体享元类:
public class ConcreteChess implements ChessFlyWeight { private String color; public ConcreteChess(String color) { super(); this.color = color; } @Override public void setColor(String c) { } @Override public String getColor() { return null; } @Override public void display(Coordinate c) { System.out.println("棋子颜色:"+color); System.out.println("棋子位置:"+c.getX()+"------"+c.getY()); } } 复制代码
工厂类:
public class ChessFlyWeightFactory { private static Map<String, ChessFlyWeight> map = new HashMap<String, ChessFlyWeight>(); 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("黑色"); System.out.println(chess1 == chess2); System.out.println("增加外部状态的处理============"); chess1.display(new Coordinate(10,10)); chess2.display(new Coordinate(20,20)); } } 复制代码
模式优缺点
优点
- 享元模式的优点在于它能够极大的减少系统中对象的个数。 -享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
- 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
- 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
模式使用场景
- 如果一个系统中存在大量的相同或者相似的对象,由于这类对象的大量使用,会造成系统内存的耗费,可以使用享元模式来减少系统中对象的数量。
- 对象的大部分状态都可以外部化,可以将这些外部状态传入对象中。
- 需要缓冲池的场景。
应用
- 在Java中,String类中用了享元模式。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a="abc",其中"abc"就是一个字符串常量。
- 数据库连接池,线程池等即是用享元模式的应用。