动机:在我们面向对象设计过程中,我们常常会面临着对象实例过多的问题,如果对象实例过多这将是我们系统性能提高的一个瓶颈。假设我们要设计一个星空场景,现在我们需要实例星星对象,我们可以实例每一颗星星,但随着我们实例星星对象增多整个场景就越来越慢了,如果你实例了1000+颗星星要你去维护,这可是一个吃力不讨好的工作。我们必须找到一个合适的方法解决以上问题
定义:享元模式(Flyweight),运用共享的技术有效地支持大量细粒度的对象。
结构图:
所涉及的角色:
抽象享元角色(Flyweight):此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口或抽象类。那些需要外部状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内部状态的话,必须负责为内部状态提供存储空间。享元对象的内部状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称做不可共享的享元对象。这个角色一般很少使用。
享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
内部状态与外部状态:在享元对象内部并且不会随着环境改变而改变的共享部分,可以称之为享元对象的内部状态,反之随着环境改变而改变的,不可共享的状态称之为外部状态。
典型的享元模式的例子为文书处理器中以图形结构来表示字符。一个做法是,每个字形有其字型外观, 字模 metrics, 和其它格式资讯,但这会使每个字符就耗用上千字节。取而代之的是,每个字符参照到一个共享字形物件,此物件会被其它有共同特质的字符所分享;只有每个字符(文件中或页面中)的位置才需要另外储存。
代码示例:
抽象享元:FlyCharacter
package com.jin.model.flyweight; public class FlyCharacter { // intrinsic state protected char symbol; protected int size; protected String font; public FlyCharacter(char symbol, int size, String font) { this.symbol = symbol; this.size = size; this.font = font; } public void display(Position position) { System.out.println("symbol: " + symbol + " size: " + size + " position: x " + position.getPos_x() + " y " + position.getPos_y()); } }
具体享元:CharacterA
package com.jin.model.flyweight; public class CharacterA extends FlyCharacter { public CharacterA() { super('A', 10, "宋体"); } }
具体享元:CharacterB
package com.jin.model.flyweight; public class CharacterB extends FlyCharacter { public CharacterB() { super('B', 11, "雅黑"); } }
具体享元:CharacterZ
package com.jin.model.flyweight; public class CharacterZ extends FlyCharacter { public CharacterZ() { super('Z', 12, "Arial"); } }
享元工厂:CharacterFactory
package com.jin.model.flyweight; import java.util.HashMap; import java.util.Map; public class CharacterFactory { // Keeps the character object by specifying key/value. private static Map<Character, FlyCharacter> charaters = new HashMap<Character, FlyCharacter>(); public static FlyCharacter getCharacter(Character key) { FlyCharacter character = null; if (charaters.containsKey(key)) { character = charaters.get(key); } else { try { character = (FlyCharacter) Class.forName( "com.jin.model.flyweight.Character" + key.toString().toUpperCase()).newInstance(); } catch (Exception e) { e.printStackTrace(); } charaters.put(key, character); } return character; } public static int getCount() { return charaters.size(); } }
外部状态:Position
package com.jin.model.flyweight; public class Position { private int pos_x; private int pos_y; public Position() { } public Position(int posX, int posY) { pos_x = posX; pos_y = posY; } public int getPos_x() { return pos_x; } public void setPos_x(int posX) { pos_x = posX; } public int getPos_y() { return pos_y; } public void setPos_y(int posY) { pos_y = posY; } }
客户端:Client
package com.jin.model.flyweight; import java.util.Random; public class Client { public static void main(String[] args) { FlyCharacter character; String text = "ABZABBZZ"; char[] letters = text.toCharArray(); Random random = new Random(); for (int i = 0; i < letters.length; i++) { character = CharacterFactory.getCharacter(letters[i]); character.display(new Position(random.nextInt(50), random .nextInt(50))); } System.out.println("总共创建了" + CharacterFactory.getCount() + "个字符对象"); } }
运行结果:
适用性:
Flyweight模式的有效性很大程度上取决于如何使用它以及在何处使用它。当以下情况都成立时使用Flyweight模式。
- 一个应用程序使用了大量的对象。
- 完全由于使用大量的对象,造成很大的存储开销。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
- 应用程序不依赖对象标识。
缺点:
- 享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
- 享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。