当系统中存在大量相同或类似的对象时,享元模式是一种较好的解决方案,它经过共享技术实现相同或类似的细粒度对象的复用,从而节约内存空间。享元模式提供了一个享元池用于存储已经建立好的享元对象,并经过享元工厂类将享元对象提供给客户端使用。java
使用面向对象技术开发时,不少状况下须要在系统中增长类和对象的个数,而且这些对象有些是相同或类似的。当对象太多时,将致使运行代价太高,性能降低等问题。为了不系统中出现大量相同或类似的对象,享元模式经过共享技术实现相同或类似对象的重用,相同的对象都指向一个实例,存储这个实例的对象称为享元池。编程
系统中有些对象并不彻底相同,而只是类似,所以须要先找出这些对象的共同点,在享元类中封装这些共同的内容。不一样的内容能够经过外部应用程序来设置,而不进行共享,在享元模式中能够共享的相同内容称为内部状态,而那些须要外部环境设置的不能共享的内容称为外部状态。网络
在享元模式中一般会出现工厂模式,须要建立一个享元工厂来维护一个享元池,用于存储具备相同内部状态的享元对象。实际使用中,可以共享的内部状态是有限的,所以享元对象通常都设计为较小的对象,它所包含的内部状态较少,这种状态通常称为细粒度对象。享元模式的目的就是使用共享技术来实现大量细粒度对象的复用。性能
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少许的对象,而这些对象都很类似,状态变化很小,能够实现对象的屡次复用。因为享元模式要求可以共享的对象必须是细粒度对象,所以又称为轻量级模式。测试
Flyweight(抽象享元类)this
抽象享元类声明是一个接口,经过它能够接受并做用于外部状态。在抽象享元类定义了具体享元类公共的方法,这些方法能够向外界提供享元对象的内部数据(内部状态),同时也能够经过这些方法来设置外部数据(外部状态)设计
ConcreteFlyweight(具体享元类)3d
具体享元类实现了抽象享元接口,保存了内部状态,具体享元对象是能够共享的。能够结合单例模式来设计享元具体类,为每个具体享元类提供惟一的享元对象。code
UnsharedConcreteFlyweight(非共享具体享元类)对象
不能被共享的抽象享元类的子类被设计为非共享具体享元类。当须要一个非共享具体享元类的对象时能够直接经过实例化建立。在某些享元模式的层次结构中,非共享具体享元对象还能够将具体享元对象做为子节点。
FlyweightFactory(享元工厂类)
享元工厂类用于建立并管理享元对象,它针对抽象享元类编程,将各类类型的具体享元对象存储在一个享元池中。当用户请求一个具体享元对象时,享元工厂提供一个存储在享元池已建立的实例或者建立一个实例,返回该新建立的实例并将其存储在享元池中。
享元模式的核心在于享元工厂类,享元工厂类的做用在于提供一个用于存储享元对象的享元池。典型的享元工厂类代码以下:
public class FlyweightFactory { private HashMap flyweights = new HashMap(); public Flyweight getFlyweight(String key) { if (flyweights.containsKey(key)) { return (Flyweight) flyweights.get(key); } else { Flyweight fw = new ConcreteFlyweight(); flyweights.put(key, fw); return fw; } } }
享元对象能作到共享的关键是区份内部状态(internal state)和外部状态(external state)。下面简单对享元的内部状态和外部状态进行分析:
典型的享元类代码以下:
public class Flyweight { private String intrinsicState; public Flyweight(String intrinsicState) { this.intrinsicState = intrinsicState; } public void operation(String extrinsicState) { ... } }
不少网络设备都是支持共享的,如交换机、集线器等,多台计算机终端能够连接同一台网络设备,并经过该网络设备进行数据转换。可是分配给每个终端计算机的端口是不一样的,能够将端口从网络设备中抽取出来做为外部状态,须要时再设置。
抽象享元类 NetworkDevices
public interface NetworkDevice { public String getType(); public void use(Port port); // 用于设置外部状态 }
具体享元类 Switch
public class Switch implements NetworkDevice { private String type; public Switch(String type) { this.type = type; } public String getType() { return type; } public void use(Port port) { System.out.println("Linked by switch, type is" + this.type + ",port is" + port.getPort()); } }
具体享元类 Hub
public class Hub implements NetworkDevice { private String type; public Hub(String type) { this.type = type; } public String getType() { return type; } public void use(Port port) { System.out.println("Linked by Hub, type is" + this.type + ",port is" + port.getPort()); } }
端口类 Port
public class Port { String port; public Port(String port) { this.port = port; } public String getPort() { return port; } public void setPort(String port) { this.port = port; } }
享元工厂 DeviceFactory
public class DeviceFactory { private List<NetworkDevice> devices = new ArrayList<NetworkDevice>(); private int totalTerminal = 0; public DeviceFactory() { NetworkDevice nd1 = new Switch("Cisco-WS-C2950-24"); devices.add(nd1); NetworkDevice nd2 = new Hub("TP-LINK-HF8M"); devices.add(nd2); } public NetworkDevice getNetworkDevice(String type) { if (type.equalsIgnoreCase("cisco")) { totalTerminal++; return devices.get(0); } else if (type.equalsIgnoreCase("tp")) { totalTerminal++; return devices.get(1); } else { return null; } } public int getTotalDevice() { return devices.size(); } public int getTotalTerminal() { return totalTerminal; } }
客户测试类 Client
public class Client { public static void main(String[] args) { DeviceFactory df = new DeviceFactory(); NetworkDevice nd1 = df.getNetworkDevice("cisco"); nd1.use(new Port("1000")); NetworkDevice nd2 = df.getNetworkDevice("cisco"); nd2.use(new Port("1001")); NetworkDevice nd3 = df.getNetworkDevice("cisco"); nd3.use(new Port("1002")); NetworkDevice nd4 = df.getNetworkDevice("tp"); nd4.use(new Port("1003")); NetworkDevice nd5 = df.getNetworkDevice("tp"); nd5.use(new Port("1004")); System.out.println("Total Device: " + df.getTotalDevice()); System.out.println("Total Terminal: " + df.getTotalTerminal()); } }
在客户端代码中,在调用享元对象的 use() 方法时,传入了一个 Port 类型对象,在该对象中封装了端口号,做为共享网络设备的外部状态,同一个网络设备具备多个不一样的端口号。
从运行结果能够得知,在调用享元对象的 use() 方法时,因为设置了不一样的端口号,所以相同的享元对象虽然具备相同的内部状态 type,可是它们的外部状态 port 不一样。
优势以下:
缺点以下:
如下状况可使用享元模式:
即全部抽象享元类的子类均可以共享,不存在非共享具体享元类
将单纯享元模式与组合模式加以组合,能够造成复合享元对象。这样的复合享元对象自己不能共享,但它们能够分解成单纯享元对象,然后者能够共享。经过复合享元模式,能够确保复合享元类 CompositeConcreteFlyweight 中所包含的每一个单纯享元类 ConcreteFlyweight 都具备相同的外部状态,而这些单纯享元的内部状态每每不一样。