1. 程式人生 > 其它 >聊聊工作中常用的設計模式

聊聊工作中常用的設計模式

平時我們寫程式碼呢,多數情況都是流水線式寫程式碼,基本就可以實現業務邏輯了。如何在寫程式碼中找到樂趣呢,我覺得,最好的方式就是:使用設計模式優化自己的業務程式碼。今天跟大家聊聊日常工作中,我都使用過哪些設計模式。

1.策略模式

1.1 業務場景

假設有這樣的業務場景,大資料系統把檔案推送過來,根據不同型別採取不同的解析方式。多數的小夥伴就會寫出以下的程式碼:

if(type=="A"){
   //按照A格式解析
 
}else if(type=="B"){
    //按B格式解析
}else{
    //按照預設格式解析
}

這個程式碼可能會存在哪些問題呢?
如果分支變多,這裡的程式碼就會變得臃腫,難以維護,可讀性低。
如果你需要接入一種新的解析型別,那隻能在原有程式碼上修改。
說得專業一點的話,就是以上程式碼,違背了面向物件程式設計的開閉原則以及單一原則。
開閉原則(對於擴充套件是開放的,但是對於修改是封閉的):增加或者刪除某個邏輯,都需要修改到原來程式碼


單一原則(規定一個類應該只有一個發生變化的原因):修改任何型別的分支邏輯程式碼,都需要改動當前類的程式碼。
如果你的程式碼就是醬紫:有多個if...else等條件分支,並且每個條件分支,可以封裝起來替換的,我們就可以使用策略模式來優化。

1.2 策略模式定義

策略模式定義了演算法族,分別封裝起來,讓它們之間可以相互替換,此模式讓演算法的變化獨立於使用演算法的的客戶。這個策略模式的定義是不是有點抽象呢?那我們來看點通俗易懂的比喻:
假設你跟不同性格型別的小姐姐約會,要用不同的策略,有的請電影比較好,有的則去吃小吃效果不錯,有的去逛街買買買最合適。當然,目的都是為了得到小姐姐的芳心,請看電影、吃小吃、逛街就是不同的策略。
策略模式針對一組演算法,將每一個演算法封裝到具有共同介面的獨立的類中,從而使得它們可以相互替換。

1.3 策略模式使用

策略模式怎麼使用呢?醬紫實現的:
一個介面或者抽象類,裡面兩個方法(一個方法匹配型別,一個可替換的邏輯實現方法)
不同策略的差異化實現(就是說,不同策略的實現類)
使用策略模式

1.3.1 一個介面,兩個方法
public interface IFileStrategy {
    
    //屬於哪種檔案解析型別
    FileTypeResolveEnum gainFileType();
    
    //封裝的公用演算法(具體的解析方法)
    void resolve(Object objectparam);
}
1.3.2 不同策略的差異化實現

A 型別策略具體實現

@Component
public class AFileResolve implements IFileStrategy {
    
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_A_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("A 型別解析檔案,引數:{}",objectparam);
      //A型別解析具體邏輯
    }
}

B 型別策略具體實現

@Component
public class BFileResolve implements IFileStrategy {
   
    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_B_RESOLVE;
    }


    @Override
    public void resolve(Object objectparam) {
      logger.info("B 型別解析檔案,引數:{}",objectparam);
      //B型別解析具體邏輯
    }
}

預設型別策略具體實現

@Component
public class DefaultFileResolve implements IFileStrategy {

    @Override
    public FileTypeResolveEnum gainFileType() {
        return FileTypeResolveEnum.File_DEFAULT_RESOLVE;
    }

    @Override
    public void resolve(Object objectparam) {
      logger.info("預設型別解析檔案,引數:{}",objectparam);
      //預設型別解析具體邏輯
    }
}
1.3.3 使用策略模式

如何使用呢?我們藉助spring的生命週期,使用ApplicationContextAware介面,把對用的策略,初始化到map裡面。然後對外提供resolveFile方法即可。

/**
 *  @author 撿田螺的小男孩
 */
@Component
public class StrategyUseService implements ApplicationContextAware{

  
    private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();

