「補課」進行時:設計模式(21)——享元模式
1. 前文匯總
2. 享元模式
2.1 定義
享元模式(Flyweight Pattern)很簡單,它解決的需求也很直接,同時它也是池技術的重要實現方式,先看下它的定義:
Use sharing to support large numbers of fine-grained objects efficiently.(使用共享對象可有效地支持大量的細粒度的對象。)
2.2 通用類圖
- Flyweight 抽象享元角色:它是一個產品的抽象類, 同時定義出對象的外部狀態和內部狀態的接口或實現。
- ConcreteFlyweight 具體享元角色:具體的一個產品類, 實現抽象角色定義的業務。
- unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如線程安全) 不能夠使用共享技術的對象, 該對象一般不會出現在享元工廠中。
- FlyweightFactory 享元工廠:它的職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得對象的方法。
2.3 通用代碼
抽象享元角色:
public abstract class Flyweight {
// 內部狀態
private String intrinsic;
// 外部狀態
protected final String extrinsic;
// 要求享元角色必須接受外部狀態
protected Flyweight(String extrinsic) {
this.extrinsic = extrinsic;
}
// 定義業務操作
abstract void operate();
public String getIntrinsic() {
return intrinsic;
}
public void setIntrinsic(String intrinsic) {
this.intrinsic = intrinsic;
}
}
具體享元角色:
public class ConcreteFlyweight1 extends Flyweight{
protected ConcreteFlyweight1(String extrinsic) {
super(extrinsic);
}
@Override
void operate() {
}
}
public class ConcreteFlyweight2 extends Flyweight{
protected ConcreteFlyweight2(String extrinsic) {
super(extrinsic);
}
@Override
void operate() {
}
}
享元工廠:
public class FlyweightFactory {
// 定義一個池容器
private static HashMap<String,Flyweight> pool = new HashMap<>();
// 享元工廠
public static Flyweight getFlyweight(String Extrinsic) {
// 需要返回的對象
Flyweight flyweight = null;
// 在池中沒有該對象
if(pool.containsKey(Extrinsic)) {
flyweight = pool.get(Extrinsic);
} else {
// 根據外部狀態創建享元對象
flyweight = new ConcreteFlyweight1(Extrinsic);
// 放置到池中
pool.put(Extrinsic, flyweight);
}
return flyweight;
}
}
2.4 優缺點
享元模式是一個非常簡單的模式, 它可以大大減少應用程序創建的對象, 降低程序內存的佔用, 增強程序的性能, 但它同時也提高了系統複雜性, 需要分離出外部狀態和內部狀態, 而且外部狀態具有固化特性, 不應該隨內部狀態改變而改變, 否則導致系統的邏輯混亂。
3. 一個小例子
享元模式很簡單,上面的通用代碼其實就是一個很好的示例,類似於 Java 中的 String 常量池,沒有的對象創建後存在池中,若池中存在該對象則直接從池中取出。
我這裡還是再舉一個簡單的例子,比如接了我一個小型的外包項目,是做一個產品展示網站,後來他的朋友們也希望做這樣的網站,但要求都有些不同,我們當然不能直接複製粘貼再來一份,有人希望是視頻站,有人希望是圖文站等等,而且因為經費原因不能每個網站租用一個空間。
這種事情在生活中很長見,不過大多數情況都是直接 copy 一份代碼,再做做改動,但是在享元模式中,就不存在這種情況啦~~~
網站抽象類:
public abstract class WebSite {
abstract void use();
}
具體網站類:
public class ConcreteWebSite extends WebSite {
private String name;
public ConcreteWebSite(String name) {
this.name = name;
}
@Override
void use() {
System.out.println("網站分類:" + name);
}
}
網絡工廠類:
public class WebSiteFactory {
private HashMap<String, WebSite> pool = new HashMap<>();
//獲得網站分類
public WebSite getWebSiteCategory(String key) {
if(!pool.containsKey(key)) {
pool.put(key, new ConcreteWebSite(key));
}
return pool.get(key);
}
//獲得網站分類總數
public int getWebSiteCount() {
return pool.size();
}
}
Client 客戶端:
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite fx = factory.getWebSiteCategory("視頻站");
fx.use();
WebSite fy = factory.getWebSiteCategory("視頻站");
fy.use();
WebSite fz = factory.getWebSiteCategory("視頻站");
fz.use();
WebSite fa = factory.getWebSiteCategory("圖文站");
fa.use();
WebSite fb = factory.getWebSiteCategory("圖文站");
fb.use();
WebSite fc = factory.getWebSiteCategory("圖文站");
fc.use();
System.out.println("網站分類總數為:" + factory.getWebSiteCount());
}
}
執行結果:
網站分類:視頻站
網站分類:視頻站
網站分類:視頻站
網站分類:圖文站
網站分類:圖文站
網站分類:圖文站
網站分類總數為:2
可以看出,雖然我們做了 6 個網站,但網站分類只有 2 個。
這樣基本算是實現了享元模式的共享對象的目的,但是這裡實際上沒有體現對象間的不同。
我們再加入一個用戶類:
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然後對 WebSite
和 ConcreteWebSite
的 use()
方法進行修改,添加 User 參數:
public abstract class WebSite {
abstract void use(User user);
}
public class ConcreteWebSite extends WebSite {
private String name;
public ConcreteWebSite(String name) {
this.name = name;
}
@Override
void use(User user) {
System.out.println("網站分類:" + name + " 用戶:" + user.getName());
}
}
最後修改一下 Client 類:
public class Client {
public static void main(String[] args) {
WebSiteFactory factory = new WebSiteFactory();
WebSite fx = factory.getWebSiteCategory("視頻站");
fx.use(new User("tom"));
WebSite fy = factory.getWebSiteCategory("視頻站");
fy.use(new User("cat"));
WebSite fz = factory.getWebSiteCategory("視頻站");
fz.use(new User("nginx"));
WebSite fa = factory.getWebSiteCategory("圖文站");
fa.use(new User("apache"));
WebSite fb = factory.getWebSiteCategory("圖文站");
fb.use(new User("netty"));
WebSite fc = factory.getWebSiteCategory("圖文站");
fc.use(new User("jboss"));
System.out.println("網站分類總數為:" + factory.getWebSiteCount());
}
}
最終結果:
網站分類:視頻站 用戶:tom
網站分類:視頻站 用戶:cat
網站分類:視頻站 用戶:nginx
網站分類:圖文站 用戶:apache
網站分類:圖文站 用戶:netty
網站分類:圖文站 用戶:jboss
網站分類總數為:2
這樣就可以協調內部與外部狀態,哪怕接手了上千個網站的需求,只要要求相同或類似,實際開發代碼也就是分類的那幾種。