l

2014年5月12日 星期一

談談壞味道(9):Middle Man

May 6 21:16~22:12

image 

 

Middle Man(中間人)

一對情侶吵架之後,女朋友不想接男朋友的電話。每當男朋友有話想對女方說,必須透過他們的共同朋友「小美」來傳話,「小美」就是這對情侶的「中間人」。在物件導向的世界中,A物件呼叫B物件的函數,A物件並不需要知道B物件如何實做這些函數,這是「封裝」的特性,隱藏B物件的內部實作。但如果B物件的很多函數實作方式,都委託(delegate)給C物件(轉呼叫C物件的函數),則這種過度使用delegation的情況就產生了Middle Man這種壞味道。

寫到這邊讓Teddy想起好久以前使用VB 6.0的經驗,由於VB 6.0不支援實作繼承,只支援介面繼承,因此不能把subclass共同的程式碼寫在superclass上面。怎麼辦?只能把共同的程式碼寫在另外一個utility類別上面,然後subclass的函數實作委託給這個utility類別。按照《Refactoring》書上的說法,這就產生了Middle Man壞味道挑眉質疑。挖哩勒,怎麼原本物件導向的「隱藏實作細節」的優點,居然變成一種壞味道啦?

Teddy覺得判斷是否存在Middle Man這個壞味道,不能只是從「做了很多delegation」來決定,還要從「delegation是否為了隱藏實作細節」來判斷。如果是為了隱藏實作細節,應該是一種可以接受的Middle Man。舉個例子,在Bridge設計模式中(如下圖),Abstraction把實作委託給Concrete Implementor,所以從客戶端的角度來看,Abstraction就是一個Middle Man。所以Bridge模式「天生」就臭臭的嗎?好像也不能這樣說。

螢幕截圖 2014-05-06 21.46.11

 

《Refactoring》書上160頁有一個移除Middle Man壞味道的例子,Person物件包含了一個Department物件:

   1: class Person...
   2:    Department _department;
   3:  
   4:        public Person getManager() }
   5:          return _department.getManager();
   6:    }

客戶端的程式可以呼叫 getManager()函數來得到某人的主管物件,而getManager()函數的實作方式是呼叫_departemtn.getManager(),所以Person變成了客戶端與Departement物件的「中間人」。

如果客戶端程式還需要呼叫Department物件的其他函數,那還不如直接把Department物件傳給客戶端,不要透過Person物件包裝。因此,可以把Person改成:


   1: class Person...
   2:    Department _department;
   3:  
   4:        public Department getDepartment() }
   5:          return _department;
   6:    }

在這個例子中,Person和Department對於客戶端而言,都是有意義的物件,所以去掉中間人並不會違反封裝,或是造成暴露實作細節的問題。鄉民們有沒有注意到這個例子和Bridge設計模式不一樣的地方呢?

***

綜合以上的說明,Middle Man之所以會是一個bad smell的原因(force)可歸類為:modifiability和understandability這兩點。

移除Middle Man壞味道的方法,在《Refactoring》書中提到可以套用Remove Middle Man、Inline Method和Replace Delegation with Inheritance。

***

友藏內心獨白:ScrumMaster不是PO和Team之間的Middle Man啊。

1 則留言:

  1. 『客戶端的程式可以呼叫 getManager()函數來得到某人的主管物件,而getManager()函數的實作方式是呼叫_departemtn.getManager(),所以Person變成了客戶端與Departement物件的「中間人」。 』
    _departemtn.getManager()->_departemnt.getManager()

    回覆刪除