    public void resolveFile(FileTypeResolveEnum fileTypeResolveEnum, Object objectParam) {
        IFileStrategy iFileStrategy = iFileStrategyMap.get(fileTypeResolveEnum);
        if (iFileStrategy != null) {
            iFileStrategy.resolve(objectParam);
        }
    }

    //把不同策略放到map
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
        tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
    }
}

2. 責任鏈模式

2.1 業務場景

我們來看一個常見的業務場景,下訂單。下訂單介面,基本的邏輯,一般有引數非空校驗、安全校驗、黑名單校驗、規則攔截等等。很多夥伴會使用異常來實現:

public class Order {

    public void checkNullParam(Object param){
      //引數非空校驗
      throw new RuntimeException();
    }
    public void checkSecurity(){
      //安全校驗
      throw new RuntimeException();
    }
    public void checkBackList(){
        //黑名單校驗
        throw new RuntimeException();
    }
    public void checkRule(){
        //規則攔截
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        Order order= new Order();
        try{
            order.checkNullParam();
            order.checkSecurity ();
            order.checkBackList();
            order2.checkRule();
            System.out.println("order success");
        }catch (RuntimeException e){
            System.out.println("order fail");
        }
    }
}

這段程式碼使用了異常來做邏輯條件判斷,如果後續邏輯越來越複雜的話,會出現一些問題:如異常只能返回異常資訊,不能返回更多的欄位,這時候需要自定義異常類。
並且,阿里開發手冊規定:禁止用異常做邏輯判斷。
【強制】 異常不要用來做流程控制,條件控制。說明:異常設計的初衷是解決程式執行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。
如何優化這段程式碼呢?可以考慮責任鏈模式

2.2 責任鏈模式定義

當你想要讓一個以上的物件有機會能夠處理某個請求的時候,就使用責任鏈模式。
責任鏈模式為請求建立了一個接收者物件的鏈。執行鏈上有多個物件節點,每個物件節點都有機會(條件匹配)處理請求事務,如果某個物件節點處理完了,就可以根據實際業務需求傳遞給下一個節點繼續處理或者返回處理完畢。這種模式給予請求的型別,對請求的傳送者和接收者進行解耦。
責任鏈模式實際上是一種處理請求的模式,它讓多個處理器(物件節點)都有機會處理該請求,直到其中某個處理成功為止。責任鏈模式把多個處理器串成鏈,然後讓請求在鏈上傳遞:

打個比喻:

假設你晚上去上選修課,為了可以走點走,坐到了最後一排。來到教室,發現前面坐了好幾個漂亮的小姐姐,於是你找張紙條,寫上:“你好, 可以做我的女朋友嗎?如果不願意請向前傳”。紙條就一個接一個的傳上去了,後來傳到第一排的那個妹子手上,她把紙條交給老師,聽說老師40多歲未婚...

2.3 責任鏈模式使用

責任鏈模式怎麼使用呢?
一個介面或者抽象類
每個物件差異化處理
物件鏈(陣列)初始化(連起來)

2.3.1 一個介面或者抽象類

這個介面或者抽象類,需要:
有一個指向責任下一個物件的屬性
一個設定下一個物件的set方法
給子類物件差異化實現的方法(如以下程式碼的doFilter方法)

/**
 * 撿田螺的小男孩
 */
public abstract class AbstractHandler {

    //責任鏈中的下一個物件
    private AbstractHandler nextHandler;

    /**
     * 責任鏈的下一個物件
     */
    public void setNextHandler(AbstractHandler nextHandler){
        this.nextHandler = nextHandler;
    }

    /**
     * 具體引數攔截邏輯,給子類去實現
     */
    public void filter(Request request, Response response) {
        doFilter(request, response);
        if (getNextHandler() != null) {
            getNextHandler().filter(request, response);
        }
    }

    public AbstractHandler getNextHandler() {
        return nextHandler;
    }

     abstract void doFilter(Request filterRequest, Response response);

}
2.3.2 每個物件差異化處理

責任鏈上,每個物件的差異化處理,如本小節的業務場景,就有引數校驗物件、安全校驗物件、黑名單校驗物件、規則攔截物件

