Re: [概念] single responsibility principle

看板OOAD作者 ( )時間15年前 (2010/01/21 15:33), 編輯推噓2(200)
留言2則, 2人參與, 最新討論串2/5 (看更多)
※ 引述《hsnucsc (hsnugo)》之銘言: : 我找了很多網站 : 都將解釋SRP成: 每一個物件, 應該要只有單一的responsibility : 而將responsibility解釋成: 只有一個理由去改變物件 : 但是我還是覺得responsibility這個詞很模糊 : (如果直接翻譯成中文, "責任", 仍然很難知道這個責任的大小) 這個責任是一個夠大的責任。 因為你不是從標準路線走到這一步, 所以大概跟你說明一下好了。 傳統 use-case driven 的開發, 會先從零散的問題裡整理出比較明確的 problem statements, 接著從那裡面區分出 functional/non-functional requirements 來。 use-case model 主要在模塑的是 functional requirements, 經過對前面得到的文字資料做名詞和動詞的分析後, 可以找到 actors 和 use cases。 在 use cases 被找到之後, 你的「需求分析工具」會要求你撰寫 use case specifications, 格式大概是: Use Case Specification: <這裡填 use case 的名字> 1. Use Case Name 1.1 Brief Description 2. Flow of Events 2.1 Basic Flow 2.2 Alternative Flows 3. Special Requirements ... 在 2. 的部分要求你描述的, 就是整個 use case 從開始到結束要做的事情。 所謂的 basic flow 是很順利的一直線走到底的流程, 沒有所謂的特例狀況和分支, 特例狀況和分支都被描述在 alternative flows 上。 一般來說使用 UML 開發的話, 這部份也時常搭配 activity diagrams 來進行。 前言就講到這裡, 有一點長; 主要目的只有告訴你什麼叫 flow of events, 還有它是怎麼來的。 flow of events 是所謂「需求規格」的一部份。 對 flow of events 做 textual analysis, 你將會得到一群名詞和動詞, 一個有效的名詞一般來說就會成為一個 class。 我想到這邊你還是很熟悉, 但是這背後還有其它的意義。 你在描述 flow of events 時, 也同時在描述每個名詞對某些行為的責任。 SRP 並不是無限上綱到脫離 requirements 讓你停不下來, 你的一切 analysis 和 design 都必須基於 requirements 進行。 就像做學術研究, 一個論文題目總是不會解決所有問題, 而是會設定一些限制條件後再對有限的問題進行拆解; 實務面的軟體設計更是如此。 責任的大小, 完全視你的需求而定, 不然你所有的東西恐怕都要定義到原子或夸克去了。 進行 SRP 分析或填寫 CRC 卡的時候, 請記得看著 flow of events 做, 如果你沒有這種東西, 就看看 flow charts 或 activity diagrams。 這些東西裡都沒有描述到的更細部責任, 那就不需要考慮, 你不需要把責任 delegate 給一個無效甚至不存在的名詞。 在設計階段, SRP 常跟 OCP 搭配, 而 OCP 常跟 strategy pattern 搭配。 strategy pattern 負責將各種行為做分類, 因此改變行為常常是「新增」 concrete strategy, 以及對 factory method (或 creation method) 的修改。 除非你需要新增的是 strategy 的 family (即 abstract strategy 或 interface), 才有可能需要去動到 context class。 因為這三種東西通常都有連動關係, 所以當你的「行為」本身根據需求並不會有「一個家族」的行為集時, 你是不需要再另外把責任再往外 delegate 給其它 classes。 以上兩段應該大概可以告訴你「什麼時候該停下來」了, 簡單來說, 一切都環繞在需求規格上; 其實我也很想直接推一句「看需求決定」來回你就好了, 但是你可能會覺得很抽象, 然後將來可能又會有其他人跑來問一樣的問題。 : 以書上的例子而言 : Automobile 車子有很多功能 : start() : stop() : changeTiles(Tile [*]) : drive() : wash() : checkOil() : getOil() : 應該要被分解成 : Automobile : start() : stop() : getOil() : Driver : drive(Automobile) : Carwash : wash() : Mechanic : changeTires(Automobile, Tire[*]) : checkOil(Automible) : 有一些method移交到其他物件處理 : 但是.... : 1. 假設Automible有 : body, front-left_tire, front_right_tire, back-left-tire, back_right_tire : changeTires()為了要change車子的輪胎 : 勢必要改變其四個輪胎 : 我猜寫法是 : changeTires(Automobile au, Tire[*] tires) : au.set_front_left_tire(tires[0]); : au.set_front_right_tire(tires[1]); : .... : 這樣為什麼不直接把changeTires這件事直接委派給Automible去做 : 同樣的道理, 如果其他drive, wash等method, 需要get or set : 來知道Automible的細節, 那何不直接把這樣工作交給Automible做 你把這世界簡單化了。 一方面也是因為你看的書不是從頭到尾 run 一個 project 給你看, 確實很容易讓你因為對「需求」的資訊不足, 使得你對例子的理解與作者假設的情境不同。 一個物件被委派責任的原因, 主要是因為它「瞭解如何完成這個任務」, 也就是說包括這項任務的細節在內, 並不是你想的單純 set/get 就好了, Automible 本身可能提供非常多 method 讓你 set/get 細部的參數 但是只有 Mechanic 知道要如何正確的 set 它們, 這中間可能還會有一些操作機械的流程, 可能還會有一些基本的安全檢測等等; 當然這一些要看你的 flow of events 有沒有描述到。 : 2. 這樣好像又回到procedural programming : data跟function是分開的, data當作function的input後, 由function來處理data : 在這裡, Automobile是要處理的data : 而Driver, Carwash, Mechanic則有點像是處理data的function : 再舉個例子: : 今天我們需要一個鐵路線路網, 上面有多條線路, 多個車站 : 且在給予起點和終點後, 可以找出一個起點到終點的path, 並印出來 : 如果是我來設計的話 : class會有Subway, Line, Station, Route : Subway.find(Sation s, Sation t)後, return 一個Route : Route.print()印出path : 但是書上的方法, : class會有Subway, Connection, Station, Printer : (Line, Connection的差異先不討論) : 他用Printer來印一個List route(有Connection跟Station) : 於是, Printer在print的method裡 : 必須要不停的用route.getXX(), station.getXX() ...... : 這感覺很像是Printer在處理一個Data : 為什麼不直接把print的工作給statoin, 給route呢 Printer 可以做相當簡化的工作, 比方說它就是只提供 print(String), 也就是字串的列印; station 和 route 並不需要知道如何列印一個字串。 Route.print() 可能要做比你想像中還要多的事情, 比方說把內部的資料表示法轉換成有意義、易讀易懂且可列印的字串, 才把列印「字串」的責任交給 Printer。 : java Exception 不是也有 printStackTrace, 並沒有說特別把print的工作交給別人啊? : 上面是增加新的物件來處理 : 我也有找到另一個方法, 是增加interface : http://www.codingefficiency.com/2009/07/18/ : solid-s-single-responsibility-principle/ : http://0rz.tw/QP6hs (縮過的網址) : 不過仍然讓人很難分辨responsibility : 說嚴格一點, 好像每個method, 都有理由改變 : 但是又不可能把每個method都新增一個物件去處理這項功能 : 希望有人可以幫忙解答 : 謝謝 -- Ling-hua Tseng (uranus@tinlans.org) Department of Computer Science, National Tsing-Hua University Interesting: C++, Compiler, PL/PD, OS, VM, Large-scale software design Researching: Software pipelining for VLIW architectures Homepage: http://www.tinlans.org -- ※ 發信站: 批踢踢實業坊(ptt.cc) ◆ From: 118.160.104.199 ※ 編輯: tinlans 來自: 118.160.104.199 (01/21 15:40)

01/21 18:53, , 1F
謝謝說明,剛好這個案子也在思考這些,有幫助^^
01/21 18:53, 1F

01/23 00:28, , 2F
Thanks
01/23 00:28, 2F
文章代碼(AID): #1BM0DXvR (OOAD)
文章代碼(AID): #1BM0DXvR (OOAD)