一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - 編程技術 - 重拾面向對象軟件設計

重拾面向對象軟件設計

2021-11-23 22:12阿里技術 編程技術

在歐洲文藝復興時期,一位偉大的數學家天文學家-哥白尼,在當時提出了日心說,駁斥了以地球為宇宙中心的天體思想,由于思想極其超前,直到半個世紀后開普勒伽利略等人經過后期研究,才逐步認可并確立了當時哥白尼思想的

重拾面向對象軟件設計

你還在用面向對象的語言,寫著面向過程的代碼嗎?

一 前言

在歐洲文藝復興時期,一位偉大的數學家天文學家-哥白尼,在當時提出了日心說,駁斥了以地球為宇宙中心的天體思想,由于思想極其超前,直到半個世紀后開普勒伽利略等人經過后期研究,才逐步認可并確立了當時哥白尼思想的先進性。

無獨有偶,在軟件工程領域也上演著同樣的故事。半個世紀前 Kristen Nygaard發明了Simula語言,這也是現在被認同的世界上第一個明確實現面向對象編程的語言,他提出了基于類的編程風格,確定了"萬物皆對象"這一面向對象理論的"終極思想",但在當時同樣未受到認可。Peter Norvig 在 Design Patterns in Dynamic Programming 對此予以了駁斥,并表述我們并不需要什么面向對象。半個世紀后 Robert C.Martin、Bertrand Meyer、Martin Fowler等人,再次印證并升華了面向對象的設計理念。編程思想的演進也不是一蹴而就,但在這一個世紀得到了飛速的發展。

二 編程思想的演進

從上個世紀五十年代馮·諾依曼創造第一臺計算機開始,一直到現在只有短短70年時間,從第一門計算機語言FORTRAN,到現在我們常用的C++,JAVA,PYTHON等,計算機語言的演進速度遠超我們所使用的任何一門自然語言。從最早的面向機器,再到面向過程,到演化為現在我們所使用的面向對象。不變的是編程的宗旨,變化的是編程的思想。

1 面向機器

重拾面向對象軟件設計

計算機是01的世界,最早的程序就是通過這種01機器碼來控制計算機的,比如0000代表讀取,0001代表保存等。理論上這才是世界上最快的語言,無需翻譯直接運行。但弊端也很明顯,那就是幾乎無法維護。運行5毫秒,編程3小時。由于機器碼無法維護,人們在此基礎上發明了匯編語言,READ代表0000,SAVE代表0001,這樣更易理解和維護。雖然匯編在機器碼上更可視更直觀,但本質上還是一門面向機器的語言,依然還是存在很高的編程成本。

2 面向過程

重拾面向對象軟件設計

面向過程是一種以事件為中心的編程思想,相比于面向機器的編程方式,是一種巨大的進步。我們不用再關注機器指令,而是聚焦于具體的問題。它將一件事情拆分成若干個執行的步驟,然后通過函數實現每一個環節,最終串聯起來完成軟件設計。

流程化的設計讓編碼更加清晰,相比于機器碼或匯編,開發效率得到了極大改善,包括現在仍然有很多場景更適合面向過程來完成。但軟件工程最大的成本在于維護,由于面向過程更多聚焦于問題的解決而非領域的設計,代碼的重用性與擴展性弊端逐步彰顯出來,隨著業務邏輯越來越復雜,軟件的復雜性也變得越來越不可控。

3 面向對象

重拾面向對象軟件設計

面向對象以分類的方式進行思考和解決問題,面向對象的核心是抽象思維。通過抽象提取共性,通過封裝收斂邏輯,通過多態實現擴展。面向對象的思想本質是將數據與行為做結合,數據與行為的載體稱之為對象,而對象要負責的是定義職責的邊界。面向過程簡單快捷,在處理簡單的業務系統時,面向對象的效果其實并不如面向過程。但在復雜系統的設計上,通用性的業務流程,個性化的差異點,原子化的功能組件等等,更適合面向對象的編程模式。

