Spring中如何使用設計模式

       關於設計模式,若是使用得當,將會使咱們的代碼更加簡潔,而且更具擴展性。本文主要講解Spring中如何使用策略模式,工廠方法模式以及Builder模式。java

1. 策略模式

       關於策略模式的使用方式,在Spring中其實比較簡單,從本質上講,策略模式就是一個接口下有多個實現類,而每種實現類會處理某一種狀況。咱們以發獎勵爲例進行講解,好比咱們在抽獎系統中,有多種獎勵方式可供選擇,好比積分,虛擬幣和現金等。在存儲時,咱們必然會使用一個相似於type的字段用於表徵這幾種發放獎勵的,那麼這裏咱們就可使用多態的方式進行獎勵的發放。好比咱們抽象出一個PrizeSender的接口,其聲明以下:數據庫

public interface PrizeSender {

  /**
   * 用於判斷當前實例是否支持當前獎勵的發放
   */
  boolean support(SendPrizeRequest request);

  /**
   * 發放獎勵
   */
  void sendPrize(SendPrizeRequest request);

}

       該接口中主要有兩個方法:support()和sendPrize(),其中support()方法主要用於判斷各個子類是否支持當前類型數據的處理,而sendPrize()則主要是用於進行具體的業務處理的,好比這裏獎勵的發放。下面就是咱們三種不一樣類型的獎勵發放的具體代碼:設計模式

// 積分發放
@Component
public class PointSender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return request.getPrizeType() == PrizeTypeEnum.POINT;
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("發放積分");
  }
}
// 虛擬幣發放
@Component
public class VirtualCurrencySender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return PrizeTypeEnum.VIRTUAL_CURRENCY == request.getPrizeType();
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("發放虛擬幣");
  }
}
// 現金髮放
@Component
public class CashSender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return PrizeTypeEnum.CASH == request.getPrizeType();
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("發放現金");
  }
}

       這裏能夠看到,在每種子類型中,咱們只須要在support()方法中經過request的某個參數來控制當前request是不是當前實例可以處理的類型,若是是,則外層的控制邏輯就會將request交給當前實例進行處理。關於這個類的設計,有幾個點須要注意:多線程

  • 使用@Component註解對當前類進行標註,將其聲明爲Spring容器所管理的一個bean;
  • 聲明一個返回boolean值的相似於support()的方法,經過這個方法來控制當前實例是否爲處理目標request的實例;
  • 聲明一個相似於sendPrize()的方法用於處理業務邏輯,固然根據各個業務的不一樣聲明的方法名確定是不一樣的,這裏只是一個對統一的業務處理的抽象;
  • 不管是support()方法仍是sendPrize()方法,都須要傳一個對象進行,而不是簡簡單單的基本類型的變量,這樣作的好處是後續若是要在Request中新增字段,那麼就不須要修改接口的定義和已經實現的各個子類的邏輯;

2. 工廠方法模式

       上面咱們講解了如何使用Spring來聲明一個策略模式,那麼如何爲不一樣的業務邏輯來注入不一樣的bean呢,或者說外層的控制邏輯是什麼樣的,這裏咱們就可使用工廠方法模式了。所謂的工廠方法模式,就是定義一個工廠方法,經過傳入的參數,返回某個實例,而後經過該實例來處理後續的業務邏輯。通常的,工廠方法的返回值類型是一個接口類型,而選擇具體子類實例的邏輯則封裝到了工廠方法中了。經過這種方式,來將外層調用邏輯與具體的子類的獲取邏輯進行分離。以下圖展現了工廠方法模式的一個示意圖:框架

工廠方法模式

       能夠看到,工廠方法將具體實例的選擇進行了封裝,而客戶端,也就是咱們的調用方只須要調用工廠的具體方法獲取到具體的事例便可,而不須要管具體的實例實現是什麼。上面咱們講解了Spring中是如何使用策略模式聲明處理邏輯的,而沒有講如何選擇具體的策略,這裏咱們就可使用工廠方法模式。以下是咱們聲明的一個PrizeSenderFactoryide

@Component
public class PrizeSenderFactory {

  @Autowired
  private List<PrizeSender> prizeSenders;

  public PrizeSender getPrizeSender(SendPrizeRequest request) {
    for (PrizeSender prizeSender : prizeSenders) {
      if (prizeSender.support(request)) {
        return prizeSender;
      }
    }

    throw new UnsupportedOperationException("unsupported request: " + request);
  }
}

       這裏咱們聲明一個了一個工廠方法getPrizeSender(),其入參就是SendPrizeRequest,而返回值是某個實現了PrizeSender接口的實例,能夠看到,經過這種方式,咱們將具體的選擇方式下移到了具體的子類中的,由於當前實現了PrizeSender的bean是否支持當前request的處理,是由具體的子類實現的。在該工廠方法中,咱們也沒有任何與具體子類相關的邏輯,也就是說,該類其實是能夠動態檢測新加入的子類實例的。這主要是經過Spring的自動注入來實現的,主要是由於咱們這裏注入的是一個List<PrizeSender>,也就是說,若是有新的PrizeSender的子類實例,只要其是Spring所管理的,那麼都會被注入到這裏來。下面就是咱們編寫的一段用於測試的代碼來模擬調用方的調用:測試

@Service
public class ApplicationService {

  @Autowired
  private PrizeSenderFactory prizeSenderFactory;