/**
 * 引數校驗物件
 **/
@Component
@Order(1) //順序排第1,最先校驗
public class CheckParamFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        System.out.println("非空引數檢查");
    }
}

/**
 *  安全校驗物件
 */
@Component
@Order(2) //校驗順序排第2
public class CheckSecurityFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke Security check
        System.out.println("安全呼叫校驗");
    }
}
/**
 *  黑名單校驗物件
 */
@Component
@Order(3) //校驗順序排第3
public class CheckBlackFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //invoke black list check
        System.out.println("校驗黑名單");
    }
}

/**
 *  規則攔截物件
 */
@Component
@Order(4) //校驗順序排第4
public class CheckRuleFilterObject extends AbstractHandler {

    @Override
    public void doFilter(Request request, Response response) {
        //check rule
        System.out.println("check rule");
    }
}
2.3.3 物件鏈連起來(初始化)&& 使用
@Component("ChainPatternDemo")
public class ChainPatternDemo {

    //自動注入各個責任鏈的物件
    @Autowired
    private List<AbstractHandler> abstractHandleList;

    private AbstractHandler abstractHandler;

    //spring注入後自動執行,責任鏈的物件連線起來
    @PostConstruct
    public void initializeChainFilter(){

        for(int i = 0;i<abstractHandleList.size();i++){
            if(i == 0){
                abstractHandler = abstractHandleList.get(0);
            }else{
                AbstractHandler currentHander = abstractHandleList.get(i - 1);
                AbstractHandler nextHander = abstractHandleList.get(i);
                currentHander.setNextHandler(nextHander);
            }
        }
    }

    //直接呼叫這個方法使用
    public Response exec(Request request, Response response) {
        abstractHandler.filter(request, response);
        return response;
    }

    public AbstractHandler getAbstractHandler() {
        return abstractHandler;
    }

    public void setAbstractHandler(AbstractHandler abstractHandler) {
        this.abstractHandler = abstractHandler;
    }
}

執行結果如下:

非空引數檢查
安全呼叫校驗
校驗黑名單
check rule

3. 模板方法模式

3.1 業務場景

假設我們有這麼一個業務場景:內部系統不同商戶,呼叫我們系統介面,去跟外部第三方系統互動(http方式)。走類似這麼一個流程,如下:

一個請求都會經歷這幾個流程:

  • 查詢商戶資訊
  • 對請求報文加簽
  • 傳送http請求出去
  • 對返回的報文驗籤

這裡,有的商戶可能是走代理出去的,有的是走直連。假設當前有A,B商戶接入,不少夥伴可能這麼實現,虛擬碼如下:

// 商戶A處理控制代碼
CompanyAHandler implements RequestHandler {
   Resp hander(req){
   //查詢商戶資訊
   queryMerchantInfo();
   //加簽
   signature();
   //http請求(A商戶假設走的是代理)
   httpRequestbyProxy()
   //驗籤
   verify();
   }
}
// 商戶B處理控制代碼
CompanyBHandler implements RequestHandler {
   Resp hander(Rreq){
   //查詢商戶資訊
   queryMerchantInfo();
   //加簽
   signature();
   // http請求(B商戶不走代理,直連)
   httpRequestbyDirect();
   // 驗籤
   verify(); 
   }
}

假設新加一個C商戶接入,你需要再實現一套這樣的程式碼。顯然,這樣程式碼就重複了,一些通用的方法,卻在每一個子類都重新寫了這一方法。
如何優化呢?可以使用模板方法模式。

3.2 模板方法模式定義

定義一個操作中的演算法的骨架流程,而將一些步驟延遲到子類中,使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。它的核心思想就是:定義一個操作的一系列步驟,對於某些暫時確定不下來的步驟,就留給子類去實現,這樣不同的子類就可以定義出不同的步驟。
打個通俗的比喻:
模式舉例:追女朋友要先“牽手”,再“擁抱”,再“接吻”, 再“拍拍..額..手”。至於具體你用左手還是右手牽,無所謂,但是整個過程,定了一個流程模板,按照模板來就行。

3.3 模板方法使用