但面向對象也不是銀彈,甚至有些場景用比不用還糟,一切的根源就是抽象。根據 MECE法則 將一個事物進行分類,if else 是軟件工程最嚴謹的分類。我們在設計抽象進行分類時,不一定能抓住最合適的切入點,錯誤的抽象比沒有抽象復雜度更高。里氏替換原則的創始人Barbara Liskov 談抽象的力量 The Power of Abstraction。

三 面向領域設計

1 真在“面向對象”嗎

  1. // 撿入客戶到銷售私海
  2. public String pick(String salesId, String customerId){
  3. // 校驗是否銷售角色
  4. Operator operator = dao.find("db_operator", salesId);
  5. if("SALES".equals(operator.getRole())){
  6. return "operator not sales";
  7. }
  8. // 校驗銷售庫容是否已滿
  9. int hold = dao.find("sales_hold", salesId);
  10. List customers = dao.find("db_sales_customer", salesId);
  11. if(customers.size() >= hold){
  12. return "hold is full";
  13. }
  14. // 校驗是否客戶可撿入
  15. Opportunity opp = dao.find("db_opportunity", customerId);
  16. if(opp.getOwnerId() != null){
  17. return "can not pick other's customer";
  18. }
  19. // 撿入客戶
  20. opp.setOwnerId(salesId);
  21. dao.save(opp);
  22. return "success";
  23. }

這是一段CRM領域銷售撿入客戶的業務代碼。這是我們熟悉的Java-面向對象語言,但這是一段面向對象代碼嗎?完全面向事件,沒有封裝沒有抽象,難以復用不易擴展。相信在我們代碼庫,這樣的代碼不在少數。為什么?因為它將成本放到了未來。我們將此稱之為“披著面向對象的外衣,干著面向過程的勾當。”

在系統設計的早期,業務規則不復雜,邏輯復用與擴展體現得也并不強烈,而面向過程的代碼在支撐這些相對簡單的業務場景是非常容易的。但軟件工程最大的成本在于維護,當系統足夠復雜時,當初那些寫起來最easy的代碼,將來就是維護起來最hard的債務。

2 領域驅動設計

重拾面向對象軟件設計

還有一種方式我們也可以這么來寫,新增“商機”模型,通過商機來關聯客戶與銷售之間的關系。而商機的歸屬也分為公海、私海等具體歸屬場景。商機除了有必要的數據外,還應該收攏一些業務行為,撿入、開放、分發等。通過領域建模,利用面向對象的特性,確定邊界、抽象封裝、行為收攏,對業務分而治之。

當我們業務上說“商機分發到私海”,而我們代碼則是“opportunity.pickTo(privateSea)”。這是領域驅動所帶來的改變,面向領域設計,面向對象編程,領域模型的抽象就是對現實世界的描述。但這并非一蹴而就的過程,當你只觸碰到大象的身板時,你認為這是一扇門,當你觸碰到大象的耳朵時,你認為是一片芭蕉。只有我們不斷抽象不斷重構,我們才能愈發接近業務的真實模型。

Use the model as the backbone of a language, Recognize that a change in the language is a change to the model.Then refactor the code, renaming classes, methods, and modules to conform to the new model

--- Eric Evans 《Domain-Driven Design Reference》

譯:使用模型作為語言的支柱,意識到言語的改變就是對模型的改變,然后重構代碼,重命名類,方法和模塊以符合新模型。

3 軟件的復雜度

重拾面向對象軟件設計

這是Martin Flowler在 Patterns of Enterprise Application Architecture 這本書中所提的關于復雜度的觀點,他將軟件開發分為數據驅動與領域驅動。很多時候開發的方式大家傾向于,拿到需求后看表怎么設計,然后看代碼怎么寫,這其實也是面向過程的一個表現。在軟件初期,這樣的方式復雜度是很低的,沒有復用沒有擴展,一人吃飽全家不餓。但隨著業務的發展系統的演進,復雜度會陡增。

而一開始通過領域建模方式,以面向對象思維進行軟件設計,復雜度的上升可以得到很好的控制。先思考我們領域模型的設計,這是我們業務系統的核心,再逐步外延,到接口到緩存到數據庫。但領域的邊界,模型的抽象,從剛開始成本是高于數據驅動的。

The goal of software architecture is to minimize the human resources required to build and maintain the required system.

--- Robert C. Martin 《Clean Architecture》

