1. 程式人生 > >享元模式(FlyWeight)

享元模式(FlyWeight)

參考部落格

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、內部狀態為不變部分,儲存於享元物件內部,而外部狀態是可變部分,它應當由客戶端來負責。