一個抽象類,定義骨架流程(抽象方法放一起)
確定的共同方法步驟,放到抽象類(去除抽象方法標記)
不確定的步驟,給子類去差異化實現
我們繼續那以上的舉例的業務流程例子,來一起用 模板方法優化一下哈:

3.3.1 一個抽象類,定義骨架流程

因為一個個請求經過的流程為一下步驟:
查詢商戶資訊
對請求報文加簽
傳送http請求出去
對返回的報文驗籤
所以我們就可以定義一個抽象類,包含請求流程的幾個方法,方法首先都定義為抽象方法哈:

/**
 * 抽象類定義骨架流程(查詢商戶資訊,加簽,http請求,驗籤)
 */
abstract class AbstractMerchantService  { 

      //查詢商戶資訊
      abstract queryMerchantInfo();
      //加簽
      abstract signature();
      //http 請求
      abstract httpRequest();
       // 驗籤
       abstract verifySinature();
 
}
3.3.2 確定的共同方法步驟,放到抽象類
abstract class AbstractMerchantService  { 

     //模板方法流程
     Resp handlerTempPlate(req){
           //查詢商戶資訊
           queryMerchantInfo();
           //加簽
           signature();
           //http 請求
           httpRequest();
           // 驗籤
           verifySinature();
     }
      // Http是否走代理(提供給子類實現)
      abstract boolean isRequestByProxy();
}
3.3.3 不確定的步驟,給子類去差異化實現

因為是否走代理流程是不確定的,所以給子類去實現。
商戶A的請求實現:

CompanyAServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //走http代理的
    boolean isRequestByProxy(){
       return true;
    }
商戶B的請求實現:
CompanyBServiceImpl extends AbstractMerchantService{
    Resp hander(req){
      return handlerTempPlate(req);
    }
    //公司B是不走代理的
    boolean isRequestByProxy(){
       return false;
    }

4. 觀察者模式

4.1 業務場景

登陸註冊應該是最常見的業務場景了。就拿註冊來說事,我們經常會遇到類似的場景,就是使用者註冊成功後,我們給使用者發一條訊息,又或者發個郵件等等,因此經常有如下的程式碼:

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendEmail();
}

這塊程式碼會有什麼問題呢?如果產品又加需求:現在註冊成功的使用者,再給使用者發一條簡訊通知。於是你又得改register方法的程式碼了。。。這是不是違反了開閉原則啦。

void register(User user){
  insertRegisterUser(user);
  sendIMMessage();
  sendMobileMessage();
  sendEmail();
}

並且,如果調發簡訊的介面失敗了,是不是又影響到使用者註冊了?!這時候,是不是得加個非同步方法給通知訊息才好。。。
實際上,我們可以使用觀察者模式優化。

4.2 觀察者模式定義

觀察者模式定義物件間的一種一對多的依賴關係,當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並被完成業務的更新。
觀察者模式屬於行為模式,一個物件(被觀察者)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。它的主要成員就是觀察者和被觀察者。
被觀察者(Observerable):目標物件,狀態發生變化時,將通知所有的觀察者。
觀察者(observer):接受被觀察者的狀態變化通知,執行預先定義的業務。
使用場景: 完成某件事情後,非同步通知場景。如,登陸成功,發個IM訊息等等。

4.3 觀察者模式使用

觀察者模式實現的話,還是比較簡單的。
一個被觀察者的類Observerable ;
多個觀察者Observer ;
觀察者的差異化實現
經典觀察者模式封裝:EventBus實戰

4.3.1 一個被觀察者的類Observerable 和 多個觀察者Observer
public class Observerable {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      notifyAllObservers();
   }
 
   //新增觀察者
   public void addServer(Observer observer){
      observers.add(observer);      
   }
   
   //移除觀察者
   public void removeServer(Observer observer){
      observers.remove(observer);      
   }
   //通知
   public void notifyAllObservers(int state){
      if(state!=1){
          System.out.println(“不是通知的狀態”);
         return ;
      }
   
      for (Observer observer : observers) {
         observer.doEvent();
      }
   }  
}
4.3.2 觀察者的差異化實現
//觀察者
interface Observer {  
   void doEvent();  
}  
//Im訊息
IMMessageObserver implements Observer{
   void doEvent(){
      System.out.println("傳送IM訊息");
   }
}

