記錄一下自己了解的一些 設計模式 ,并盡量使用表達清楚的例子進行講解。
政策模式
政策模式應該是最基礎的一個設計模式,它是對行為的一個抽象。jdk中的comparator比較器就是一個使用政策設計模式的政策。
比如有一個student學生類,有name和age兩個屬性。如果有個需求需要列印學生名單,并按照字母順序排序,可以使用comparator接口并在内部使用name進行比較即可。
如果哪一天需要按照年齡進行排序,那麼隻需要修改comparator即可,也就是使用一個新的政策,其它完全不變。
工廠模式
工廠模式的意義在于對象的建立、管理可以使用工廠去管理,而不是建立者自身。最典型的工廠模式使用者就是spring,spring内部的容器就是一個工廠,所有的bean都由這個容器管理,包括它們的建立、銷毀、注入都被這個容器管理。
工廠模式分簡單工廠和抽象工廠。它們的差別在于抽象工廠抽象程度更高,把工廠也抽象成了一個接口,這樣可以再每添加一個新的對象的時候而不需要修改工廠的代碼。
比如有個repository接口,用于存儲資料,有databaserepository,cacherepository,filerepository分别在資料庫,緩存,檔案中存儲資料,定義如下:
public interface repository {
void save(object obj);
}
class databaserepository implements repository {
@override
public void save(object obj) {
system.out.println("save in database");
class cacherepository implements repository {
system.out.println("save in cache");
class filerepository implements repository {
system.out.println("save in file");
}
簡單工廠的使用
public class repositoryfactory {
public repository create(string type) {
repository repository = null;
switch (type) {
case "db":
repository = new databaserepository();
break;
case "cache":
repository = new cacherepository();
case "file":
repository = new filerepository();
return repository;
public static void main(string[] args) {
repositoryfactory factory = new repositoryfactory();
factory.create("db").save(new object());
factory.create("cache").save(new object());
factory.create("file").save(new object());
簡單工廠的弊端在于每添加一個新的repository,都必須修改repositoryfactory中的代碼
抽象工廠的使用
public interface repositoryfactoryprovider {
repository create();
class databaserepositoryfactory implements repositoryfactoryprovider {
public repository create() {
return new databaserepository();
class cacherepositoryfactory implements repositoryfactoryprovider {
return new cacherepository();
class filerepositoryfactory implements repositoryfactoryprovider {
return new filerepository();
抽象工廠的測試:
repositoryfactoryprovider dbprovider = new databaserepositoryfactory();
dbprovider.create().save(new object());
repositoryfactoryprovider cacheprovider = new cacherepositoryfactory();
cacheprovider.create().save(new object());
repositoryfactoryprovider fileprovider = new filerepositoryfactory();
fileprovider.create().save(new object());
抽象工廠把工廠也進行了抽象話,是以添加一個新的repository的話,隻需要新增一個repositoryfactory即可,原有代碼不需要修改。
裝飾者模式
裝飾者模式的作用就在于它可以在不改變原有類的基礎上動态地給類添加新的功能。之前寫過一篇 通過源碼分析mybatis的緩存 文章,mybatis中的query就是使用了裝飾者設計模式。
用一段簡單的代碼來模拟一下mybatis中query的實作原理:
@data
@allargsconstructor
@tostring
class result { // 查詢結果類,相當于一個domain
private object obj;
private string sql;
public interface query { // 查詢接口,有簡單查詢和緩存查詢
result query(string sql);
public class simplequery implements query { // 簡單查詢,相當于直接查詢資料庫,這裡直接傳回result,相當于是資料庫查詢的結果
public result query(string sql) {
return new result(new object(), sql);
public class cachequery implements query { // 緩存查詢,如果查詢相同的sql,不直接查詢資料庫,而是傳回map中存在的result
private query query;
private map cache = new hashmap<>();
public cachequery(query query) {
this.query = query;
if(cache.containskey(sql)) {
return cache.get(sql);
result result = query.query(sql);
cache.put(sql, result);
return result;
測試:
query simplequery = new simplequery();
system.out.println(simplequery.query("select * from t_student") == simplequery.query("select * from t_student")); // false
query cachequery = new cachequery(simplequery);
system.out.println(cachequery.query("select * from t_student") == cachequery.query("select * from t_student")); // true
這裡cachequery就是一個裝飾類,simplequery是一個被裝飾者。我們通過裝飾者設計模式動态地給simplequery添加了緩存功能,而不需要修改simplequery的代碼。
當然,裝飾者模式也有缺點,就是會存在太多的類。
如果我們需要添加一個過濾的查詢(sql中有敏感字的就直接傳回null,而不查詢資料庫),隻需要可以添加一個filterquery裝飾者即可:
public class filterquery implements query {
private list words = new arraylist<>();
public filterquery(query query) {
words.add("fuck");
words.add("sex");
public result query(string sql) {
for(string word : words) {
if(sql.contains(word)) return null;
return query.query(sql);
query filterquery = new filterquery(simplequery);
system.out.println(filterquery.query("select * from t_student where name = 'fuck'")); // null
system.out.println(filterquery.query("select * from t_student where name = 'format'")); // result(obj=java.lang.object@1b4fb997, sql=select * from t_student where name = 'format')
代理模式
代理模式的作用是使用一個代理類來代替原先類進行操作。比較常見的就是aop中就是使用代理模式完成事務的處理。
代理模式分靜态代理和動态代理,靜态代理的原理就是對目标對象進行封裝,最後調用目标對象的方法即可。
動态代理跟靜态代理的差別就是動态代理中的代理類是程式運作的時候生成的。spring中對于接口的代理使用jdk内置的proxy和invocationhandler實作,對于類的代理使用cglib完成。
以1個userservice為例,使用jdk自帶的代理模式完成計算方法調用時間的需求:
// userservice接口
public interface iuserservice {
void printall();
// userservice實作類
class userservice implements iuserservice {
public void printall() {
system.out.println("print all users");
// invocationhandler政策,這裡列印了方法調用前後的時間
class userinvocationhandler implements invocationhandler {
private iuserservice userservice;
public object invoke(object proxy, method method, object[] args) throws throwable {
system.out.println("start : " + system.currenttimemillis());
object result = method.invoke(userservice, args);
system.out.println("end : " + system.currenttimemillis());
iuserservice userservice = new userservice();
userinvocationhandler uih = new userinvocationhandler(userservice);
iuserservice proxy = (iuserservice) proxy.newproxyinstance(userservice.getclass().getclassloader(), new class[] {iuserservice.class}, uih);
proxy.printall(); // 列印出start : 1489665566456 print all users end : 1489665566457
組合模式
組合模式經常跟政策模式配合使用,用來組合所有的政策,并周遊這些政策找出滿足條件的政策。之前寫過一篇
springmvc關于json、xml自動轉換的原理研究
文章,裡面springmvc把傳回的傳回值映射給使用者的response做了一層抽象,封裝到了handlermethodreturnvaluehandler政策接口中。
在handlermethodreturnvaluehandlercomposite類中,使用存在的handlermethodreturnvaluehandler對傳回值進行處理,在handlermethodreturnvaluehandlercomposite内部的代碼如下:
// 政策集合
private final list returnvaluehandlers = new arraylist();
public void handlereturnvalue(object returnvalue, methodparameter returntype,
modelandviewcontainer mavcontainer, nativewebrequest webrequest) throws exception {
// 調用selecthandler方法
handlermethodreturnvaluehandler handler = selecthandler(returnvalue, returntype);
if (handler == null) {
throw new illegalargumentexception("unknown return value type: " + returntype.getparametertype().getname());
handler.handlereturnvalue(returnvalue, returntype, mavcontainer, webrequest); // 使用找到的handler進行處理
private handlermethodreturnvaluehandler selecthandler(object value, methodparameter returntype) {
boolean isasyncvalue = isasyncreturnvalue(value, returntype);
// 周遊存在的handlermethodreturnvaluehandler
for (handlermethodreturnvaluehandler handler : this.returnvaluehandlers) {
if (isasyncvalue && !(handler instanceof asynchandlermethodreturnvaluehandler)) {
continue;
if (handler.supportsreturntype(returntype)) { // 找到比對的handler
return handler;
return null;
模闆模式
跟政策模式類似,模闆模式會先定義好實作的邏輯步驟,但是具體的實作方式由子類完成,跟政策模式的差別就是模闆模式是有邏輯步驟的。比如要給院系裡的學生排序,并取出排名第一的學生。這裡就有2個步驟,分别是排序和取出第一名學生。
一段僞代碼:
public abstract class abstractstudentgetter {
public final student getstudent(list students) {
sort(students); // 第一步
if(!collectionutils.isempty(students)) {
return students.get(0); // 第二步
abstract public void sort(list students);
class agestudentgetter extends abstractstudentgetter { // 取出年紀最大的學生
public void sort(list students) {
students.sort(new comparator() {
@override
public int compare(student s1, student s2) {
return s2.getage() - s1.getage();
});
class namestudentgetter extends abstractstudentgetter { // 按照名字字母排序取出第一個學生
students.sort(new comparator() {
return s2.getname().compareto(s1.getname());
metricsobserable metricsobserable = new metricsobserable();
metricsobserable.addobserver(new admina());
metricsobserable.addobserver(new adminb());
metricsobserable.updatecounter("request-count", 100l);
觀察者設計模式
觀察者設計模式主要的使用場景在于一個對象變化之後,依賴該對象的對象會收到通知。典型的例子就是rss的訂閱,當訂閱了部落格的rss之後,當部落格更新之後,訂閱者就會收到新的訂閱資訊。
jdk内置提供了observable和observer,用來實作觀察者模式:
// 定義一個observable
public class metricsobserable extends observable {
private map countermap = new hashmap<>();
public void updatecounter(string key, long value) {
countermap.put(key, value);
setchanged();
notifyobservers(countermap);
// observer
public class admina implements observer {
public void update(observable o, object arg) {
system.out.println("admina: " + arg);
public class adminb implements observer {
system.out.println("adminb: " + arg);
metricsobserable metricsobserable = new metricsobserable();
metricsobserable.addobserver(new admina());
metricsobserable.addobserver(new adminb());
列印出:
adminb: {request-count=100}
admina: {request-count=100}
享元模式
線程池中會構造幾個核心線程用于處理,這些線程會去取阻塞隊列裡的任務然後進行執行。這些線程就是會被共享、且被重複使用的。因為線程的建立、銷毀、排程都是需要消耗資源的,沒有必要每次建立新的線程,而是共用一些線程。這就是享元模式的使用。類似的還有jdbc連接配接池,對象池等。
之前有一次面試被問到:
integer.valueof("1") == integer.valueof("1") // true還是false
當時回答的是false,後來翻了下integer的源碼發現integer裡面有個内部類integercache,用于緩存一些共用的integer。這個緩存的範圍可以在jvm啟動的時候進行設定。
其實後來想想也應該這麼做,我們沒有必要每次使用對象的時候都傳回新的對象,可以共享這些對象,因為新對象的建立都是需要消耗記憶體的。
擴充卡模式
擴充卡模式比較好了解。像生活中插線口的插頭有2個口的,也有3個口的。如果電腦的電源插口隻有3個口的,但是我們需要一個2個口的插口的話,這個時候就需要使用插座來外接這個3個口的插頭,插座上有2個口的插頭。
這個例子跟我們程式設計一樣,當使用者系統的接口跟我們系統内部的接口不一緻時,我們可以使用擴充卡來完成接口的轉換。
使用繼承的方式實作類的适配:
public class source {
public void method() {
system.out.println("source method");
interface targetable {
void method();
void newmethod();
class adapter extends source implements targetable {
public void newmethod() {
system.out.println("new method");
targetable targetable = new adapter();
targetable.method(); // source method
targetable.newmethod(); // new method
上述方式是用接口和繼承的方式實作擴充卡模式。當然我們也可以使用組合的方式實作(把source當成屬性放到adapter中)。
單例模式
單例模式比較好了解,spring就是典型的例子。被spring中的容器管理的對象都有對應的scope,配置成singleton說明這個對象就是單例,也就是在spring容器的生命周期中,這個類隻有1個執行個體。
java中單例模式的寫法也有好多種。比如懶漢式、餓漢式、内部類方式、枚舉方式等。
需要注意的如果使用dcl的話需要初始化過程,這篇 java記憶體模型之從jmm角度分析dcl 文章中說明了dcl的正确用法。
effectice java中推薦的單例方式寫法是使用枚舉類型的方式。
外觀模式
外觀模式用來包裝一組接口用于友善使用。 比如系統中分10個子產品,有個功能需要組合使用所有的子產品,這個時候就需要一個包裝類包裝這10個接口,然後進行業務邏輯的調用。
作者:佚名
來源:51cto