享元模式(FlyWeight)
阿新 • • 發佈:2019-01-22
1. 模式概念
所謂享元模式就是以共享的方式支援大量細粒度物件
的複用。
在瞭解享元模式之前我們先要了解兩個概念:內部狀態(Internal State)、外部狀態(External State)。
- 內部狀態:儲存在享元物件內部,不隨外界環境改變而改變的共享部分。
- 外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態,享元物件的外部狀態由客戶端儲存,在需要用到的時候再傳入享元物件的內部。
內部狀態儲存於享元物件內部,而外部狀態則應該由客戶端來考慮。
2. 單純享元模式:
角色如下:
- 抽象享元角色(Flyweight):規定所有具體享元角色需要實現的方法
- 具體享元角色(ConcreteFlyweight): 實現抽象享元中的方法,為內部狀態提供儲存空間
- 享元工廠角色(FlyweightFactory): 負責建立和管理享元物件,保證享元物件被系統共享。
程式碼實現:
public interface Flyweight {
//name 表示外部狀態
public void operation(String name);
}
public class ConcreteFlyweight implements Flyweight {
//內部狀態
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String name) {
System.out.println("內部狀態:" + intrinsicState);
System.out.println("外部狀態:" + name);
}
}
/**
通過工廠來生成例項,傳入內部狀態生成不同的例項
快取的物件:key-內部狀態, value-具體享元物件
*/
public class FlyweightFactory {
private static HashMap<String, Flyweight> files = new HashMap<>();
public static Flyweight getInstance(String intrinsicState) {
Flyweight value = files.get(intrinsicState);
if(value == null) {
value = new ConcreteFlyweight(intrinsicState);
files.put(intrinsicState, value);
}
return value;
}
public static int getSize() {
return files.size();
}
}
//客戶端程式
public class Client {
public static void main(String[] args) {
Flyweight fly1 = FlyweightFactory.getInstance("土豆");
fly1.operation("我");
Flyweight fly2 = FlyweightFactory.getInstance("辣椒");
fly2.operation("他");
Flyweight fly3 = FlyweightFactory.getInstance("土豆");
fly3.operation("你");
System.out.println(fly1 == fly3);
System.out.println(FlyweightFactory.getSize());
}
}
3. 複合享元
在複合享元模式中,將一些單純享元物件複合,形成複合享元物件,這些複合享元物件是不可以共享的,但是它們可以分解成單純享元物件,這些單純享元物件可以共享。
原始碼:
public interface Flyweight {
/**
* @param name 表示外部狀態
*/
public void operation(String name);
}
//共享
public class ConcreteFlyweight implements Flyweight {
//內部狀態
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String name) {
System.out.println("點菜者:" + name);
System.out.println("菜名:" + intrinsicState);
}
}
//不共享
public class ConcreteCompositeFlyweight implements Flyweight {
private HashMap<String, Flyweight> map = new HashMap<>();
//新增單純享元物件
public void add(String state, Flyweight fly) {
map.put(state, fly);
}
@Override
public void operation(String name) {
Flyweight temp = null;
for(String key : map.keySet()) {
temp = map.get(key);
temp.operation(name);
}
}
}
//生產例項
public class FlyweightFactory {
private static HashMap<String, Flyweight> files = new HashMap<>();
public static Flyweight getInstance(String state) {
Flyweight fly = files.get(state);
if(fly == null) {
fly = new ConcreteFlyweight(state);
files.put(state, fly);
}
return fly;
}
public static Flyweight getInstance(List<String> states) {
ConcreteCompositeFlyweight compositeFlyweight = new ConcreteCompositeFlyweight();
for(String state : states) {
compositeFlyweight.add(state, getInstance(state));
}
return compositeFlyweight;
}
}
public class Client {
public static void main(String[] args) {
List<String> compositeState = new ArrayList<String>();
compositeState.add("辣椒炒肉");
compositeState.add("牛肉");
compositeState.add("雞肉");
compositeState.add("辣椒炒肉");
compositeState.add("牛肉");
Flyweight compositeFly1 = FlyweightFactory.getInstance(compositeState);
Flyweight compositeFly2 = FlyweightFactory.getInstance(compositeState);
compositeFly1.operation("我");//外蘊狀態是同一個
System.out.println();
compositeFly2.operation("你");
System.out.println("---------------------------------");
System.out.println("複合享元模式是否可以共享物件:" + (compositeFly1 == compositeFly2));
String state = "牛肉";
Flyweight fly1 = FlyweightFactory.getInstance(state);
Flyweight fly2 = FlyweightFactory.getInstance(state);
System.out.println("單純享元模式是否可以共享物件:" + (fly1 == fly2));
}
}
4. 研磨設計模式
問題描述:在系統當中,存在大量的細粒度物件,而且存在大量的重複資料,嚴重耗費記憶體,如何解決?
問題解答:享元模式
/***
* 描述授權資料的享元介面
*/
public interface Flyweight {
/**
* 判斷傳入的安全實體和許可權,是否和享元物件內部狀態匹配
* @param securityEntity 安全實體
* @param permit 許可權
* @return true表示匹配,false表示不匹配
*/
public boolean match(String securityEntity,String permit);
/**
* 為flyweight新增子flyweight物件
* @param f 被新增的子flyweight物件
*/
public void add(Flyweight f);
}
/**
* 封裝授權資料中重複出現部分的享元物件
*/
public class AuthorizationFlyweight implements Flyweight{
/**
* 內部狀態,安全實體
*/
private String securityEntity;
/**
* 內部狀態,許可權
*/
private String permit;
/**
* 構造方法,傳入狀態資料
* @param state 狀態資料,包含安全實體和許可權的資料,用","分隔
*/
public AuthorizationFlyweight(String state){
String ss[] = state.split(",");
securityEntity = ss[0];
permit = ss[1];
}
public String getSecurityEntity() {
return securityEntity;
}
public String getPermit() {
return permit;
}
public boolean match(String securityEntity, String permit) {
if(this.securityEntity.equals(securityEntity)
&& this.permit.equals(permit)){
return true;
}
return false;
}
public void add(Flyweight f) {
throw new UnsupportedOperationException("物件不支援這個功能");
}
}
/**
* 不需要共享的享元物件的實現,也是組合模式中的組合物件
*/
public class UnsharedConcreteFlyweight implements Flyweight{
/**
* 記錄每個組合物件所包含的子元件
*/
private List<Flyweight> list = new ArrayList<Flyweight>();
public void add(Flyweight f) {
list.add(f);
}
public boolean match(String securityEntity, String permit) {
for(Flyweight f : list){
//遞迴呼叫
if(f.match(securityEntity, permit)){
return true;
}
}
return false;
}
}
public class TestDB {
/**
* 用來存放單獨授權資料的值
*/
public static Collection<String> colDB = new ArrayList<String>();
/**
* 用來存放組合授權資料的值,key為組合資料的id,value為該組合包含的多條授權資料的值
*/
public static Map<String,String[]> mapDB = new HashMap<String,String[]>();
static{
//通過靜態塊來填充模擬的資料,增加一個標識來表明是否組合授權資料
colDB.add("張三,人員列表,檢視,1");
colDB.add("李四,人員列表,檢視,1");
colDB.add("李四,操作薪資資料,,2");
mapDB.put("操作薪資資料",new String[]{"薪資資料,檢視","薪資資料,修改"});
//增加更多的授權資料
for(int i=0;i<3;i++){
colDB.add("張三"+i+",人員列表,檢視,1");
}
}
}
public class FlyweightFactory {
private static FlyweightFactory factory = new FlyweightFactory();
private FlyweightFactory(){
}
public static FlyweightFactory getInstance(){
return factory;
}
/**
* 快取多個flyweight物件
*/
private Map<String,Flyweight> fsMap = new HashMap<String,Flyweight>();
/**
* 獲取key對應的享元物件
* @param key 獲取享元物件的key
* @return key對應的享元物件
*/
public Flyweight getFlyweight(String key) {
Flyweight f = fsMap.get(key);
//換一個更簡單點的寫法
if(f==null){
f = new AuthorizationFlyweight(key);
fsMap.put(key,f);
}
return f;
}
}
/**
* 安全管理,實現成單例
*/
public class SecurityMgr {
private static SecurityMgr securityMgr = new SecurityMgr();
private SecurityMgr(){
}
public static SecurityMgr getInstance(){
return securityMgr;
}
/**
* 在執行期間,用來存放登入人員對應的許可權,
* 在Web應用中,這些資料通常會存放到session中
*/
private Map<String,Collection<Flyweight>> map =
new HashMap<String,Collection<Flyweight>>();
/**
* 模擬登入的功能
* @param user 登入的使用者
*/
public void login(String user){
//登入的時候就需要把該使用者所擁有的許可權,從資料庫中取出來,放到快取中去
Collection<Flyweight> col = queryByUser(user);
map.put(user, col);
}
/**
* 判斷某使用者對某個安全實體是否擁有某許可權
* @param user 被檢測許可權的使用者
* @param securityEntity 安全實體
* @param permit 許可權
* @return true表示擁有相應許可權,false表示沒有相應許可權
*/
public boolean hasPermit(String user,String securityEntity,String permit){
Collection<Flyweight> col = map.get(user);
System.out.println("現在測試"+securityEntity+"的"+permit+"許可權,map.size="+map.size());
if(col==null || col.size()==0){
System.out.println(user+"沒有登入或是沒有被分配任何許可權");
return false;
}
for(Flyweight fm : col){
//輸出當前例項,看看是否同一個例項物件
System.out.println("fm=="+fm);
if(fm.match(securityEntity, permit)){
return true;
}
}
return false;
}
/**
* 從資料庫中獲取某人所擁有的許可權
* @param user 需要獲取所擁有的許可權的人員
* @return 某人所擁有的許可權
*/
private Collection<Flyweight> queryByUser(String user){
Collection<Flyweight> col = new ArrayList<Flyweight>();
for(String s : TestDB.colDB){
String ss[] = s.split(",");
if(ss[0].equals(user)){
Flyweight fm = null;
if(ss[3].equals("2")){
//表示是組合
fm = new UnsharedConcreteFlyweight();
//獲取需要組合的資料
String tempSs[] = TestDB.mapDB.get(ss[1]);
for(String tempS : tempSs){
Flyweight tempFm = FlyweightFactory.getInstance().getFlyweight(tempS);
//把這個物件加入到組合物件中
fm.add(tempFm);
}
}else{
fm = FlyweightFactory.getInstance().getFlyweight(ss[1]+","+ss[2]);
}
col.add(fm);
}
}
return col;
}
}
public class Client {
public static void main(String[] args) throws Exception{
//需要先登入,然後再判斷是否有許可權
SecurityMgr mgr = SecurityMgr.getInstance();
mgr.login("張三");
mgr.login("李四");
boolean f1 = mgr.hasPermit("張三","薪資資料","檢視");
boolean f2 = mgr.hasPermit("李四","薪資資料","檢視");
boolean f3 = mgr.hasPermit("李四","薪資資料","修改");
System.out.println("f1=="+f1);
System.out.println("f2=="+f2);
System.out.println("f3=="+f3);
for(int i=0;i<3;i++){
mgr.login("張三"+i);
mgr.hasPermit("張三"+i,"薪資資料","檢視");
}
}
}
5. 總結
1、享元模式可以極大地減少系統中物件的數量。
2、享元模式的核心在於享元工廠,它主要用來確保合理地共享享元物件。
3、內部狀態為不變部分,儲存於享元物件內部,而外部狀態是可變部分,它應當由客戶端來負責。