譯:軟件架構的終極目標是,用最小的人力成本來滿足構建和維護該系統的需求

如果剛開始我們直接以數據驅動面向過程的流程式代碼,可以很輕松的解決問題,并且之后也不會面向更復雜的場景與業務,那這套模式就是最適合這套系統的架構設計。如果我們的系統會隨著業務的發展逐漸復雜,每一次的發布都會提升下一次發布的成本,那么我們應該考慮投入必要的成本來面向領域驅動設計。

四 抽象的品質

抽象永遠是軟件工程領域最難的命題,因為它沒有規則,沒有標準,甚至沒有對錯,只分好壞,只分是否適合。同樣一份淘寶商品模型的領域抽象,可以算是業界標桿了,但它并非適合你的系統。那我們該如何駕馭“抽象”呢?UML的創始人Grady booch在 Object Oriented Analysis and Design with Applications 一書中,提到了評判一種抽象的品質可以通過如下5個指標進行測量:耦合性、內聚性、充分性、完整性與基礎性。

1 耦合性

一個模塊與另一個模塊之間建立起來的關聯強度的測量稱之為耦合性。一個模塊與其他模塊高度相關,那它就難以獨立得被理解、變化或修改。TCL語言發明者John Ousterhout教授也有同樣的觀點。我們應該盡可能減少模塊間的耦合依賴,從而降低復雜度。

Complexity is caused by two things: dependencies and obscurity.

--- John Ousterhout 《A Philosophy of Software Design》

譯:復雜性是由兩件事引起的:依賴性和模糊性。

但這并不意味著我們就不需要耦合。軟件設計是朝著擴展性與復用性發展的,繼承天然就是強耦合,但它為我們提供了軟件系統的復用能力。如同摩擦力一般,起初以為它阻礙了我們前進的步伐,實則沒有摩擦力,我們寸步難行。

2 內聚性

內聚性與耦合性都是結構化設計中的概念,內聚性測量的是單個模塊里,各個元素的的聯系程度。高內聚低耦合,是寫在教科書里的觀點,但我們也并非何時何地都應該盲目追求高內聚。

內聚性分為偶然性內聚與功能性內聚。金魚與消防栓,我們一樣可以因為它們都不會吹口哨,將他們抽象在一起,但很明顯我們不該這么干,這就是偶然性內聚。最希望出現的內聚是功能性內聚,即一個類或模式的各元素一同工作,提供某種清晰界定的行為。比如我將消防栓、滅火器、探測儀等內聚在一起,他們是都屬于消防設施,這是功能性內聚。

3 充分性

充分性指一個類或模塊需要應該記錄某個抽象足夠多的特征,否則組件將變得不用。比如Set集合類,如果我們只有remove、get卻沒有add,那這個類一定沒法用了,因為它沒有形成一個閉環 。不過這種情況相對出現較少,只要當我們真正去使用,完成它的一系列流程操作后,缺失的一些內容是比較容易發現并解決的。

4 完整性

完整性指類或模塊需要記錄某個抽象全部有意義的特征。完整性與充分性相對,充分性是模塊的最小內涵,完整性則是模塊的最大外延。我們走完一個流程,可以清晰得知道我們缺哪些,可以讓我們馬上補齊抽象的充分性,但可能在另一個場景這些特征就又不夠了,我們需要考慮模塊還需要具備哪些特征或者他應該還補齊哪些能力。

5 基礎性

充分性、完整性與基礎性可以說是3個相互輔助相互制約的原則。基礎性指抽象底層表現形式最有效的基礎性操作(似乎用自己在解釋自己)。比如Set中的add操作,是一個基礎性操作,在已經存在add的情況下,我們是否需要一次性添加2個元素的add2操作?很明顯我們不需要,因為我們可以通過調用2次add來完成,所以add2并不符合基礎性。

但我們試想另一個場景,如果要判斷一個元素是否在Set集合中,我們是否需要增加一個contains方法。Set已經有foreach、get等操作了,按照基礎性理論,我們也可以把所有的元素遍歷一遍,然后看該元素是否包含其中。但基礎性有一個關鍵詞叫“有效”,雖然我們可以通過一些基礎操作進行組合,但它會消耗大量資源或者復雜度,那它也可以作為基礎操作的一個候選者。