//手機簡訊
MobileNoObserver implements Observer{
   void doEvent(){
      System.out.println("傳送簡訊訊息");
   }
}
//EmailNo
EmailObserver implements Observer{
   void doEvent(){
      System.out.println("傳送email訊息");
   }
}
4.3.3 EventBus實戰

自己搞一套觀察者模式的程式碼,還是有點小麻煩。實際上,Guava EventBus就封裝好了,它 提供一套基於註解的事件匯流排,api可以靈活的使用,爽歪歪。
我們來看下EventBus的實戰程式碼哈,首先可以宣告一個EventBusCenter類,它類似於以上被觀察者那種角色Observerable。

public class EventBusCenter {

    private static EventBus eventBus = new EventBus();

    private EventBusCenter() {
    }

    public static EventBus getInstance() {
        return eventBus;
    }
     //新增觀察者
    public static void register(Object obj) {
        eventBus.register(obj);
    }
    //移除觀察者
    public static void unregister(Object obj) {
        eventBus.unregister(obj);
    }
    //把訊息推給觀察者
    public static void post(Object obj) {
        eventBus.post(obj);
    }
}

然後再宣告觀察者EventListener

public class EventListener {

    @Subscribe //加了訂閱,這裡標記這個方法是事件處理方法  
    public void handle(NotifyEvent notifyEvent) {
        System.out.println("傳送IM訊息" + notifyEvent.getImNo());
        System.out.println("傳送簡訊訊息" + notifyEvent.getMobileNo());
        System.out.println("傳送Email訊息" + notifyEvent.getEmailNo());
    }
}

//通知事件類
public class NotifyEvent  {

    private String mobileNo;

    private String emailNo;

    private String imNo;

    public NotifyEvent(String mobileNo, String emailNo, String imNo) {
        this.mobileNo = mobileNo;
        this.emailNo = emailNo;
        this.imNo = imNo;
    }
 }

使用demo測試:

public class EventBusDemoTest {

    public static void main(String[] args) {

        EventListener eventListener = new EventListener();
        EventBusCenter.register(eventListener);
        EventBusCenter.post(new NotifyEvent("13372817283", "[email protected]", "666"));
        }
}

執行結果:

傳送IM訊息666
傳送簡訊訊息13372817283
傳送Email訊息[email protected]

5. 工廠模式

5.1 業務場景

工廠模式一般配合策略模式一起使用。用來去優化大量的if...else...或switch...case...條件語句。
我們就取第一小節中策略模式那個例子吧。根據不同的檔案解析型別,建立不同的解析物件

 IFileStrategy getFileStrategy(FileTypeResolveEnum fileType){
     IFileStrategy  fileStrategy ;
     if(fileType=FileTypeResolveEnum.File_A_RESOLVE){
       fileStrategy = new AFileResolve();
     }else if(fileType=FileTypeResolveEnum.File_A_RESOLV){
       fileStrategy = new BFileResolve();
     }else{
       fileStrategy = new DefaultFileResolve();
     }
     return fileStrategy;
 }

其實這就是工廠模式,定義一個建立物件的介面,讓其子類自己決定例項化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。
策略模式的例子,沒有使用上一段程式碼,而是藉助spring的特性,搞了一個工廠模式,哈哈,小夥伴們可以回去那個例子細品一下,我把程式碼再搬下來,小夥伴們再品一下吧:

/**
 *  @author 撿田螺的小男孩
 */
@Component
public class StrategyUseService implements ApplicationContextAware{

    private Map<FileTypeResolveEnum, IFileStrategy> iFileStrategyMap = new ConcurrentHashMap<>();

    //把所有的檔案型別解析的物件,放到map,需要使用時,信手拈來即可。這就是工廠模式的一種體現啦
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, IFileStrategy> tmepMap = applicationContext.getBeansOfType(IFileStrategy.class);
        tmepMap.values().forEach(strategyService -> iFileStrategyMap.put(strategyService.gainFileType(), strategyService));
    }
}

