記錄自己理解的一些設(shè)計模式
記錄一下自己理解的一些 設(shè)計模式 ,并盡量使用表達(dá)清楚的例子進(jìn)行講解。
策略模式
策略模式應(yīng)該是最基礎(chǔ)的一個設(shè)計模式,它是對行為的一個抽象。jdk中的Comparator比較器就是一個使用策略設(shè)計模式的策略。
比如有一個Student學(xué)生類,有name和age兩個屬性。如果有個需求需要打印學(xué)生名單,并按照字母順序排序,可以使用Comparator接口并在內(nèi)部使用name進(jìn)行比較即可。 如果哪一天需要按照年齡進(jìn)行排序,那么只需要修改Comparator即可,也就是使用一個新的策略,其它完全不變。
工廠模式
工廠模式的意義在于對象的創(chuàng)建、管理可以使用工廠去管理,而不是創(chuàng)建者自身。最典型的工廠模式使用者就是Spring,Spring內(nèi)部的容器就是一個工廠,所有的bean都由這個容器管理,包括它們的創(chuàng)建、銷毀、注入都被這個容器管理。
工廠模式分簡單工廠和抽象工廠。它們的區(qū)別在于抽象工廠抽象程度更高,把工廠也抽象成了一個接口,這樣可以再每添加一個新的對象的時候而不需要修改工廠的代碼。
比如有個Repository接口,用于存儲數(shù)據(jù),有DatabaseRepository,CacheRepository,F(xiàn)ileRepository分別在數(shù)據(jù)庫,緩存,文件中存儲數(shù)據(jù),定義如下:
- 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 {
 - @Override
 - public void save(Object obj) {
 - System.out.println("save in cache");
 - }
 - }
 - class FileRepository implements Repository {
 - @Override
 - public void save(Object obj) {
 - 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();
 - break;
 - case "file":
 - repository = new FileRepository();
 - break;
 - }
 - 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 {
 - @Override
 - public Repository create() {
 - return new DatabaseRepository();
 - }
 - }
 - class CacheRepositoryFactory implements RepositoryFactoryProvider {
 - @Override
 - public Repository create() {
 - return new CacheRepository();
 - }
 - }
 - class FileRepositoryFactory implements RepositoryFactoryProvider {
 - @Override
 - public Repository create() {
 - 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());
 
抽象工廠把工廠也進(jìn)行了抽象話,所以添加一個新的Repository的話,只需要新增一個RepositoryFactory即可,原有代碼不需要修改。
裝飾者模式
裝飾者模式的作用就在于它可以在不改變原有類的基礎(chǔ)上動態(tài)地給類添加新的功能。之前寫過一篇 通過源碼分析MyBatis的緩存 文章,mybatis中的query就是使用了裝飾者設(shè)計模式。
用一段簡單的代碼來模擬一下mybatis中query的實(shí)現(xiàn)原理:
- @Data
 - @AllArgsConstructor
 - @ToString
 - class Result { // 查詢結(jié)果類,相當(dāng)于一個domain
 - private Object obj;
 - private String sql;
 - }
 - public interface Query { // 查詢接口,有簡單查詢和緩存查詢
 - Result query(String sql);
 - }
 - public class SimpleQuery implements Query { // 簡單查詢,相當(dāng)于直接查詢數(shù)據(jù)庫,這里直接返回Result,相當(dāng)于是數(shù)據(jù)庫查詢的結(jié)果
 - @Override
 - public Result query(String sql) {
 - return new Result(new Object(), sql);
 - }
 - }
 - public class CacheQuery implements Query { // 緩存查詢,如果查詢相同的sql,不直接查詢數(shù)據(jù)庫,而是返回map中存在的Result
 - private Query query;
 - private Map cache = new HashMap<>();
 - public CacheQuery(Query query) {
 - this.query = query;
 - }
 - @Override
 - public Result query(String sql) {
 - 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是一個被裝飾者。我們通過裝飾者設(shè)計模式動態(tài)地給SimpleQuery添加了緩存功能,而不需要修改SimpleQuery的代碼。
當(dāng)然,裝飾者模式也有缺點(diǎn),就是會存在太多的類。
如果我們需要添加一個過濾的查詢(sql中有敏感字的就直接返回null,而不查詢數(shù)據(jù)庫),只需要可以添加一個FilterQuery裝飾者即可:
- public class FilterQuery implements Query {
 - private Query query;
 - private List words = new ArrayList<>();
 - public FilterQuery(Query query) {
 - this.query = query;
 - words.add("fuck");
 - words.add("sex");
 - }
 - @Override
 - 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')
 
代理模式
代理模式的作用是使用一個代理類來代替原先類進(jìn)行操作。比較常見的就是aop中就是使用代理模式完成事務(wù)的處理。
代理模式分靜態(tài)代理和動態(tài)代理,靜態(tài)代理的原理就是對目標(biāo)對象進(jìn)行封裝,***調(diào)用目標(biāo)對象的方法即可。
動態(tài)代理跟靜態(tài)代理的區(qū)別就是動態(tài)代理中的代理類是程序運(yùn)行的時候生成的。Spring中對于接口的代理使用jdk內(nèi)置的Proxy和InvocationHandler實(shí)現(xiàn),對于類的代理使用cglib完成。
以1個UserService為例,使用jdk自帶的代理模式完成計算方法調(diào)用時間的需求:
- // UserService接口
 - public interface IUserService {
 - void printAll();
 - }
 - // UserService實(shí)現(xiàn)類
 - class UserService implements IUserService {
 - @Override
 - public void printAll() {
 - System.out.println("print all users");
 - }
 - }
 - // InvocationHandler策略,這里打印了方法調(diào)用前后的時間
 - @AllArgsConstructor
 - class UserInvocationHandler implements InvocationHandler {
 - private IUserService userService;
 - @Override
 - 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());
 - return result;
 - }
 - }
 
測試:
- 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
 
組合模式
組合模式經(jīng)常跟策略模式配合使用,用來組合所有的策略,并遍歷這些策略找出滿足條件的策略。之前寫過一篇 SpringMVC關(guān)于json、xml自動轉(zhuǎn)換的原理研究 文章,里面springmvc把返回的返回值映射給用戶的response做了一層抽象,封裝到了HandlerMethodReturnValueHandler策略接口中。
在HandlerMethodReturnValueHandlerComposite類中,使用存在的HandlerMethodReturnValueHandler對返回值進(jìn)行處理,在HandlerMethodReturnValueHandlerComposite內(nèi)部的代碼如下:
- // 策略集合
 - private final List returnValueHandlers = new ArrayList();
 - @Override
 - public void handleReturnValue(Object returnValue, MethodParameter returnType,
 - ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
 - // 調(diào)用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進(jìn)行處理
 - }
 - 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;
 - }
 
模板模式
跟策略模式類似,模板模式會先定義好實(shí)現(xiàn)的邏輯步驟,但是具體的實(shí)現(xiàn)方式由子類完成,跟策略模式的區(qū)別就是模板模式是有邏輯步驟的。比如要給院系里的學(xué)生排序,并取出***的學(xué)生。這里就有2個步驟,分別是排序和取出***名學(xué)生。
一段偽代碼:
- public abstract class AbstractStudentGetter {
 - public final Student getStudent(List students) {
 - sort(students); // ***步
 - if(!CollectionUtils.isEmpty(students)) {
 - return students.get(0); // 第二步
 - }
 - return null;
 - }
 - abstract public void sort(List students);
 - }
 - class AgeStudentGetter extends AbstractStudentGetter { // 取出年紀(jì)***的學(xué)生
 - @Override
 - 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 { // 按照名字字母排序取出***個學(xué)生
 - @Override
 - public void sort(List students) {
 - students.sort(new Comparator() {
 - @Override
 - public int compare(Student s1, Student s2) {
 - return s2.getName().compareTo(s1.getName());
 - }
 - });
 - }
 - }
 
測試:
- MetricsObserable metricsObserable = new MetricsObserable();
 - metricsObserable.addObserver(new AdminA());
 - metricsObserable.addObserver(new AdminB());
 - metricsObserable.updateCounter("request-count", 100l);
 
觀察者設(shè)計模式
觀察者設(shè)計模式主要的使用場景在于一個對象變化之后,依賴該對象的對象會收到通知。典型的例子就是rss的訂閱,當(dāng)訂閱了博客的rss之后,當(dāng)博客更新之后,訂閱者就會收到新的訂閱信息。
jdk內(nèi)置提供了Observable和Observer,用來實(shí)現(xiàn)觀察者模式:
- // 定義一個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 {
 - @Override
 - public void update(Observable o, Object arg) {
 - System.out.println("adminA: " + arg);
 - }
 - }
 - public class AdminB implements Observer {
 - @Override
 - public void update(Observable o, Object arg) {
 - System.out.println("adminB: " + arg);
 - }
 - }
 
測試:
- MetricsObserable metricsObserable = new MetricsObserable();
 - metricsObserable.addObserver(new AdminA());
 - metricsObserable.addObserver(new AdminB());
 - metricsObserable.updateCounter("request-count", 100l);
 
打印出:
- adminB: {request-count=100}
 - adminA: {request-count=100}
 
享元模式
線程池中會構(gòu)造幾個核心線程用于處理,這些線程會去取阻塞隊列里的任務(wù)然后進(jìn)行執(zhí)行。這些線程就是會被共享、且被重復(fù)使用的。因?yàn)榫€程的創(chuàng)建、銷毀、調(diào)度都是需要消耗資源的,沒有必要每次創(chuàng)建新的線程,而是共用一些線程。這就是享元模式的使用。類似的還有jdbc連接池,對象池等。
之前有一次面試被問到:
- Integer.valueOf("1") == Integer.valueOf("1") // true還是false
 
當(dāng)時回答的是false,后來翻了下Integer的源碼發(fā)現(xiàn)Integer里面有個內(nèi)部類IntegerCache,用于緩存一些共用的Integer。這個緩存的范圍可以在jvm啟動的時候進(jìn)行設(shè)置。
其實(shí)后來想想也應(yīng)該這么做,我們沒有必要每次使用對象的時候都返回新的對象,可以共享這些對象,因?yàn)樾聦ο蟮膭?chuàng)建都是需要消耗內(nèi)存的。
適配器模式
適配器模式比較好理解。像生活中插線口的插頭有2個口的,也有3個口的。如果電腦的電源插口只有3個口的,但是我們需要一個2個口的插口的話,這個時候就需要使用插座來外接這個3個口的插頭,插座上有2個口的插頭。
這個例子跟我們編程一樣,當(dāng)用戶系統(tǒng)的接口跟我們系統(tǒng)內(nèi)部的接口不一致時,我們可以使用適配器來完成接口的轉(zhuǎn)換。
使用繼承的方式實(shí)現(xiàn)類的適配:
- public class Source {
 - public void method() {
 - System.out.println("source method");
 - }
 - }
 - interface Targetable {
 - void method();
 - void newMethod();
 - }
 - class Adapter extends Source implements Targetable {
 - @Override
 - public void newMethod() {
 - System.out.println("new method");
 - }
 - }
 
