1. 程式人生 > >Java設計模式——享元模式(Flyweight)

Java設計模式——享元模式(Flyweight)

定義:物件結構型模式運用共享技術有效地支援大量細粒度的物件。

在面向物件程式設計中,有時候應用中建立的物件過多,導致儲存空間的不必要的浪費(一部分屬性是很多物件共享的,另一部分是每個物件根據自己的使用情況獨有的,但是每個物件都將所有的屬性全部進行建立,這樣即使是可共享的屬性,也都每個物件都有自己單獨的,就造成了資源的浪費)

舉個例子:

一個字元有顏色、使用位置兩物件性,

class char{
      private Color color;//顏色
      private  Position position;//位置
}

位置需要根據實際使用情況進行設定,屬於不可共享的,而顏色(假如系統只提供了紅色和藍色)屬於共享的,

如果不使用享元模式,假如有1000個字元,這樣就要建立1000個顏色物件和1000個位置物件,生成了1000個字元物件,好浪費

使用享元模式,將位置物件的屬性去掉,並建立一個方法

class char{
      private Color color;//顏色
      public void operation(Position position){}//位置
}

建立一個顏色工廠,只儲存了兩個顏色物件,使用以下程式碼進行建立字元

char c1=factory.getChar("紅色");
c.operation(position1);
char c2=factory.getChar("藍色");
c.operation(position2);
char c3=factory.getChar("紅色");
c.operation(position3);
....

這樣,1000個字元其實只建立了2個字元物件,位置在真正使用的時候作為引數傳入給字元的方法,相當於一個字元物件在使用時根據實際情況,不停的更換自己的位置

享元模式關鍵在於區分內蘊狀態(color)和外蘊狀態(position)

在享元物件內部並且不會隨環境改變而改變的共享部分為內蘊狀態,而隨環境改變而改變的、不可以共享的狀態就是外蘊狀態。

進入正題:

享元模式的結構分為單純享元模式和複合享元模式

單純享元模式:

(1) 抽象享元角色(Flyweight):為具體享元角色規定了必須實現的方法,也是每個物件獨有的屬性物件進入的入口(當然外蘊狀態不是必須的,如果需要的話就定義),以引數的形式通過此方法傳入。在java中可以由抽象類、介面來擔當。

(2) 具體享元角色(ConcreteFlyweight):實現抽象角色規定的方法,並定義共享物件的屬性 (3) 享元工廠角色(FlyweightFactory):負責建立和管理享元角色。要想達到共享的目的,這個角色的實現是關鍵 (4) 客戶端角色(Client):維護對所有享元物件的引用,而且還需要儲存對應的每個物件獨有的屬性、狀態
public interface Flyweight{
 public void operation( Out state );//必要的實現方法,Out為外部狀態,不是必須的,用來接收每個物件獨有的屬性(有的話)

}

public class ConcreteFlyweight implements Flyweight {
 private In state;//內部狀態
  public ConcreteFlyweight(In state){
      this.state=state;
  }
 public void operation( Out state ){
   //具體操作
 }
}

public class FlyweightFactory { //一個Flyweight記憶體池,儲存內部狀態
 //Flyweight pool
 private HashMap flyweights = new HashMap();
  public Flyweight getFlyweight( In state ) {
  Flyweight flyweight = (Flyweight) flyweights.get(state);
  if( flyweight == null ) {
   //產生新的ConcreteFlyweight
   flyweight = new ConcreteFlyweight(state);
   flyweights.put( state, flyweight );
  }
   return flyweight;
 }
}

//Client:

FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.getFlyweight( inState );

fly.operation(outState);

 適用性

1)一個應用程式使用大量相同或者相似的物件,造成很大的儲存開銷。

2)物件的大部分狀態都可以外部化,可以將這些外部狀態傳入物件中。

3)如果刪除物件的外部狀態,那麼可以用相對較少的共享物件取代很多組物件。

4) 應用程式不依賴於物件標識。由於Flyweight物件可以被共享,對於概念上明顯有別的物件,標識測試將返回真值。

5)使用享元模式需要維護一個儲存享元物件的享元池,而這需要耗費資源,因此,應當在多次重複使用享元物件時才值得使用享元模式

舉一個完整的例子結束: 咖啡屋系統

系統要求:

有一家咖啡屋,有不同口味的咖啡,顧客購買後,服務員會將咖啡送到顧客相應的桌位上

系統分析:

定義一個訂單物件,包含口味、購買顧客的桌位號兩個屬性,而口味是固定的幾種,不受環境的影響,可作為內部狀態,可被所有訂單共享,而桌位號是根據顧客來定的,受環境影響,可作為外部狀態,不可被共享,這樣就把內部狀態和外部狀態區分開,使用享元模式

系統設計:

interface Order{
     public void serve(Table table);
}
class Flavor implement Order{
    private String flavor;
    public Flavor(String flavor){
         this.flavor=flavor;
    }
    public void serve(Table table){
    System.out.printlnn("Serving table"+table.getNumber()+"with flavor"+flavor);
    }
}
class FlavorFactory{ 