五 軟件設計原則

抽象的品質可以指導我們抽象與建模,但總歸還是不夠具象,在此基礎上一些更落地更易執行的設計原則涌現出來,最著名的當屬面向對象的五大設計原則 S.O.L.I.D。

1 開閉原則OCP

Software entities should be open for extension,but closed for modification

-- Bertrand Meyer 《Object Oriented Software Construction》

譯:軟件實體應當對擴展開放,對修改關閉。

開閉原則是Bertrand Meyer 1988年在 Object Oriented Software Construction 書中所提到一個觀點,軟件實體應該對擴展開放對修改關閉。

我們來看一個關于開閉原則的例子,需要傳進來的用戶列表,分類型進行二次排序,我們代碼可以這樣寫。

  1. public List<User> sort(List<User> users, Enum type){
  2. if(type == AGE){
  3. // 按年齡排序
  4. users = resortListByAge(users);
  5. }else if(type == NAME){
  6. // 按名稱首字母排序
  7. users = resortListByName(users);
  8. }else if(type == NAME){
  9. // 按客戶健康分排序
  10. users = resortListByHealth(users);
  11. }
  12. return users;
  13. }

上述代碼就是一個明顯違背開閉原則的例子,當我們需要新增一種類似時,需要修改主流程。由于這些方法都定義在私有函數中,我們哪怕對現有邏輯做調整,我們也需要修改到這份代碼文件。

還有一種做法,可以實現對擴展開放對修改關閉,JDK的排序其實已經為我們定義了這樣的標準。我們將不同的排序方式進行抽象,每種邏輯單獨實現,單個調整邏輯不影響其他內容,新增排序方式也無需對已有模塊進行調整。

重拾面向對象軟件設計

2 依賴倒置DIP

High level modules shouldnot depend upon low level modules.Both should depend upon abstractions. Abstractions should not depend upon details. Details should depend upon abstractions

--- Robert C.Martin C++ Report 1996

譯:高層模塊不應該依賴低層模塊,兩者都應該依賴抽象;抽象不應該依賴細節,細節應該依賴抽象。

Robert C.Martin是《Clean Code》《Code Architecture》兩本經典書籍的作者,1996年他在C++ Report中發表了一篇名為 The Dependency Inversion Principle 的文章。他認為模塊間的依賴應該是有序的,高層不應該依賴低層,低層應該依賴高層,抽象不應該依賴細節,細節應該依賴抽象。

怎么理解Robert C.Martin的這一觀點。我們看這張圖,我們的手可以握住這個杯子,是我們依賴杯子嗎?有人說我們需要調杯子提供的hold服務,我們才能握住它,所以是我們依賴杯子。但我們再思考一下,棍子我們是不是也可以握,水壺我們也可以握,但貓狗卻不行,為什么?因為我們的杯子是按照我們的手型進行設計的,我們定義了一個可握持的holdable接口,杯子依賴我們的需求進行設計。所以是杯子依賴我們,而非我們依賴杯子。

重拾面向對象軟件設計

依賴倒置原則并非一個新創造的理論,我們生活的很多地方都有在運用。比如一家公司需要設立“法人”,如果這家公司出了問題,監管局就會找公司法人。并非監管局依賴公司提供的法人職位,它可以找到人,而是公司依賴監管局的要求,才設立法人職位。這也是依賴倒置的一種表現。

3 其他設計原則

這里沒有一一將 S.O.L.I.D 一一列舉完,大家想了解的可以自行查閱。除了SOLID之外,還有一些其他的設計原則,同樣也非常優秀。

PLOA最小驚訝原則

If a necessary feature has a high astonishment factor, it may be necessary to redesign the feature

-- Michael F. Cowlishaw

譯:如果必要的特征具有較高的驚人因素,則可能需要重新設計該特征。

