面向对象技术可以很好的解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致对象创建及垃圾回收的代价过高,造成性能下降等问题。享元模式通过共享相同或者相似的细粒度对象解决了这一类问题。
定义:
享元模式(FlyWeight),运用共享技术有效的支持大量细粒度的对象。
细粒度含义
- JAVA中的定义:细粒度模型,通俗的讲就是将业务模型中的对象加以细分,从而得到更科学合理的对象模型,直观的说就是划分出很多对象.
- 划分:所谓细粒度的划分就是在pojo类上的面向对象的划分,而不是在于表的划分上 例如:在三层结构中持久化层里面只做单纯的数据库操作
内部/外部状态
- 内部状态(内蕴状态) 是存储在享元对象内部,一般在构造时确定或通过setter设置,并且不会随环境改变而改变的状态,因此内部状态可以共享。
- 外部状态(外蕴状态) 是随环境改变而改变、不可以共享的状态。外部状态在需要使用时通过客户端传入享元对象。外部状态必须由客户端保存。
模式中的角色:
(1) FlyWeight-抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
(2) ConcreteFlyWeight-具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
(3) UnSharedConcreteFlyWeight-非共享 享元实现类
(4) FlyWeightFactory-享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
(5) 客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
角色关系UML:
网站共享为例子
不含有非共享对象
UML图如下:
java代码:
抽象享元接口
package demo27;
/**
*
* @ClassName: WebSite
* @Description:抽象网站
* @author cheng
* @date
public interface WebSite
/**
*
* @Title: use
* @Description:使用
*/
void
具体享元角色
package demo27;
/**
*
* @ClassName: CSDNWebSite
* @Description:CSDN网站
* @author cheng
* @date
public class CSDNWebSite implements WebSite
private String name;
public CSDNWebSite(String name) {
super();
this.name = name;
}
/**
* 复写
*/
@Override
public void use() {
System.out.println("网站分类:"
享元工厂
package demo27;
import java.util.HashMap;
import java.util.Map;
/**
*
* @ClassName: WebSiteFactory
* @Description:网站工厂
* @author cheng
* @date
public class WebSiteFactory
// 存放享元对象的容器
private Map<String, WebSite> siteMap = new HashMap<String, WebSite>();
/**
*
* @Title: getWebSite
* @Description:获取享元对象
* @param key
* @return
public WebSite getWebSite(String key) {
if (!siteMap.containsKey(key)) {
siteMap.put(key, new CSDNWebSite(key));
}
return siteMap.get(key);
}
/**
*
* @Title: getWebSiteCount
* @Description:获取享元对象的个数
* @return
public int getWebSiteCount() {
return
测试
package demo27;
/**
*
* @ClassName: ClientTest
* @Description:测试
* @author cheng
* @date
public class ClientTest
public static void main(String[] args) {
// 创建工厂
WebSiteFactory webSiteFactory = new WebSiteFactory();
// 获取享元对象
WebSite ws1 = webSiteFactory.getWebSite("博客");
ws1.use();
WebSite ws2 = webSiteFactory.getWebSite("博客");
ws2.use();
WebSite ws3 = webSiteFactory.getWebSite("头条");
ws3.use();
WebSite ws4 = webSiteFactory.getWebSite("商城");
ws4.use();
WebSite ws5 = webSiteFactory.getWebSite("商城");
ws5.use();
// 享元对象个数
运行结果
含有非共享对象
UML图如下:
java代码:
抽象享元接口
package demo28;
/**
*
* @ClassName: WebSite
* @Description:抽象网站
* @author cheng
* @date
public interface WebSite
/**
*
* @Title: use
* @Description:使用
*/
void
具体享元角色
package demo28;
/**
*
* @ClassName: CSDNWebSite
* @Description:CSDN网站
* @author cheng
* @date
public class CSDNWebSite implements WebSite
private String name;
public CSDNWebSite(String name) {
super();
this.name = name;
}
/**
* 复写
*/
@Override
public void use(User user) {
System.out.println("网站分类:" + name + "===============用户姓名:"
+ user.getName() + ";用户密码:"
非共享对象
package demo28;
/**
*
* @ClassName: User
* @Description: 用户
* @author chengrui
* @date
public class User
private String name;
private String password;
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String name, String password) {
super();
this.name = name;
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
享元工厂
package demo28;
import java.util.HashMap;
import java.util.Map;
/**
*
* @ClassName: WebSiteFactory
* @Description:网站工厂
* @author cheng
* @date
public class WebSiteFactory
// 存放享元对象的容器
private Map<String, WebSite> siteMap = new HashMap<String, WebSite>();
/**
*
* @Title: getWebSite
* @Description:获取享元对象
* @param key
* @return
public WebSite getWebSite(String key) {
if (!siteMap.containsKey(key)) {
siteMap.put(key, new CSDNWebSite(key));
}
return siteMap.get(key);
}
/**
*
* @Title: getWebSiteCount
* @Description:获取享元对象的个数
* @return
public int getWebSiteCount() {
return
测试
package demo28;
/**
*
* @ClassName: ClientTest
* @Description:测试
* @author cheng
* @date
public class ClientTest
public static void main(String[] args) {
// 创建工厂
WebSiteFactory webSiteFactory = new WebSiteFactory();
// 获取享元对象
WebSite ws1 = webSiteFactory.getWebSite("博客");
ws1.use(new User("张三", "123456"));
WebSite ws2 = webSiteFactory.getWebSite("博客");
ws2.use(new User("李四", "123456"));
WebSite ws3 = webSiteFactory.getWebSite("头条");
ws3.use(new User("王五", "123456"));
WebSite ws4 = webSiteFactory.getWebSite("商城");
ws4.use(new User("赵六", "123456"));
WebSite ws5 = webSiteFactory.getWebSite("商城");
ws5.use(new User("王八", "123456"));
// 享元对象个数
运行结果
优点
- 享元模式的外部状态相对独立,使得对象可以在不同的环境中被复用(共享对象可以适应不同的外部环境)
- 享元模式可共享相同或相似的细粒度对象,从而减少了内存消耗,同时降低了对象创建与垃圾回收的开销
缺点
- 外部状态由客户端保存,共享对象读取外部状态的开销可能比较大
- 享元模式要求将内部状态与外部状态分离,这使得程序的逻辑复杂化,同时也增加了状态维护成本
使用场景
如果一个应用程序使用了大量的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式。
例如,如果发现某个对象的生成了大量细粒度的实例,并且这些实例除了几个参数外基本是相同的,如果把那些共享参数移到类外面,在方法调用时将他们传递进来,就可以通过共享大幅度单个实例的数目。
例如围棋游戏中的棋子只有颜色和位置不同,颜色是内部状态,不会随环境改变而改变,位置是外部状态,会随环境改变而改变,所以有几种颜色(2)就创建几个享元对象就可以了,而不是有多少棋子(N)就创建多少个对象
N => 2
再例如扑克牌只有花色和数字不同,花色是内部状态,不会随环境改变而改变,此时可以将数字看成外部状态,以此来减少享元对象的创建,所以有几种花色(4)就创建几个享元对象就可以了,而不是有多少张(54)扑克牌就创建多少个对象
54 => 4
其实在Java中就存在这种类型的实例:String。
Java中将String类定义为final(不可改变的),JVM中字符串一般保存在字符串常量池中,这个字符串常量池在jdk 6.0以前是位于常量池中,位于永久代,而在JDK 7.0中,JVM将其从永久代拿出来放置于堆中。
我们使用如下代码定义的两个字符串指向的其实是同一个字符串常量池中的字符串值。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2);//true
如果我们以str1==str2进行比较的话所得结果为:true,因为str1和str2保存的是字符串常量池中的同一个字符串地址。这就类似于我们今天所讲述的享元模式,字符串一旦定义之后就可以被共享使用,因为他们是不可改变的,同时被多处调用也不会存在任何隐患。