5.2 使用工廠模式

定義工廠模式也是比較簡單的:
一個工廠介面,提供一個建立不同物件的方法。
其子類實現工廠介面,構造不同物件
使用工廠模式

5.3.1 一個工廠介面
interface IFileResolveFactory{
   void resolve();
}
5.3.2 不同子類實現工廠介面
class AFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("檔案A型別解析");
   }
}

class BFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("檔案B型別解析");
   }
}

class DefaultFileResolve implements IFileResolveFactory{
   void resolve(){
      System.out.println("預設檔案型別解析");
   }
}
5.3.3 使用工廠模式
//構造不同的工廠物件
IFileResolveFactory fileResolveFactory;
if(fileType=“A”){
    fileResolveFactory = new AFileResolve();
}else if(fileType=“B”){
    fileResolveFactory = new BFileResolve();
 }else{
    fileResolveFactory = new DefaultFileResolve();
}

fileResolveFactory.resolve();

一般情況下,對於工廠模式,你不會看到以上的程式碼。工廠模式會跟配合其他設計模式如策略模式一起出現的。

6. 單例模式

6.1 業務場景

單例模式,保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。I/O與資料庫的連線,一般就用單例模式實現de的。Windows裡面的Task Manager(工作管理員)也是很典型的單例模式。
來看一個單例模式的例子

/**
 * 撿田螺的小男孩
 */
public class LanHanSingleton {

    private static LanHanSingleton instance;

    private LanHanSingleton(){

    }

    public static LanHanSingleton getInstance(){
        if (instance == null) {
            instance = new LanHanSingleton();
        }
        return instance;
    }

}

以上的例子,就是懶漢式的單例實現。例項在需要用到的時候,才去建立,就比較懶。如果有則返回,沒有則新建,需要加下 synchronized關鍵字,要不然可能存線上性安全問題。

6.2 單例模式的經典寫法

其實單例模式還有有好幾種實現方式,如餓漢模式,雙重校驗鎖,靜態內部類,列舉等實現方式。

6.2.1 餓漢模式
public class EHanSingleton {

   private static EHanSingleton instance = new EHanSingleton();
   
   private EHanSingleton(){      
   }

   public static EHanSingleton getInstance() {
       return instance;
   }
   
}

餓漢模式,它比較飢餓、比較勤奮,例項在初始化的時候就已經建好了,不管你後面有沒有用到,都先新建好例項再說。這個就沒有執行緒安全的問題,但是呢,浪費記憶體空間呀。

6.2.2 雙重校驗鎖
public class DoubleCheckSingleton {

   private volatile static DoubleCheckSingleton instance;

   private DoubleCheckSingleton() { }
   
   public static DoubleCheckSingleton getInstance(){
       if (instance == null) {
           synchronized (DoubleCheckSingleton.class) {
               if (instance == null) {
                   instance = new DoubleCheckSingleton();
               }
           }
       }
       return instance;
   }
}

雙重校驗鎖實現的單例模式,綜合了懶漢式和餓漢式兩者的優缺點。以上程式碼例子中,在synchronized關鍵字內外都加了一層 if條件判斷,這樣既保證了執行緒安全,又比直接上鎖提高了執行效率,還節省了記憶體空間。

6.2.3 靜態內部類
public class InnerClassSingleton {

   private static class InnerClassSingletonHolder{
       private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
   }

   private InnerClassSingleton(){}
   
   public static final InnerClassSingleton getInstance(){
       return InnerClassSingletonHolder.INSTANCE;
   }
}

靜態內部類的實現方式,效果有點類似雙重校驗鎖。但這種方式只適用於靜態域場景,雙重校驗鎖方式可在例項域需要延遲初始化時使用。

6.2.4 列舉
public enum SingletonEnum {

    INSTANCE;
    public SingletonEnum getInstance(){
        return INSTANCE;
    }
}

列舉實現的單例,程式碼簡潔清晰。並且它還自動支援序列化機制,絕對防止多次例項化。


文章來源:https://mp.weixin.qq.com/s/2jWM4O_1pS7r9P5-1U6cjw