構造器或者 setter ?

我的網站

Java 對象在使用前需要創建出來是不言而喻的,無論是 domain、框架、庫或者其它任何形式的類都一樣。當你的編碼是面向對象的,這些類不過就是對象的定義而已,總之就是不能在沒有創建前就使用對象。

當我們談到對象的初始化時,我通常都會考慮到它們的依賴。是如何注入它們的?你是會用構造器還是 setter ?

讓我來幫助你們做出一個正確的選擇吧。

之前我們有個需求去處理一些事件。爲了處理這些事件我們需要從倉庫裏訪問必要的數據然後傳遞給觸發器,而觸發器會根據所給的數據觸發對應的動作。

在實現的時候我們創建瞭如下的類:

public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }

   public void handle(SomeEvent event) {
       // some code
   }
}

需求總是會不斷更改的。我們的客戶說偶爾呢他們會在對應的動作執行之前將訪問到的一些倉庫中的信息存儲下來,他們會拿這些數據做些統計以及更長久的分析。

那我們更改過後的類就成這樣了:

public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }

   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker) {
       // some code
   }

   public void handle(SomeEvent event) {
       // some code
   }
}

一個月過後我們的客戶又提出了另一個需求,在觸發一個事件後需要給個通知。這個在處理一些緊急事件是必要的,需要一個更高的透明度。

好,我們滿足了以上兩個需求後代碼如下:

public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }

   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker) {
       // some code
   }

   public SomeHandler(Repository repository, Trigger trigger, Notifier notifier) {
       // some code
   }

   public SomeHandler(Repository repository, Trigger trigger, SnapshotTaker snapshotTaker, Notifier notifier) {
       // some code
   }

   public void handle(SomeEvent event) {
       // some code
   }
}

這代碼看起來沒什麼問題,難道不是麼?好吧,這是個反問。

用構造器還是不用?

在上述的例子中我們的類有 4 個構造器。爲什麼會有這麼多呢?這是用戶需求更改的需要。而且似乎很不錯。我們的應用應該要滿足用戶的需求。

但是問題出在哪裏呢?問題就出在類的設計上。

爲什麼我們會需要這麼多的構造器?因爲有些參數是可選的,它們完全由外部條件所決定。那我們真的需要這麼多的構造器麼?在回答這個問題之前,我們最好問一下這個不同的:構造器的涵義是啥?

我們要創建一個擁有合法狀態的對象,在創建一個可用對象的參數裏不應該再包括其它非必需參數。這纔是那些放入構造器的參數的意義。換句話說就是,我們在構造器裏邊兒只應該放必要的參數,構造器裏的參數不應該是可選擇的。如果某個參數是可選的,那麼在創建一個合法的對象時它不是必須要的。

如果我們需要一些額外的參數來處理業務那麼我們應該通過不同的方式將其注入,這就是 setter 的作用之所在。我們並沒有強制的去調用 setter 方法,它所提供的功能並不是必須的。因此當存在那種非必需的參數時我們應該用 setters 來注入。

那麼,我們還需要這麼多的構造器麼?請代碼來說事兒:

public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }

   public void setSnapshotTaker(SnapshotTaker snapshotTaker) {
       // some code
   }

   public void setNotifier(Notifier notifier) {
       // some code
   }

   public void handle(SomeEvent event) {
       // some code
   }
}

代碼量少了且更清晰了。這裏第一眼就知道什麼是必須的以及什麼是可能用到的。

但是等等! Setter ?!

我並不喜歡 setters。爲什麼呢?因爲某種程度上這些方法違反了 封裝性,那麼我們可以用什麼來代替 setters 呢?在這個樣例中可以用什麼來代替呢?

不過我們無法避免食用這些方法,更準確地說,我們需要它們所帶來的功能。相當於用戶有需求來開啓這些功能,在樣例中修改器是需要保留的。當然,我們始終能夠讓代碼更優,更加的域相關。如何做呢?我們只需要把這些關係和域關聯起來即可:

public class SomeHandler {
   public SomeHandler(Repository repository, Trigger trigger) {
       // some code
   }

   public void enable(SnapshotTaker snapshotTaker) {
       // some code
   }

   public void enable(Notifier notifier) {
       // some code
   }

   public void handle(SomeEvent event) {
       // some code
   }
}

我寫到我對 setters 不感冒,因爲它們破壞了封裝性,而不僅僅是功能本身。使用類似 setX 的方法還存在另外一個問題,甚至方法的名稱都是面向實現的。有時 setter 功能是需要的,但是記住對於這類方法的命名需要和域關聯起來。

太多的選擇

有時太多的可選項也會導致問題,這可能暗示着你正在破壞單一職責原則。如果有太多的選擇意味着有太多的職責,這個時候就需要再次考慮你當前的方案了。

當增加一個可能值到類中的時候一定要小心,有可能這個類擔負了太多的事兒?

總結

希望你覺得這片文章有用。

你現在應該明白在構造器中只應該放那些必要的參數,而非必需的通過那些有明確意義的方法來實現。


翻譯原文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章