本文的主要內容:html
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
設計模式 | 適配器模式及典型應用java
點擊[閱讀原文]可訪問個人我的博客:laijianfeng.org數據庫
享元模式(Flyweight Pattern):運用共享技術有效地支持大量細粒度對象的複用。系統只使用少許的對象,而這些對象都很類似,狀態變化很小,能夠實現對象的屢次複用。因爲享元模式要求可以共享的對象必須是細粒度對象,所以它又稱爲輕量級模式,它是一種對象結構型模式。享元模式結構較爲複雜,通常結合工廠模式一塊兒使用。編程
Flyweight(抽象享元類):一般是一個接口或抽象類,在抽象享元類中聲明瞭具體享元類公共的方法,這些方法能夠向外界提供享元對象的內部數據(內部狀態),同時也能夠經過這些方法來設置外部數據(外部狀態)。設計模式
ConcreteFlyweight(具體享元類):它實現了抽象享元類,其實例稱爲享元對象;在具體享元類中爲內部狀態提供了存儲空間。一般咱們能夠結合單例模式來設計具體享元類,爲每個具體享元類提供惟一的享元對象。數組
UnsharedConcreteFlyweight(非共享具體享元類):並非全部的抽象享元類的子類都須要被共享,不能被共享的子類可設計爲非共享具體享元類;當須要一個非共享具體享元類的對象時能夠直接經過實例化建立。緩存
FlyweightFactory(享元工廠類):享元工廠類用於建立並管理享元對象,它針對抽象享元類編程,將各類類型的具體享元對象存儲在一個享元池中,享元池通常設計爲一個存儲「鍵值對」的集合(也能夠是其餘類型的集合),能夠結合工廠模式進行設計;當用戶請求一個具體享元對象時,享元工廠提供一個存儲在享元池中已建立的實例或者建立一個新的實例(若是不存在的話),返回新建立的實例並將其存儲在享元池中。安全
單純享元模式:在單純享元模式中,全部的具體享元類都是能夠共享的,不存在非共享具體享元類。
複合享元模式:將一些單純享元對象使用組合模式加以組合,還能夠造成複合享元對象,這樣的複合享元對象自己不能共享,可是它們能夠分解成單純享元對象,然後者則能夠共享bash
在享元模式中引入了享元工廠類,享元工廠類的做用在於提供一個用於存儲享元對象的享元池,當用戶須要對象時,首先從享元池中獲取,若是享元池中不存在,則建立一個新的享元對象返回給用戶,並在享元池中保存該新增對象。微信
典型的享元工廠類的代碼以下:
class FlyweightFactory {
//定義一個HashMap用於存儲享元對象,實現享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//若是對象存在,則直接從享元池獲取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//若是對象不存在,先建立一個新的對象添加到享元池中,而後返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
複製代碼
享元類的設計是享元模式的要點之一,在享元類中要將內部狀態和外部狀態分開處理,一般將內部狀態做爲享元類的成員變量,而外部狀態經過注入的方式添加到享元類中。
典型的享元類代碼以下所示:
class Flyweight {
//內部狀態intrinsicState做爲成員變量,同一個享元對象其內部狀態是一致的
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState=intrinsicState;
}
//外部狀態extrinsicState在使用時由外部設置,不保存在享元對象中,即便是同一個對象
public void operation(String extrinsicState) {
//......
}
}
複製代碼
享元模式通常的類圖以下
通常網盤對於相同的文件只保留一份,譬若有一個場景:當咱們上傳一部別人上傳過的電影,會發現很快就上傳完成了,實際上不是真的上傳,而是引用別人曾經上傳過的那部電影,這樣一能夠提升咱們的用戶體驗,二能夠節約存儲空間避免資源浪費
注意:這個場景是小編想的,與通常見到的例子不太同樣,小編其實不肯定是否是享元模式,請你們多多指教
首先定義一個工具類 HashUtil,計算內容的hash值(注:計算hash是從 www.cnblogs.com/oxgen/p/396… 處複製的)
public class HashUtil {
public static String computeHashId(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
// http://stackoverflow.com/questions/332079
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
複製代碼
資源類 Resource,至關於享元類的內部狀態
public class Resource {
private String hashId;
private int byteSize;
private String content;
public Resource(String content) {
this.content = content;
this.hashId = HashUtil.computeHashId(content); // 文件的hash值
this.byteSize = content.length();
}
// ....getter、setter、toString...
}
複製代碼
用戶的文件類 File,其中的 resource 爲內部狀態,owner和filename爲外部狀態
public class File {
protected String owner;
protected String filename;
protected Resource resource;
public File(String owner, String filename) {
this.owner = owner;
this.filename = filename;
}
public String fileMeta() {// 文件存儲到文件系統中須要的key
if (this.owner == null || filename == null || resource == null) {
return "未知文件";
}
return owner + "-" + filename + resource.getHashId();
}
public String display() {
return fileMeta() + ", 資源內容:" + getResource().toString();
}
// ....getter、setter、toString...
}
複製代碼
網盤類 PanServer,該類使用單例模式(在其餘例子中該類還使用工廠方法模式),在upload方法中根據所上傳的文件的hashId判斷是否已經有相同內容的文件存在,存在則引用,不存在才上傳該文件
public class PanServer {
private static PanServer panServer = new PanServer(); // 單例模式
private Map<String, Resource> resourceSystem; // 資源系統,至關於享元池
private Map<String, File> fileSystem; // 文件系統
public PanServer() {
resourceSystem = new HashMap<String, Resource>();
fileSystem = new HashMap<String, File>();
}
public static PanServer getInstance() {
return panServer;
}
public String upload(String username, LocalFile localFile) {
long startTime = System.currentTimeMillis();
File file = new File(username, localFile.getFilename());
String hashId = HashUtil.computeHashId(localFile.getContent()); // 計算文件hash值
System.out.println(username + " 上傳文件");
try {
if (resourceSystem.containsKey(hashId)) {
System.out.println(String.format("檢測到內容相同的文件《%s》,爲了節約空間,重用文件", localFile.getFilename()));
file.setResource(this.resourceSystem.get(hashId));
Thread.sleep(100);
} else {
System.out.println(String.format("文件《%s》上傳中....", localFile.getFilename()));
Resource newResource = new Resource(localFile.getContent());
file.setResource(newResource);
this.resourceSystem.put(newResource.getHashId(), newResource); // 將資源對象存儲到資源池中
Thread.sleep(3000); // 上傳文件須要耗費必定時間
}
} catch (Exception e) {
e.printStackTrace();
}
fileSystem.put(file.fileMeta(), file);
long endTime = System.currentTimeMillis();
System.out.println(String.format("文件上傳完成,共耗費 %s 毫秒\n", endTime - startTime));
return file.fileMeta();
}
public void download(String fileKey) {
File file = this.fileSystem.get(fileKey);
if (file == null) {
System.out.println("文件不存在");
} else {
System.out.println("下載文件:" + file.display());
}
// 轉爲 LocalFile 返回
}
}
複製代碼
客戶端和本地文件類
public class LocalFile {
private String filename;
private String content;
public LocalFile(String filename, String content) {
this.filename = filename;
this.content = content;
}
//...省略...
}
public class Test {
public static void main(String[] args) {
PanServer panServer = PanServer.getInstance();
String fileContent = "這是一個pdf文件《設計模式:從入門到放棄》";
LocalFile localFile1 = new LocalFile("小明的設計模式.pdf", fileContent);
String fikeKey1 = panServer.upload("小明", localFile1);
LocalFile localFile2 = new LocalFile("大明的設計模式.pdf", fileContent);
String fikeKey2 = panServer.upload("大明", localFile2);
panServer.download(fikeKey1);
panServer.download(fikeKey2);
}
}
複製代碼
輸出
小明 上傳文件
文件《小明的設計模式.pdf》上傳中....
文件上傳完成,共耗費 3077 毫秒
大明 上傳文件
檢測到內容相同的文件《大明的設計模式.pdf》,爲了節約空間,重用文件
文件上傳完成,共耗費 100 毫秒
下載文件:小明-小明的設計模式.pdf-f73ea50f00f87b42d1f2e4eb6b71d383, 資源內容:Resource {hashId='f73ea50f00f87b42d1f2e4eb6b71d383', byteSize=22, content='這是一個pdf文件《設計模式:從入門到放棄》'}
下載文件:大明-大明的設計模式.pdf-f73ea50f00f87b42d1f2e4eb6b71d383, 資源內容:Resource {hashId='f73ea50f00f87b42d1f2e4eb6b71d383', byteSize=22, content='這是一個pdf文件《設計模式:從入門到放棄》'}
複製代碼
小明和大明各自上傳了一份文件,文件的內容(內部狀態)是相同的,可是名稱(外部狀態)不一樣,因爲內部狀態相同沒有必要重複存儲,因此內部狀態之拷貝了一份
享元模式的主要優勢以下:
享元模式的主要缺點以下:
適用場景:
Java中將String類定義爲final(不可改變的),JVM中字符串通常保存在字符串常量池中,java會確保一個字符串在常量池中只有一個拷貝,這個字符串常量池在JDK6.0之前是位於常量池中,位於永久代,而在JDK7.0中,JVM將其從永久代拿出來放置於堆中。
咱們作一個測試:
public class Main {
public static void main(String[] args) {
String s1 = "hello";
String s2 = "hello";
String s3 = "he" + "llo";
String s4 = "hel" + new String("lo");
String s5 = new String("hello");
String s6 = s5.intern();
String s7 = "h";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1==s2);//true
System.out.println(s1==s3);//true
System.out.println(s1==s4);//false
System.out.println(s1==s9);//false
System.out.println(s4==s5);//false
System.out.println(s1==s6);//true
}
}
複製代碼
String類的final
修飾的,以字面量的形式建立String變量時,jvm會在編譯期間就把該字面量hello
放到字符串常量池中,由Java程序啓動的時候就已經加載到內存中了。這個字符串常量池的特色就是有且只有一份相同的字面量,若是有其它相同的字面量,jvm則返回這個字面量的引用,若是沒有相同的字面量,則在字符串常量池建立這個字面量並返回它的引用。
因爲s2指向的字面量hello
在常量池中已經存在了(s1先於s2),因而jvm就返回這個字面量綁定的引用,因此s1==s2
。
s3中字面量的拼接其實就是hello
,jvm在編譯期間就已經對它進行優化,因此s1和s3也是相等的。
s4中的new String("lo")
生成了兩個對象,lo
,new String("lo")
,lo
存在字符串常量池,new String("lo")
存在堆中,String s4 = "hel" + new String("lo")
實質上是兩個對象的相加,編譯器不會進行優化,相加的結果存在堆中,而s1存在字符串常量池中,固然不相等。s1==s9
的原理同樣。
s4==s5
兩個相加的結果都在堆中,不用說,確定不相等。
s1==s6
中,s5.intern()
方法能使一個位於堆中的字符串在運行期間動態地加入到字符串常量池中(字符串常量池的內容是程序啓動的時候就已經加載好了),若是字符串常量池中有該對象對應的字面量,則返回該字面量在字符串常量池中的引用,不然,建立複製一份該字面量到字符串常量池並返回它的引用。所以s1==s6
輸出true。
使用例子以下:
public static void main(String[] args) {
Integer i1 = 12 ;
Integer i2 = 12 ;
System.out.println(i1 == i2);
Integer b1 = 128 ;
Integer b2 = 128 ;
System.out.println(b1 == b2);
}
複製代碼
輸出是
true
false
複製代碼
爲何第一個是true,第二個是false? 反編譯後能夠發現 Integer b1 = 128;
實際變成了 Integer b1 = Integer.valueOf(128);
,因此咱們來看 Integer
中的 valueOf
方法的實現
public final class Integer extends Number implements Comparable<Integer> {
public static Integer valueOf(int var0) {
return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
}
//...省略...
}
複製代碼
IntegerCache 緩存類
//是Integer內部的私有靜態類,裏面的cache[]就是jdk事先緩存的Integer。
private static class IntegerCache {
static final int low = -128;//區間的最低值
static final int high;//區間的最高值,後面默認賦值爲127,也能夠用戶手動設置虛擬機參數
static final Integer cache[]; //緩存數組
static {
// high value may be configured by property
int h = 127;
//這裏能夠在運行時設置虛擬機參數來肯定h :-Djava.lang.Integer.IntegerCache.high=250
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {//用戶設置了
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);//雖然設置了可是仍是不能小於127
// 也不能超過最大值
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
//循環將區間的數賦值給cache[]數組
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
private IntegerCache() {}
}
複製代碼
能夠看到 Integer
默認先建立並緩存 -128 ~ 127
之間數的 Integer
對象,當調用 valueOf
時若是參數在 -128 ~ 127
之間則計算下標並從緩存中返回,不然建立一個新的 Integer
對象
public final class Long extends Number implements Comparable<Long> {
public static Long valueOf(long var0) {
return var0 >= -128L && var0 <= 127L ? Long.LongCache.cache[(int)var0 + 128] : new Long(var0);
}
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
//...
}
複製代碼
同理,Long
中也有緩存,不過不能指定緩存最大值
對象池化的基本思路是:將用過的對象保存起來,等下一次須要這種對象的時候,再拿出來重複使用,從而在必定程度上減小頻繁建立對象所形成的開銷。用於充當保存對象的「容器」的對象,被稱爲「對象池」(Object Pool,或簡稱Pool)
Apache Commons Pool實現了對象池的功能。定義了對象的生成、銷燬、激活、鈍化等操做及其狀態轉換,並提供幾個默認的對象池實現。
有幾個重要的對象:
PooledObject(池對象):用於封裝對象(如:線程、數據庫鏈接、TCP鏈接),將其包裹成可被池管理的對象。
PooledObjectFactory(池對象工廠):定義了操做PooledObject實例生命週期的一些方法,PooledObjectFactory必須實現線程安全。
Object Pool (對象池):Object Pool負責管理PooledObject,如:借出對象,返回對象,校驗對象,有多少激活對象,有多少空閒對象。
// 對象池
private final Map<S, PooledObject<S>> allObjects = new ConcurrentHashMap<S, PooledObject<S>>();
複製代碼
重要方法:
borrowObject:從池中借出一個對象。
returnObject:將一個對象返還給池。
因爲篇幅較長,後面會專門出一篇介紹並使用 Apache Commons Pool2
的文章,敬請期待
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
Java中String字符串常量池
Integer的享元模式解析
7種結構型模式之:享元模式(Flyweight)與數據庫鏈接池的原理
Apache commons-pool2-2.4.2源碼學習筆記
Apache Commons Pool2 源碼分析