測試:
- Targetable targetable = new Adapter();
 - targetable.method(); // source method
 - targetable.newMethod(); // new method
 
上述方式是用接口和繼承的方式實(shí)現(xiàn)適配器模式。當(dāng)然我們也可以使用組合的方式實(shí)現(xiàn)(把Source當(dāng)成屬性放到Adapter中)。
單例模式
單例模式比較好理解,Spring就是典型的例子。被Spring中的容器管理的對象都有對應(yīng)的scope,配置成singleton說明這個對象就是單例,也就是在Spring容器的生命周期中,這個類只有1個實(shí)例。
java中單例模式的寫法也有好多種。比如懶漢式、餓漢式、內(nèi)部類方式、枚舉方式等。
需要注意的如果使用dcl的話需要初始化過程,這篇 Java內(nèi)存模型之從JMM角度分析DCL 文章中說明了dcl的正確用法。
Effectice java中推薦的單例方式寫法是使用枚舉類型的方式。
外觀模式
外觀模式用來包裝一組接口用于方便使用。 比如系統(tǒng)中分10個模塊,有個功能需要組合使用所有的模塊,這個時候就需要一個包裝類包裝這10個接口,然后進(jìn)行業(yè)務(wù)邏輯的調(diào)用。















 
 
 


 
 
 
 