「補課」進行時:設計模式(21)——享元模式
阿新 • • 發佈:2020-12-24
![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg)
## 1. 前文彙總
[「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f/)
## 2. 享元模式
### 2.1 定義
享元模式(Flyweight Pattern)很簡單,它解決的需求也很直接,同時它也是池技術的重要實現方式,先看下它的定義:
Use sharing to support large numbers of fine-grained objects efficiently.(使用共享物件可有效地支援大量的細粒度的物件。)
### 2.2 通用類圖
![](https://cdn.geekdigging.com/DesignPatterns/21/Flyweight_UML.png)
- Flyweight 抽象享元角色:它是一個產品的抽象類, 同時定義出物件的外部狀態和內部狀態的介面或實現。
- ConcreteFlyweight 具體享元角色:具體的一個產品類, 實現抽象角色定義的業務。
- unsharedConcreteFlyweight 不可共享的享元角色:不存在外部狀態或者安全要求(如執行緒安全) 不能夠使用共享技術的物件, 該物件一般不會出現在享元工廠中。
- FlyweightFactory 享元工廠:它的職責非常簡單, 就是構造一個池容器, 同時提供從池中獲得物件的方法。
### 2.3 通用程式碼
抽象享元角色:
```java
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;
}
}
```
具體享元角色:
```java
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() {
}
}
```
享元工廠:
```java
public class FlyweightFactory {
// 定義一個池容器
private static HashMap 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 一份程式碼,再做做改動,但是在享元模式中,就不存在這種情況啦~~~
網站抽象類:
```java
public abstract class WebSite {
abstract void use();
}
```
具體網站類:
```java
public class ConcreteWebSite extends WebSite {
private String name;
public ConcreteWebSite(String name) {
this.name = name;
}
@Override
void use() {
System.out.println("網站分類:" + name);
}
}
```
網路工廠類:
```java
public class WebSiteFactory {
private HashMap 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 客戶端:
```java
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());
}
}
```
執行結果:
```java
網站分類:視訊站
網站分類:視訊站
網站分類:視訊站
網站分類:圖文站
網站分類:圖文站
網站分類:圖文站
網站分類總數為:2
```
可以看出,雖然我們做了 6 個網站,但網站分類只有 2 個。
這樣基本算是實現了享元模式的共享物件的目的,但是這裡實際上沒有體現物件間的不同。
我們再加入一個使用者類:
```java
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
然後對 `WebSite` 和 `ConcreteWebSite` 的 `use()` 方法進行修改,新增 User 引數:
```java
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 類:
```java
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());
}
}
```
最終結果:
```java
網站分類:視訊站 使用者:tom
網站分類:視訊站 使用者:cat
網站分類:視訊站 使用者:nginx
網站分類:圖文站 使用者:apache
網站分類:圖文站 使用者:netty
網站分類:圖文站 使用者:jboss
網站分類總數為:2
```
這樣就可以協調內部與外部狀態,哪怕接手了上千個網站的需求,只要要求相同或類似,實際開發程式碼也就是分類的那