iT邦幫忙

0

物件導向設計的單一職責,範圍要怎麼決定呢

大家好:
目前在學習物件導向設計
想請問對於類別與函數的單一職責,它們的範圍要怎麼決定呢?

例如類別的職責:
我有一個 Data 類別負責儲存從資料庫查詢到的數據
當要將這些數據用表格顯示在網頁上時
想請問:
Data 類別可以設定一個 display() 方法來負責這項任務嗎?
還是要另外建立一個 Display 類別來負責顯示數據在網頁上?

又例如函數的職責:
我要將使用者從表單送出的數據,用 create() 方法新增到資料庫內
而在新增數據之前,我要用 filter() 方法先過濾這些數據
確認是正確數據後才新增到資料庫
想請問:
filter() 方法是要放在 create() 方法內?
還是先執行 filter() 方法,確認數據沒問題後再執行 create() 方法呢?
我的想法是:
因為要新增數據到資料庫內之前,每次都一定要先確認數據沒問題,這二個任務是不可分割的
所以過濾數據應該要放在 create() 方法內
但這樣 create() 方法好像就不是只有單一職責了

對於類別和函數的單一職責的範圍,一直有疑問
想請教大家
謝謝

weiclin iT邦高手 4 級 ‧ 2019-02-01 11:44:57 檢舉
可以參考這篇
http://teddy-chen-tw.blogspot.com/2011/12/3.html
samjam iT邦新手 4 級 ‧ 2019-02-03 22:28:08 檢舉
謝謝您,很有幫助的文章

2 個回答

4
froce
iT邦高手 1 級 ‧ 2019-02-01 16:01:01
最佳解答

我覺得把握一個大原則,當你覺得code重複時代表你的code不好。
就以上你自己舉的兩個例子:

1.假設你今天有個Person類別、另一個是Order類別,存的欄位不一樣,都是你自己可以掌控/修改的,你都想把這兩個類別產生的實例下面的屬性都列舉出來,請問你是會兩個類別都用個別的display方法,還是利用一個display函式去做?

2.你要create之前要filter,那有時候你不用filter就create,有時候你要filter的時候還要做某些特殊動作(比如傳callback之類的),那你難道對那些特例還要做特殊處理嗎?

這兩個例子我會給的python虛擬碼會是下面這樣:
1.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
class Order:
    def __init__(self, price, date):
        self.price = price
        self.date = date
        
def display(obj):
	atts = [print(att, getattr(obj, att)) for att in dir(obj) if not att.startswith("_")] #顯示所有公開屬性
    
p = Person("John", 32)
o = Order(1000, '2019-02-01')
display(p)
display(o)

而不是:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def display(self):
        print(self.name)
        print(self.age)
        
class Order:
    def __init__(self, price, date):
        self.price = price
        self.date = date
        
    def display(self):
        print(self.price)
        print(self.date)
def filter():
    ...
    
def create():
    ...
    
data1 = filter(ori_data, column1='aaa', column2='bbb', ...)
create(data1)

而不是

def create(data):
    def filter(data):
        ...
        
    tmp = filter(data)
    ...

當然你在class內可以這樣:

class ex1:
    def filter(self):
        ...
        
    def create(self):
        tmp = self.filter(...)
        ...

samjam iT邦新手 4 級 ‧ 2019-02-03 22:27:21 檢舉

您解釋的很清楚,我了解囉,解開了我的疑問,謝謝喔

0
rewrite
iT邦新手 4 級 ‧ 2019-02-01 17:07:53

個人淺見,你可以參考參考,不一定要全盤接收

依單一職責的原理,設計該類別時要有「針對性」且「複雜度低」又要易於「維護」的大範圍區段

一開始在學的時候有時候會拆的太過或是綁的太死,現在也是還在磨練中

我自己是有一個判斷方式

就是先用行為判別,行為判別的定義在於此類別的「可視範圍」到哪跟我有另外一個任務要加到已存在的類別時的「否定再延伸」

以你的data class來說

定義可視範圍:「負責儲存從資料庫查詢到的數據」在這一段話裡分析出

可視範圍:
存取
--儲存
----新增
----修改
----刪除
--取用
----查詢
----過濾

否定再延伸「負責顯示數據在網頁上」: 資料存取 >> 顯示
資料可以取用後可以下echo、print_r,可以顯示但是加工要「額外」處理,所以要再延伸會很牽強,所以display就不適合放在Data內


而函數職責屬於功能的實踐,除了可視範圍跟否定再延伸,還要考慮的是「功能複用度」

就有疑問的點來討論
「因為要"新增"數據到資料庫內之前,每次都一定要先確認數據沒問題」
我們將新增的這個功能替換成其他的詞句
「因為要"編輯"數據到資料庫內之前,每次都一定要先確認數據沒問題」
「因為要"刪除"數據到資料庫內之前,每次都一定要先確認數據沒問題」
如果要將
filter()包在指定方法內,那麼你相同的動作就都要重複包一次,這就不符合單一職責原則
那如果你的filter()有參數上的異動,你就要在包裝的功能內去修改filter(),這樣就不符合「開閉原則」

大致上會像是這樣

<?php

class Data {
	/**
	 * 新增
	 * @return [type] [description]
	 */
	public function create() { /****/ }

	/**
	 * 修改
	 * @return [type] [description]
	 */
	public function update() { /****/ }

	/**
	 * 刪除
	 * @return [type] [description]
	 */
	public function delete() { /****/ }

	/**
	 * 查詢
	 * @return [type] [description]
	 */
	public function query() { /****/ }

	/**
	 * 過濾
	 * @return [type] [description]
	 */
	public function filter() { /****/ }

}
<?php

class Response {

	private $headers = array();
	private $level = 0;
	private $output;

	public function addHeader($header) {
		$this->headers[] = $header;
	}

	public function setOutput($output) {
		$this->output = $output;
	}

	public function getOutput() {
		return $this->output;
	}

	public function display($data='') {
		if ($this->output) {
			
			if (!headers_sent()) {
				foreach ($this->headers as $header) {
					header($header, true);
				}
			}

			echo $output;
		}
	}

}
samjam iT邦新手 4 級 ‧ 2019-02-03 22:25:32 檢舉

謝謝您的回答,對我很有幫助

我要發表回答

立即登入回答