  public void mockedClient() {
    SendPrizeRequest request = new SendPrizeRequest();
    request.setPrizeType(PrizeTypeEnum.POINT);  // 這裏的request通常是根據數據庫或外部調用來生成的
    PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
    prizeSender.sendPrize(request);
  }
}

       在客戶端代碼中,首先經過PrizeSenderFactory獲取一個PrizeSender實例,而後經過其sendPrize()方法發放具體的獎勵,經過這種方式,將具體的獎勵發放邏輯與客戶端調用進行了解耦。並且根據前面的講解,咱們也知道,若是新增了一種獎勵方式,咱們只須要聲明一個新的實現了PrizeSender的bean便可,而不須要對現有代碼進行任何修改。ui

3. Builder模式

       關於Builder模式,我想使用過lombok的同窗確定會說builder模式很是的簡單,只須要在某個bean上使用@Builder註解進行聲明便可,lombok能夠自動幫咱們將其聲明爲一個Builder的bean。關於這種使用方式,本人不置能否,不過就個人理解,這裏主要有兩個點咱們須要理解:this

  • Builder模式就其名稱而言,是一個構建者,我更傾向於將其理解爲經過必定的參數,經過必定的業務邏輯來最終生成某個對象。若是僅僅只是使用lombok的這種方式,其本質上也仍是建立了一個簡單的bean,這個與經過getter和setter方式構建一個bean是沒有什麼大的區別的;
  • 在Spring框架中,使用設計模式最大的問題在於若是在各個模式bean中可以注入Spring的bean,若是可以注入,那麼將大大的擴展其使用方式。由於咱們就能夠真的實現經過傳入的簡單的幾個參數,而後結合Spring注入的bean進行必定的處理後,以構造出咱們所須要的某個bean。顯然,這是lombok所沒法實現的;

        關於Builder模式,咱們能夠之前面獎勵發放的SendPrizeRequest的構造爲例進行講解。在構造request對象的時候,必然是經過前臺傳如的某些參數來通過必定的處理,最後生成一個request對象。那麼咱們就可使用Builder模式來構建一個SendPrizeRequest。這裏假設根據前臺調用,咱們可以獲取到prizeId和userId,那麼咱們就能夠建立一個以下的SendPrizeRequestprototype

public class SendPrizeRequest {

  private final PrizeTypeEnum prizeType;
  private final int amount;
  private final String userId;

  public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {
    this.prizeType = prizeType;
    this.amount = amount;
    this.userId = userId;
  }

  @Component
  @Scope("prototype")
  public static class Builder {

    @Autowired
    PrizeService prizeService;

    private int prizeId;
    private String userId;

    public Builder prizeId(int prizeId) {
      this.prizeId = prizeId;
      return this;
    }

    public Builder userId(String userId) {
      this.userId = userId;
      return this;
    }

    public SendPrizeRequest build() {
      Prize prize = prizeService.findById(prizeId);
      return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);
    }
  }

  public PrizeTypeEnum getPrizeType() {
    return prizeType;
  }

  public int getAmount() {
    return amount;
  }

  public String getUserId() {
    return userId;
  }
}

        這裏就是使用Spring維護一個Builder模式的示例,具體的 維護方式就是在Builder類上使用@Component@Scope註解來標註該Builder類,這樣咱們就能夠在Builder類中注入咱們所須要的實例來進行必定的業務處理了。關於該模式,這裏有幾點須要說明:

  • 在Builder類上必須使用@Scope註解來標註該實例爲prototype類型,由於很明顯,咱們這裏的Builder實例是有狀態的,沒法被多線程共享;
  • 在Builder.build()方法中,咱們能夠經過傳入的參數和注入的bean來進行必定的業務處理,從而獲得構建一個SendPrizeRequest所須要的參數;
  • Builder類必須使用static修飾,由於在Java中,若是內部類不用static修飾,那麼該類的實例必須依賴於外部類的一個實例,而咱們這裏本質上是但願經過內部類實例來構建外部類實例,也就是說內部類實例存在的時候,外部類實例是還不存在的,於是這裏必須使用static修飾;
  • 根據標準的Builder模式的使用方式,外部類的各個參數都必須使用final修飾,而後只須要爲其聲明getter方法便可。

       上面咱們展現瞭如何使用Spring的方式來聲明一個Builder模式的類,那麼咱們該如何進行使用呢,以下是咱們的一個使用示例:

@Service
public class ApplicationService {

  @Autowired
  private PrizeSenderFactory prizeSenderFactory;

  @Autowired
  private ApplicationContext context;

  public void mockedClient() {
    SendPrizeRequest request = newPrizeSendRequestBuilder()
        .prizeId(1)
        .userId("u4352234")
        .build();

    PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
    prizeSender.sendPrize(request);
  }

  public Builder newPrizeSendRequestBuilder() {
    return context.getBean(Builder.class);
  }
}

       上述代碼中,咱們主要要看一下newPrizeSendRequestBuilder()方法,在Spring中,若是一個類是多例類型,也即便用@Scope("prototype")進行了標註,那麼每次獲取該bean的時候就必須使用ApplicationContext.getBean()方法獲取一個新的實例,至於具體的緣由,讀者可查閱相關文檔。咱們這裏就是經過一個單獨的方法來建立一個Builder對象,而後經過流式來爲其設置prizeId和userId等參數,最後經過build()方法構建獲得了一個SendPrizeRequest實例,經過該實例來進行後續的獎勵發放。

4. 小結

       本文主要經過一個獎勵發放的示例來對Spring中如何使用工廠方法模式,策略模式和Builder模式的方式進行講解,而且着重強調了實現各個模式時咱們所須要注意的點。

相關文章
相關標籤/搜索