Loading... # 设计模式(二十三)——享元模式 ## 参考 > 大话设计模式  ——  程杰 著 ## 目录 [设计模式(一)——简单工厂模式](https://www.princelei.club/archives/67.html) [设计模式(二)——策略模式](https://www.princelei.club/archives/68.html) [设计模式(三)——设计原则](https://www.princelei.club/archives/116.html) [设计模式(四)——装饰模式](https://www.princelei.club/archives/117.html) [设计模式(五)——代理模式](https://www.princelei.club/archives/119.html) [设计模式(六)——工厂方法模式](https://www.princelei.club/archives/132.html) [设计模式(七)——原型模式](https://www.princelei.club/archives/133.html) [设计模式(八)——模板方法模式](https://www.princelei.club/archives/134.html) [设计模式(九)——外观模式](https://www.princelei.club/archives/135.html) [设计模式(十)——建造者模式](https://www.princelei.club/archives/136.html) [设计模式(十一)——观察者模式](https://www.princelei.club/archives/137.html) [设计模式(十二)——抽象工厂模式](https://www.princelei.club/archives/138.html) [设计模式(十三)——状态模式](https://www.princelei.club/archives/139.html) [设计模式(十四)——适配器模式](https://www.princelei.club/archives/140.html) [设计模式(十五)——备忘录模式](https://www.princelei.club/archives/141.html) [设计模式(十六)——组合模式](https://www.princelei.club/archives/147.html) [设计模式(十七)——迭代器模式](https://www.princelei.club/archives/148.html) [设计模式(十八)——单例模式](https://www.princelei.club/archives/157.html) [设计模式(十九)——桥接模式](https://www.princelei.club/archives/159.html) [设计模式(二十)——命令模式](https://www.princelei.club/archives/160.html) [设计模式(二十一)——职责链模式](https://www.princelei.club/archives/161.html) [设计模式(二十二)——中介者模式](https://www.princelei.club/archives/162.html) [设计模式(二十三)——享元模式](https://www.princelei.club/archives/163.html) [设计模式(二十四)——解释器模式](https://www.princelei.club/archives/173.html) [设计模式(二十五)——访问者模式](https://www.princelei.club/archives/174.html) ## 何为享元模式 - 享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。 ## 示例 享元模式设计围棋中的棋子,一盘围棋理论上有361个空位可以放棋子,那如果常规的面向对象方式编程,每盘棋都可能有两三百个棋子产生,一台服务器很难支持更多的玩家玩围棋游戏了,毕竟内存空间还是有限的。用享元模式处理棋子,棋子对象可以减少到只有两个实例(黑、白)。 ``` /** * 围棋棋子类:抽象享元类 */ public abstract class IgoChessman { public abstract String getColor(); public void display() { System.out.println("棋子颜色:" + this.getColor()); } } ``` ``` /** * //黑色棋子类:具体享元类 */ public class BlackIgoChessman extends IgoChessman { @Override public String getColor() { return "黑色"; } } ``` ``` /** * 白色棋子类:具体享元类 */ public class WhiteIgoChessman extends IgoChessman{ @Override public String getColor() { return "白色"; } } ``` ``` /** * 围棋棋子工厂类:享元工厂类 */ public class IgoChessmanFactory { private Map<String, IgoChessman> flyweights = new HashMap<>(); public IgoChessman getIgoChessman(String color) { if (!flyweights.containsKey(color)) { IgoChessman igoChessman = color.equals("w") ? new WhiteIgoChessman() : new BlackIgoChessman(); flyweights.put(color, igoChessman); } return flyweights.get(color); } } ``` ``` /** * 客户端 */ public class MainClass { public static void main(String[] args) { IgoChessman black1,black2,black3,white1,white2; //获取享元工厂对象 IgoChessmanFactory factory = new IgoChessmanFactory(); //通过享元工厂获取三颗黑子 black1 = factory.getIgoChessman("b"); black2 = factory.getIgoChessman("b"); black3 = factory.getIgoChessman("b"); System.out.println("判断两颗黑子是否相同:" + (black1==black2)); //通过享元工厂获取两颗白子 white1 = factory.getIgoChessman("w"); white2 = factory.getIgoChessman("w"); System.out.println("判断两颗白子是否相同:" + (white1==white2)); //显示棋子 black1.display(); black2.display(); black3.display(); white1.display(); white2.display(); } } ``` 输出: ``` 判断两颗黑子是否相同:true 判断两颗白子是否相同:true 棋子颜色:黑色 棋子颜色:黑色 棋子颜色:黑色 棋子颜色:白色 棋子颜色:白色 ```   虽然我们获取了三个黑子对象和两个白子对象,但是它们的内存地址相同,也就是说,它们实际上是同一个对象。黑色棋子和白色棋子可以共享,但是它们将显示在棋盘的不同位置,如何让相同的黑子或者白子能够多次重复显示且位于一个棋盘的不同地方? ## 内部状态与外部状态 享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将它们传递进来,就可以通过共享大幅度地减少单个实例的数目。 ## 带外部状态的解决方案 除了增加一个坐标类Coordinates以外,抽象享元类IgoChessman中的display()方法也将对应增加一个Coordinates类型的参数,用于在显示棋子时指定其坐标,Coordinates类和修改之后的IgoChessman类的代码如下所示: ``` /** * 坐标类 */ public class Coordinates { private int x; private int y; public Coordinates(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 abstract class IgoChessman { public abstract String getColor(); public void display(Coordinates coord){ System.out.println("棋子颜色:" + this.getColor() + ",棋子位置:" + coord.getX() + "," + coord.getY() ); } } ``` ``` /** * 客户端 */ public class MainClass { public static void main(String[] args) { IgoChessman black1,black2,black3,white1,white2; //获取享元工厂对象 IgoChessmanFactory factory = new IgoChessmanFactory(); //通过享元工厂获取三颗黑子 black1 = factory.getIgoChessman("b"); black2 = factory.getIgoChessman("b"); black3 = factory.getIgoChessman("b"); System.out.println("判断两颗黑子是否相同:" + (black1==black2)); //通过享元工厂获取两颗白子 white1 = factory.getIgoChessman("w"); white2 = factory.getIgoChessman("w"); System.out.println("判断两颗白子是否相同:" + (white1==white2)); //显示棋子,同时设置棋子的坐标位置 black1.display(new Coordinates(1,2)); black2.display(new Coordinates(3,4)); black3.display(new Coordinates(1,3)); white1.display(new Coordinates(2,5)); white2.display(new Coordinates(2,4)); } } ``` 输出: ``` 判断两颗黑子是否相同:true 判断两颗白子是否相同:true 棋子颜色:黑色,棋子位置:1,2 棋子颜色:黑色,棋子位置:3,4 棋子颜色:黑色,棋子位置:1,3 棋子颜色:白色,棋子位置:2,5 棋子颜色:白色,棋子位置:2,4 ``` 从输出结果可以看到,在每次调用display()方法时,都设置了不同的外部状态——坐标值,因此相同的棋子对象虽然具有相同的颜色,但是它们的坐标值不同,将显示在棋盘的不同位置。 ## 享元模式应用   如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;还有就是对象的大多数状态可以外部状态,如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。在实际中享元模式一般用于池设计,如:字符串常量池、数据库连接池等。   享元模式,可以运用共享技术有效地支持大量细粒度的对象,不过,使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。因此,应当在有足够多的对象实例可供共享时才值得使用享元模式。 Last modification:June 11th, 2020 at 06:20 pm © 允许规范转载