??所謂享元模式就是運行共享技術有效地支持大量細粒度對象的復用。系統(tǒng)使用少量對象,而且這些都比較相似,狀態(tài)變化小,可以實現(xiàn)對象的多次復用。
??共享模式是支持大量細粒度對象的復用,所以享元模式要求能夠共享的對象必須是細粒度對象。
??在了解享元模式之前我們先要了解兩個概念:內(nèi)部狀態(tài)、外部狀態(tài)。
??內(nèi)部狀態(tài):在享元對象內(nèi)部不隨外界環(huán)境改變而改變的共享部分。
??外部狀態(tài):隨著環(huán)境的改變而改變,不能夠共享的狀態(tài)就是外部狀態(tài)。
??由于享元模式區(qū)分了內(nèi)部狀態(tài)和外部狀態(tài),所以我們可以通過設置不同的外部狀態(tài)使得相同的對象可以具備一些不同的特性,而內(nèi)部狀態(tài)設置為相同部分。在我們的程序設計過程中,我們可能會需要大量的細粒度對象來表示對象,如果這些對象除了幾個參數(shù)不同外其他部分都相同,這個時候我們就可以利用享元模式來大大減少應用程序當中的對象。如何利用享元模式呢?這里我們只需要將他們少部分的不同的部分當做參數(shù)移動到類實例的外部去,然后在方法調(diào)用的時候?qū)⑺麄儌鬟f過來就可以了。這里也就說明了一點:內(nèi)部狀態(tài)存儲于享元對象內(nèi)部,而外部狀態(tài)則應該由客戶端來考慮。
??享元模式結(jié)構(gòu):

??享元模式存在如下幾個角色:
??Flyweight:抽象享元類。所有具體享元類的超類或者接口,通過這個接口,F(xiàn)lyweight可以接受并作用于外部專題;
??ConcreteFlyweight:具體享元類。指定內(nèi)部狀態(tài),為內(nèi)部狀態(tài)增加存儲空間。
??UnsharedConcreteFlyweight:非共享具體享元類。指出那些不需要共享的Flyweight子類。
??FlyweightFactory:享元工廠類。用來創(chuàng)建并管理Flyweight對象,它主要用來確保合理地共享Flyweight,當用戶請求一個Flyweight時,F(xiàn)lyweightFactory就會提供一個已經(jīng)創(chuàng)建的Flyweight對象或者新建一個(如果不存在)。
??享元模式的核心在于享元工廠類,享元工廠類的作用在于提供一個用于存儲享元對象的享元池,用戶需要對象時,首先從享元池中獲取,如果享元池中不存在,則創(chuàng)建一個新的享元對象返回給用戶,并在享元池中保存該新增對象。
public class FlyweightFactory {
// 享元對象池
private HashMap<String, FlyWeight> flyWeights = new HashMap<String, FlyWeight>();
public FlyWeight getFlyWeight(String key) {
// 享元池中存在享元對象,則直接返回
if(flyWeights.containsKey(key)) {
return (FlyWeight) flyWeights.get(key);
} else {
// 享元池中不存在享元對象,并將它放到享元池中
FlyWeight fw = new ConcreteFlyweight();
flyWeights.put(key, fw);
return fw;
}
}
}
??實例場景:假如我們有一個繪圖的應用程序,通過它我們可以出繪制各種各樣的形狀、顏色的圖形,那么這里形狀和顏色就是內(nèi)部狀態(tài)了,通過享元模式我們就可以實現(xiàn)該屬性的共享了。另外在抽象出一個外部狀態(tài)即繪圖的用戶,也就是說繪圖的用戶對象是不可以共享的,但是當大量用戶繪制了相同屬性(享元對象內(nèi)部狀態(tài))的對象時,可以返回同一個引用,從而減少系統(tǒng)的對象數(shù)量。代碼實現(xiàn)如下:
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;
}
}
/**
* @Description: 抽象形狀類,只有一個繪制圖型的抽象方法,使用該方法需要傳遞一個用戶對象
* @author: zxt
* @time: 2018年7月9日 上午9:43:41
*/
public abstract class Shape {
public abstract void draw(User user);
}
/**
* @Description: 繪制圖形的具體類
* @author: zxt
* @time: 2018年7月9日 上午9:46:16
*/
public class Circle extends Shape {
// 享元類的內(nèi)部狀態(tài)
private String color;
public Circle(String color) {
this.color = color;
}
@Override
public void draw(User user) {
System.out.println("用戶:" + user.getName() + ",畫了一個 '" + color + "' 的圓形!");
}
}
public class CircleFactory {
// 享元對象池
private static HashMap<String, Shape> shapes = new HashMap<String, Shape>();
public static Shape getShape(String key) {
// 享元池中存在享元對象,則直接返回
if(shapes.containsKey(key)) {
return (Shape) shapes.get(key);
} else {
// 享元池中不存在享元對象,并將它放到享元池中
Shape shape = new Circle(key);
shapes.put(key, shape);
return shape;
}
}
public static int getShapesNum() {
return shapes.size();
}
}
public class Client {
public static void main(String[] args) {
Shape shape1 = CircleFactory.getShape("紅色");
shape1.draw(new User("張三"));
Shape shape2 = CircleFactory.getShape("灰色");
shape2.draw(new User("李四"));
Shape shape3 = CircleFactory.getShape("綠色");
shape3.draw(new User("王五"));
Shape shape4 = CircleFactory.getShape("紅色");
shape4.draw(new User("趙四"));
Shape shape5 = CircleFactory.getShape("灰色");
shape5.draw(new User("前乾"));
Shape shape6 = CircleFactory.getShape("灰色");
shape6.draw(new User("孫李"));
System.out.println("一共創(chuàng)建了:" + CircleFactory.getShapesNum() + " 個圖形對象!");
}
}

??模式優(yōu)缺點
??優(yōu)點
??1、享元模式的優(yōu)點在于它能夠極大的減少系統(tǒng)中對象的個數(shù)。
??2、享元模式由于使用了外部狀態(tài),外部狀態(tài)相對獨立,不會影響到內(nèi)部狀態(tài),所以享元模式使得享元對象能夠在不同的環(huán)境被共享。
??缺點
??1、由于享元模式需要區(qū)分外部狀態(tài)和內(nèi)部狀態(tài),使得應用程序在某種程度上來說更加復雜化了。
??2、為了使對象可以共享,享元模式需要將享元對象的狀態(tài)外部化,而讀取外部狀態(tài)使得運行時間變長。
??模式適用場景
??1、如果一個系統(tǒng)中存在大量的相同或者相似的對象,由于這類對象的大量使用,會造成系統(tǒng)內(nèi)存的耗費,可以使用享元模式來減少系統(tǒng)中對象的數(shù)量。
??2、對象的大部分狀態(tài)都可以外部化,可以將這些外部狀態(tài)傳入對象中。
??3、String對象的常量池,以及Integer等包裝類的緩存策略:Integer.valueOf(int i)等都使用了享元模式。
??模式總結(jié)
??1、享元模式可以極大地減少系統(tǒng)中對象的數(shù)量。但是它可能會引起系統(tǒng)的邏輯更加復雜化。
??2、享元模式的核心在于享元工廠,它主要用來確保合理地共享享元對象。
??3、內(nèi)部狀態(tài)為不變共享部分,存儲于享元對象內(nèi)部,而外部狀態(tài)是可變部分,它應當由客戶端來負責。