PLOA最小驚訝原則是斯坦福大家計算機教授 Michael F. Cowlishaw 提出的。不管你的代碼有“多好”,如果大部分人都對此感到吃驚,或許我們應該重新設計它。JDK中就存在一例違反PLOA原則的案例,我們來看下面這段代碼。

  1. /**
  2. * Set a Formatter. This Formatter will be used
  3. * to format LogRecords for this Handler.
  4. *

  5. * Some Handlers may not use Formatters, in
  6. * which case the Formatter will be remembered, but not used.
  7. *

  8. * @param newFormatter the Formatter to use (may not be null)
  9. * @exception SecurityException if a security manager exists and if
  10. * the caller does not have LoggingPermission("control").
  11. */
  12. public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
  13. checkPermission();
  14. // Check for a null pointer:
  15. newFormatter.getClass();
  16. formatter = newFormatter;
  17. }

在分享會上,我故意將這行注釋遮蓋起來,大家都猜不到 newFormatter.getClass() 這句代碼寫在這里的作用。如果要檢查空指針,完全可以用Objects工具類提供的方法,實現完全一樣,但代碼表現出來的含義就千差萬別了。

  1. public static T requireNonNull(T obj) {
  2. if (obj == null)
  3. throw new NullPointerException();
  4. return obj;
  5. }

KISS簡單原則

Keep it Simple and Stupid

-- Robert S. Kaplan

譯:保持愚蠢,保持簡單

KISS原則是 Robert S. Kaplan 提出的一個理論,Kaplan并非是一個軟件學家,他是平衡積分卡Balanced Scorecard創始人,而他所提出的這個理論對軟件行業依然適用。把事情變復雜很簡單,把事情變簡單很復雜。我們需要盡量讓復雜的問題簡明化、簡單化。

六 寫在最后

軟件設計的最大目標,就是降低復雜性,萬物不為我所有,但萬物皆為我用。引用JDK集合框架創辦人Josh Bloch 的一句話來結束。學習編程藝術首先要學會基本的規則,然后才能知道什么時候可以打破這些規則。

You should not slavishly follow these rules, but violate them only occasionally and with good reason. Learning the art of programming, like most other disciplines, consists of first learning the rules and then learning when to break them.

--- Josh Bloch 《Effective Java》

譯:你不該盲目的遵從這些規則,應該只在偶爾情況下,有充分理由后才去打破這些規則

學習編程藝術首先要學會基本的規則,然后才能知道什么時候可以打破這些規則

參閱書籍

1、《Object Oriented Analysis and Design with Applications》https://niexiaolong.github.io/Object%20Oriented%20Analysis%20and%20Design%20with%20Applications.pdf

2、《Clean Architecture》

https://detail.tmall.com/item.htm?id=654392764249

3、《A Philosophy of Software Design》

https://www.amazon.com/-/zh/dp/173210221X/ref=sr_1_1?qid=1636246895

原文鏈接:https://zhuanlan.51cto.com/art/202111/692205.htm#topx

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 欧美1区 | 成年人视频在线免费看 | 欧美一级精品 | 久久一本岛在免费线观看2020 | 色哟哟国产成人精品 | 国产亚洲欧美一区二区三区 | 国产综合社区 | 欧美猛男同志video在线 | 日韩高清在线免费看 | 亚洲精品91香蕉综合区 | 91亚洲专区 | 国产图色 | 麻豆最新地址 | 国产精品久久久久网站 | 久久亚洲精品AV成人无码 | 男人添女人 | 久久不卡免费视频 | 国产精品猎奇系列在线观看 | 娇妻在床上迎合男人 | 嫩草影院精品视频在线观看 | 999热这里只有精品 999久久久免费精品国产牛牛 | 国产精品亚洲午夜不卡 | 被巨大黑人的翻白眼 | 久久国产精品高清一区二区三区 | 精品蜜臀AV在线天堂 | 日韩精品免费一区二区 | 成人免费草草视频 | 高清在线免费观看 | 69re在线观看 | 鞋奴的视频VK | 色老板在线观看 | 男人边吃奶边做好爽视频免费 | 久久精品国产清白在天天线 | 成年人在线免费看 | 好男人在线观看免费高清2019韩剧 | 91久久精品国产亚洲 | a级精品九九九大片免费看 a级动漫 | 午夜黄视频 | 性关系视频网站 | 1769在线视频| 门房秦大爷在线阅读 |