享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。
享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。主要解决在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
大话设计模式中程杰老师给出的定义是,享元模式:运用共享技术有效地支持大量细粒度的对象。
享元模式结构图:
结构图解析:
FlyweightFactory一个享元工厂,用来创建并管理Flyweight对象。它主要是用来确保合理的共享Flyweight,当用户请求一个Flyweight时,FlyweightFactory对象提供一个已创建的实例或者创建一个(如果不存在的话);
Flyweight是所有具体享元类的超类或接口,通过这个接口,Flyweight可以接受并作用于外部状态;
ConcreteFlyweight继承Flyweight超类或实现Flyweight接口,并为内部状态增加存储空间;
UnsharedConcreteFlyweight指那些不需要共享的Flyweight子类。因为Flyweight接口共享成为可能,但它并不强制共享。
示例代码:
package com.flyweight;
public abstract class Flyweight {
public abstract void operation(int extrinsicstate);
}
package com.flyweight;
public class ConcreteFlyweight extends Flyweight {
@Override
public void operation(int extrinsicstate) {
System.out.println("具体Flyweight:" + extrinsicstate);
}
}
package com.flyweight;
public class UnsharedConcreteFlyweight extends Flyweight {
@Override
public void operation(int extrinsicstate) {
System.out.println("不共享的的具体Flyweight:" + extrinsicstate);
}
}
package com.flyweight;
import java.util.Hashtable;
public class FlyweightFactory {
private Hashtable flyweights = new Hashtable();
//获取Flyweight实例对象,如果实例对象不存在就构建,存在直接返回
public Flyweight getFlyweight(String key){
if ((Flyweight) flyweights.get(key)== null) {
flyweights.put(key, new ConcreteFlyweight());
}
return (Flyweight) flyweights.get(key);
}
}
package com.flyweight;
public class FlyweightPatternDem {
public static void main(String[] args) {
int extrinsicstate = 22;//外部状态
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight flyweightA = flyweightFactory.getFlyweight("instanceA");
flyweightA.operation(--extrinsicstate);
Flyweight flyweightB = flyweightFactory.getFlyweight("instanceB");
flyweightB.operation(--extrinsicstate);
Flyweight flyweightC = flyweightFactory.getFlyweight("instanceC");
flyweightC.operation(--extrinsicstate);
Flyweight flyweightD = new UnsharedConcreteFlyweight();
flyweightD.operation(--extrinsicstate);
}
}
运行结果:
当我敲完以上代码的时候,我对于extrinsicstate这个外部状态很迷惑,什么是外部状态,既然有外部状态,那也就有内部状态咯。
内部状态与外部状态:
在享元对象内部并且不会随环境改变而改变的共享部分,可以称为是享元对象的内部状态,而随着环境改变而改变的、不可以共享的状态就是外部状态了。事实上,享元模式可以避免大量非常相似类的开销。在程序设计中,有时需要生成大量细粒度的类实例来表示数据。如果能发现这些实例除了几个参数外基本上都是相同的,有时就能接受大幅度地减少需要实例化的类的数量。如果能把那些参数移到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度地减少单个实例的数目。也就是说,享元模式Flyweight执行时所需的状态是有内部也可能有外部,内部状态存储于ConcreteFlyweight对象之中,而外部对象则应该考虑由客户端对象存储或计算,当调用Flyweight对象操作时,将该状态传递给它。
下面我们来看一个大话设计模式中给出的简单实例:
有一群类似的商家客户,需要提供一个网站,网站实现的功能有信息发布、产品展示、博客留言、论坛等,这些商家客户要求实现的功能都差不多,如果我们对于每一个商家客户要求的网站都去专门申请空间和数据库,然后把类似的代码都复制一遍,当商家客户很多的时候如果有bug或者是新的需求改动,维护量就太大了。我们怎么办呢,看一下使用享元模式如何来实现吧。
注意:一个网站可以被多个用户注册,我们不需要对每个注册的用户分别创建一个网站,只需要共用一套代码就行了,所以在该实例中用户类是网站的外部状态;而网站实现的功能是内部状态,也就是说,不管用户的数量如何变,网站的功能就这几个,不会随着用户数量的改变而产生较大的改变。
package com.flyweight;
/** * 用户类,用于网站的客户账号,是网站类的外部状态 * @author LMB * */
public class User {
private String name;
public User(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.flyweight;
/** * 网站抽象类 * @author LMB * */
public abstract class WebSite {
public abstract void setUser(User user);
}
package com.flyweight;
public class ConcreteWebSite extends WebSite {
private String name="";
public ConcreteWebSite(String name) {
this.name = name;
}
@Override
public void setUser(User user) {
System.out.println("网站分类:" + name + " 用户:" + user.getName());
}
}
package com.flyweight;
import java.util.Hashtable;
/** * 网站工厂类 * @author LMB * */
public class WebSiteFactory {
private Hashtable flyweights = new Hashtable();
//获得网站类型
public WebSite getWedSiteCategory(String key){
if ((WebSite) flyweights.get(key) == null) {
flyweights.put(key, new ConcreteWebSite(key));
}
return (WebSite) flyweights.get(key);
}
//获得网站分类总数
public int getWebSiteCount(){
return flyweights.size();
}
}
package com.flyweight;
public class WebSiteDemo {
public static void main(String[] args) {
WebSiteFactory webSiteFactory = new WebSiteFactory();
//使用网站工厂类去创建客户需要的网站
WebSite webSiteA = webSiteFactory.getWedSiteCategory("产品展示");//设置网站分类
webSiteA.setUser(new User("Tom"));//设置用户
WebSite webSiteB = webSiteFactory.getWedSiteCategory("信息发布");
webSiteB.setUser(new User("Jack"));
WebSite webSiteC = webSiteFactory.getWedSiteCategory("博客留言");
webSiteC.setUser(new User("Sarah"));
WebSite webSiteD = webSiteFactory.getWedSiteCategory("论坛");
webSiteD.setUser(new User("Jerry"));
System.out.println("共需要构建 " + webSiteFactory.getWebSiteCount() + " 种类型的网站");
}
}
享元模式的应用:
1、如果一个应用程序使用了大量的对象,而大量的这些对象造成了很大的存储开销时就应该考虑使用;
2、还有就是对象的大多数状态可以是外部状态(如以上实例中的网站类型),如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象,此时可以考虑使用享元模式。
优点:享元模式可以运用共享技术有效地支持大量细粒度的对象,大大减少对象的创建,降低系统的内存,使效率提高。
缺点:使用享元模式需要维护一个记录了系统已有的所有享元的列表,而这本身需要耗费资源,另外享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这是的程序的逻辑复杂。因此,应当有足够多的对象实例可供共享时才值得使用享元模式。
注意事项:
1、注意划分外部状态和内部状态,否则可能会引起线程安全问题。
2、这些类必须有一个工厂对象加以控制。