    private HashMap flavors = new HashMap();
   
    public Order getOrder(String flavor) {
     Order order = (Order) flavors.get(flavor);
     if( order == null ) {
      order = new Flavor(flavor);
      flavors.put(flavor,order);
     }
    return order;
 }

}
class Table{
    private int number;
    public int getNumber(){return number};
    public void setNumber(int number){this.number=number};
}
public class Client{    
    FlavorFactory flavorFactory;  
    public static void main(Stirng[] args){          

        flavorFactory=new FlavorFactory();
        Order order1=flavorFactory.get("Black Coffee");

        Order order2=flavorFactory.get("Capucino");

        Order order3=flavorFactory.get("Black Coffee");

        Order order4=flavorFactory.get("Espresson");

        order1.serve(new Table(1));        

        order2.serve(new Table(2));

        order3.serve(new Table(3));

        order4.serve(new Table(4));

    }
}

(1) 抽象享元角色(Flyweight):Order

(2) 具體享元角色(ConcreteFlyweight):Flavor

(3) 享元工廠角色(FlyweightFactory):FlavorFactory
(4) 客戶端角色(Client):Client

複合享元模式:




       複合享元角色(UnsharedConcreteFlyweight),其所代表的物件是不可以共享的,但是一個複合享元物件可以分解成為多個本身是單純享元物件的組合,複合享元角色又稱做不可共享的享元物件
      複合享元物件是由單純享元物件通過複合而成,因此它提供了add()這樣的聚集管理方法。由於一個複合享元物件具有不同的聚集元素,這些聚集元素在符合享元物件被建立之後加入,這本身就意味著複合享元物件的狀態是會改變的,因此複合享元物件是不能共享的
     複合享元角色實現了抽象享元角色所規定的介面,也就是operation(),這個方法有一個參量,代表複合享元物件的外蘊狀態。一個複合享元物件的所有單純享元物件元素的外蘊狀態都是與複合享元物件的外蘊狀態相等的;而一個複合享元物件所含有的單純享元物件的內蘊狀態一般是不等的
public interface Flyweight{
 public void operation( Out state );//必要的實現方法,Out為外部狀態,不是必須的,用來接收每個物件獨有的屬性(有的話)

}

public class ConcreteFlyweight implements Flyweight {
 private In state;//內蘊狀態
  public ConcreteFlyweight(In state){
      this.state=state;
  }
 public void operation( Out state ){
   //具體操作
 }
}

public class UnsharedConcreteFlyweight extends Flyweight{
  private HashMap flies=new HashMap();
  private Flyweight flyweight;
  public UnsharedConcreteFlyweight(){}
  //增加一個新的單純享元物件到聚集中
  public void add(In state,Flyweight fly){
    files.put(state,fly);
  }
  public void operation(Out state){
    Flyweight fly=null;
    for(Iterator it=files.entrySet().iterator();it.hasNext()){
      Map.Entry e=(Map.Entry)it.next();
      fly=(Flyweight)e.getValue();
      fly.operation(state);
    }
  }
}

public class FlyweightFactory {
 //Flyweight pool
 private HashMap flyweights = new HashMap();
  //複合享元工廠
  public Flyweight getUnsharedFlyweight(Vector states){
    UnsharedConcreteFlyweight ucfly=new UnsharedConcreteFlyweight();
    int length=states.size();
    In state=null;
    for(int i=0;i<length;i++){
      state=new In(states.get(i));
      ucfly.add(state,this.getFlyweight(state));
    }
    return ucfly;
  }
 public Flyweight getFlyweight( In state ) {
  Flyweight flyweight = (Flyweight) flyweights.get(state);
  if( flyweight == null ) {
   //產生新的ConcreteFlyweight
   flyweight = new ConcreteFlyweight(state);
   flyweights.put( state, flyweight );
  }
   return flyweight;
 }
}

FlyweightFactory factory = new FlyweightFactory();
Flyweight fly = factory.getUnsharedFlyweight(states);
fly.operation(outState);

複合享元角色是單純享元 角色的聚集,所包含的單純享元角色,其外蘊狀態都是一致的,內蘊狀態不一致,
拿上面的咖啡屋系統舉例,當同一個桌號的客人點了多個風味的咖啡時,此時外蘊狀態是一致的,如果我們仍使用單純享元模式的話:
        Order order1=flavorFactory.get("Black Coffee");
        Order order2=flavorFactory.get("Capucino");
        Order order3=flavorFactory.get("Black Coffee");
        Order order4=flavorFactory.get("Espresson");
        order1.serve(new Table(1));        
        order2.serve(new Table(1));
        order3.serve(new Table(1));
        order4.serve(new Table(1));
寫起來很不方便,如果使用複合享元模式的話,就方便很多:
Vector flavors=new Vector();
flavors.add("Black Coffee");
flavors.add("Capucino");
flavors.add("Black Coffee");
flavors.add("Espresson");
Order order=flavorFactory.getComposite(flavors);
order.serve(new Table(1));