簡介
Use sharing to support large numbers of fine-grained objects efficiently.
使用共享對象可有效地支持大量的細(xì)粒度的對象。
享元模式(Flyweight)又稱為 輕量級模式,它是一種對象結(jié)構(gòu)型模式。
面向?qū)ο蠹夹g(shù)可以很好地解決一些靈活性或可擴(kuò)展性問題豆挽,但在很多情況下需要在系統(tǒng)中增加類和對象的個數(shù)吴侦。當(dāng)對象數(shù)量太多時魁淳,將導(dǎo)致運(yùn)行代價過高臊恋,帶來性能下降等問題。享元模式 正是為解決這一類問題而誕生的篡悟。
享元模式 是對象池的一種實(shí)現(xiàn)。類似于線程池匾寝,線程池可以避免不停的創(chuàng)建和銷毀多個對象搬葬,消耗性能。享元模式 也是為了減少內(nèi)存的使用艳悔,避免出現(xiàn)大量重復(fù)的創(chuàng)建銷毀對象的場景急凰。
享元模式 的宗旨是共享細(xì)粒度對象,將多個對同一對象的訪問集中起來猜年,不必為每個訪問者創(chuàng)建一個單獨(dú)的對象抡锈,以此來降低內(nèi)存的消耗疾忍。
享元模式 把一個對象的狀態(tài)分成內(nèi)部狀態(tài)和外部狀態(tài),內(nèi)部狀態(tài)即是不變的企孩,外部狀態(tài)是變化的锭碳;然后通過共享不變的部分,達(dá)到減少對象數(shù)量并節(jié)約內(nèi)存的目的勿璃。
享元模式 本質(zhì):緩存共享對象擒抛,降低內(nèi)存消耗
主要解決
當(dāng)系統(tǒng)中多處需要同一組信息時,可以把這些信息封裝到一個對象中补疑,然后對該對象進(jìn)行緩存歧沪,這樣,一個對象就可以提供給多處需要使用的地方莲组,避免大量同一對象的多次創(chuàng)建诊胞,消耗大量內(nèi)存空間。
享元模式 其實(shí)就是 工廠模式 的一個改進(jìn)機(jī)制锹杈,享元模式 同樣要求創(chuàng)建一個或一組對象撵孤,并且就是通過工廠方法生成對象的,只不過 享元模式 中為工廠方法增加了緩存這一功能竭望。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
- 享元模式 可以極大減少內(nèi)存中對象的數(shù)量邪码,使得相同對象或相似對象在內(nèi)存中只保存一份,降低內(nèi)存占用咬清,增強(qiáng)程序的性能闭专;
- 享元模式 的外部狀態(tài)相對獨(dú)立,而且不會影響其內(nèi)部狀態(tài)旧烧,從而使得享元對象可以在不同的環(huán)境中被共享影钉;
缺點(diǎn)
- 享元模式 使得系統(tǒng)更加復(fù)雜,需要分離出內(nèi)部狀態(tài)和外部狀態(tài)掘剪,這使得程序的邏輯復(fù)雜化平委;
- 為了使對象可以共享,享元模式 需要將享元對象的狀態(tài)外部化夺谁,而且外部狀態(tài)必須具備固化特性肆汹,不應(yīng)該隨內(nèi)部狀態(tài)改變而改變,否則會導(dǎo)致系統(tǒng)的邏輯混亂予权;
使用場景
- 系統(tǒng)中存在大量的相似對象昂勉;
- 細(xì)粒度的對象都具備較接近的外部狀態(tài),而且內(nèi)部狀態(tài)與環(huán)境無關(guān)扫腺,也就是說對象沒有特定身份岗照;
- 需要緩沖池的場景;
模式講解
首先來看下 享元模式 的通用 UML 類圖:
從 UML 類圖中,我們可以看到攒至,享元模式 主要包含三種角色:
- 抽象享元角色(Flyweight):享元對象抽象基類或者接口厚者,同時定義出對象的外部狀態(tài)和內(nèi)部狀態(tài)的接口或?qū)崿F(xiàn);
- 具體享元角色(ConcreteFlyweight):實(shí)現(xiàn)抽象角色定義的業(yè)務(wù)迫吐。該角色的內(nèi)部狀態(tài)處理應(yīng)該與環(huán)境無關(guān)库菲,不能出現(xiàn)會有一個操作改變內(nèi)部狀態(tài),同時修改了外部狀態(tài)志膀;
- 享元工廠(FlyweightFactory):負(fù)責(zé)管理享元對象池和創(chuàng)建享元對象熙宇;
以下是 享元模式 的通用代碼:
class Client {
public static void main(String[] args) {
IFlyweight flyweight1 = FlyweightFactory.getFlyweight("aa");
IFlyweight flyweight2 = FlyweightFactory.getFlyweight("bb");
flyweight1.operation("a");
flyweight2.operation("b");
}
// 抽象享元角色
interface IFlyweight {
void operation(String extrinsicState);
}
// 具體享元角色
static class ConcreteFlyweight implements IFlyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("Object address: " + System.identityHashCode(this));
System.out.println("IntrinsicState: " + this.intrinsicState);
System.out.println("ExtrinsicState: " + extrinsicState);
}
}
// 享元工廠
static class FlyweightFactory {
private static Map<String, IFlyweight> pool = new HashMap<>();
// 因?yàn)閮?nèi)部狀態(tài)具備不變性,因此作為緩存的鍵
public static IFlyweight getFlyweight(String intrinsicState) {
if (!pool.containsKey(intrinsicState)) {
IFlyweight flyweight = new ConcreteFlyweight(intrinsicState);
pool.put(intrinsicState, flyweight);
}
return pool.get(intrinsicState);
}
}
}
舉個例子
例子:我們知道溉浙,過年回家的時候很麻煩烫止,因?yàn)槲覀冃枰獡尩揭粡埢丶业幕疖嚻薄屍钡臅r候戳稽,我們肯定是要查詢下有沒有我們需要的票信息馆蠕,這里我們假設(shè)一張火車的信息包含:出發(fā)站,目的站惊奇,價格互躬,座位類別。現(xiàn)在要求編寫一個查詢火車票查詢偽代碼颂郎,可以通過出發(fā)站吼渡,目的站查到相關(guān)票的信息。
直接思路:例子要求通過出發(fā)站祖秒,目的站查詢火車票的相關(guān)信息,那么我們只需構(gòu)建出火車票類對象舟奠,然后提供一個查詢出發(fā)站竭缝,目的站的接口給到客戶進(jìn)行查詢即可;
具體代碼如下:
class Client {
public static void main(String[] args) {
ITicket ticket = TicketFactory.queryTicket("深圳北", "潮汕");
ticket.showInfo("硬座");
}
interface ITicket {
void showInfo(String bunk);
}
static class TrainTicket implements ITicket {
private String from;
private String to;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void showInfo(String bunk) {
int price = new Random().nextInt(500);
System.out.println(String.format("%s->%s:%s價格:%s 元", this.from, this.to, bunk, price));
}
}
static class TicketFactory {
public static ITicket queryTicket(String from, String to) {
return new TrainTicket(from, to);
}
}
}
分析:上面的代碼中沼瘫,客戶端進(jìn)行查詢時抬纸,系統(tǒng)通過TicketFactory
直接創(chuàng)建一個火車票對象,但是這樣做的話耿戚,當(dāng)某個瞬間如果有大量的用戶請求同一張票的信息時湿故,系統(tǒng)就會創(chuàng)建出大量該火車票對象,系統(tǒng)內(nèi)存壓力驟增膜蛔。而其實(shí)更好的做法應(yīng)該是緩存該票對象坛猪,然后復(fù)用提供給其他查詢請求,這樣一個對象就足以支撐數(shù)以千計(jì)的查詢請求皂股,對內(nèi)存完全無壓力墅茉,使用 享元模式 可以很好地解決這個問題;
具體代碼如下:只需對上面代碼的TicketFactory
進(jìn)行更改,增加緩存機(jī)制:
class Client {
public static void main(String[] args) {
ITicket ticket = TicketFactory.queryTicket("深圳北", "潮汕");
ticket.showInfo("硬座");
ticket = TicketFactory.queryTicket("深圳北", "潮汕");
ticket.showInfo("軟座");
ticket = TicketFactory.queryTicket("深圳北", "潮汕");
ticket.showInfo("硬臥");
}
...
...
static class TicketFactory {
private static Map<String, ITicket> sTicketPool = new HashMap<>();
// 側(cè)重于演示就斤,不考慮性能等問題
public static synchronized ITicket queryTicket(String from, String to) {
String key = from + "->" + to;
if (TicketFactory.sTicketPool.containsKey(key)) {
System.out.println("使用緩存 ==> " + key);
return TicketFactory.sTicketPool.get(key);
}
System.out.println("第一次查詢悍募,創(chuàng)建對象 ==> " + key);
ITicket ticket = new TrainTicket(from, to);
TicketFactory.sTicketPool.put(key, ticket);
return ticket;
}
}
}
運(yùn)行結(jié)果如下:
第一次查詢,創(chuàng)建對象 ==> 深圳北->潮汕
深圳北->潮汕:硬座價格:429 元
使用緩存 ==> 深圳北->潮汕
深圳北->潮汕:軟座價格:321 元
使用緩存 ==> 深圳北->潮汕
深圳北->潮汕:硬臥價格:481 元
可以看到洋机,除了第一次查詢創(chuàng)建對象后坠宴,后續(xù)查詢相同車次票信息都是使用緩存對象,無需創(chuàng)建新對象了绷旗。