
我眼中的面向對象分析
面向對象
似乎我也沒學過其他的編程思維方式了,面向對象是我編程時常用的思維方式,因為我覺得它更加貼近于我們的生活,更加容易地去理解和定義程序想要表達的內容。正是因為如此,每當項目要開啟的時候,我都會使用該種思維來分析和設計程序。多年下來發現它確實有著它的魅力,幫助我解決了很多設計中的問題。于是我總結了一下,在下面章節中說說面向對象使用上的一些心得。
給你的程序分“類”
面向對象的基礎就是“類”的設計,其設計好壞直接影響到程序的結構。那么,如何才能設計出合理的類型呢?本人覺得應該從實際需求出發,提取需求中各種實體對象,最終形成程序中使用的對象。某些同學喜歡為自己的程序埋點,添加很多為日后作擴展的類型,其實我是不建議過度設計,我覺得可以在需求中提及的實體預留一些功能性的擴展,但是不建議為需求中沒有的功能點設計類型,因為,你永遠都把握不準產品日后的發展會是什么樣子的,最終形態是怎么樣,也有可能你埋下的點還沒來得及實現,程序就需要進行重構了等等的情況都會導致你的額外工作變成無用功。
假如公司要求你做一個附近的陌生人交友的App。從這個App的需求,可以提取出附近、陌生人以及交友三個關鍵字,附近描述的是一種相對的地理位置所以它應該是一個類的屬性,而陌生人也是相對于某個人來說的一種人與人的關系,所以這里會被分解為一個人的實體,還有一個與其它人實體的關系。而最后一個交友關鍵字其實隱含了一種建立關系的行為,這里可以是聊天,也可以是其它的交友方式。我們再進一步分析,如果App里面提供了聊天功能,那么需求中還隱含了一個實體,那就是聊天的內容。所以,根據需求這些點,我們可以設計出兩個類型:
這就由需求所驅動產生的類型,以這些類型為核心類圍繞需求進行補充和擴展來不斷滿足后續的需求。
讓類型也符合數據庫實體設計
關系數據庫設計中常會提及第三范式,其特點是去除表中直接依賴。這意思是說兩個實體對象不應該被設計在同一個表中,如:評論表中有記錄是某個用戶發表的評論,可能會把用戶名稱等信息歸類到評論表中,那這樣是不符合第三范式要求的,正確的做法是把用戶信息放入用戶表,評論表中可以保留一個用戶id來標識用戶表中的用戶記錄,從而取消在評論表中直接依賴用戶信息,導致用戶信息的冗余。
其實我個人覺得這種范式要求很好地劃分了實體之間的界線,對于類的設計也同樣適用。譬如:你定義了一個人類,該類型有四肢屬性和說話的行為。這樣很好理解,因為現實中人就具備這些特性。但是在后續的開發過程中由于需求的變動,出現了人類因為有飛行道具可以進行飛行。而把飛行的行為直接賦予人類,這種圖省事的做法其實是存在很多弊端的:
首先是不容易讀懂,因為常識性的認為人是不會飛的,人會飛的話那肯定是個鳥人。所以會讓人摸不著頭腦。
再者就是這樣的定義往往會限制其它跟你協作開發的同事。因為大多數情況下我們都會秉持著能盡量不動舊代碼就不要去動,以免動出問題來。那么你這樣的設計就讓人不得不按照現有的思維去進行擴展。直到哪一天是在沒有辦法了,再進行徹底的重構。
所以更優的做法應該是定義出一個道具類。至于道具實現什么功能,由其子類實現:
// 道具基類
class Item
{
//道具的行為
function action(target : Person);
}
//飛行道具
class FlyingItem :Item
{
function action(target : Person)
{
//飛行的實現代碼
}
}
類似這樣的實現就可以分離人類和道具類型的耦合,而且也提高了道具類型的擴展。所以,在設計類型時一定要給實體分清界線。
另外一個數據庫設計時使用較多的就是實體的關系描述。實體的關系不同,在設計表存儲數據時也有所不同。通常有三種實體關系:
一對一,即兩個實體之間的關系是一一對應的。如:一個中國公民跟他的身份證是唯一綁定的,知道身份證就能夠找到對應的人,同樣對應的人也能夠找到與其對應的唯一的身份證。類似這種關系,通常是建立兩張實體數據表,然后關系可以同時放兩個實體表中。
一對多,即兩種實體之間,第一種實體可以對應多個第二種實體,但是第二種實體只能對應唯一的第一種實體。如用戶評論的例子,用戶可以發多條評論,但是一條評論只能夠是特定的一個用戶發出來。對于這種關系,通常也是建立兩張實體數據表,然后會在對應多個實體的數據表中記錄關系,拿上面的例子來說,就是用戶表和評論表,然后評論表會放入一個userId之類的標識來記錄與用戶的對應關系。
多對多,即兩種實體之間,第一種實體可以對應多個第二種實體,第二種實體也可以對應多個第一種實體。例如作者和書籍的關系就是一種多對多的關系,一個作者可以寫多本書,同時,一本書有可能有多個作者共同寫成。對于這種關系,則需要建立兩種實體數據表以及一張關系表來進行描述。
說了這么多實體關系的東西,其實最想說的一點就是,在進行面向對象分析和設計時,關系的分析也是不可缺少的。但是需要怎么來評估關系的劃分和歸屬,我覺得按照數據庫的關系設計標準來進行也是一種不錯的方法。
一對一,在該種關系下,其實關系的描述可以同時放在兩個類型上,如上面所說的中國公民與身份證的例子,用類可以描述為:
class Chinese
{
//姓名
property name:String;
//對應的身份證
property idCard:IDCard;
}
class IDCard
{
//身份證號
property id:String;
//對應的人
property person:Chinese;
}
一對多,該種關系下,稍微與數據庫表設計有點不同,由于數據表可以通過查詢的方式來找出實體對應的多個另一種實體,這已經超出了類設計階段的范疇。因此,對應多個類實例的類型需要定義一個集合的屬性來保留對應的多個類實例。這樣在閱讀代碼時也能夠很好理解,拿用戶評論的例子來說:
class User
{
property comments:Set<Comment>;
}
class Comment
{
property user:User;
}
類似這樣能夠很好的描述用戶進行過多少的評論,然后哪條評論是哪個用戶評論的。
多對多,這是一種很復雜的關系,主要是很多情況下,我們都會忽略這樣的一種關系,把它直接給變成了一對多的關系設計。其實對于種情況我們都可以將其關系的處理交由另外一個類型進行處理,與數據庫表設計一樣,兩個實體類型,一個關系類型。拿剛才作者跟書籍的關系為例,可以設計成下面的樣子:
//作者書籍關系管理類型
class AuthorBookRelationshipManager
{
//設置書籍的作者信息
static function setBookAuthor(book : Book, authors : Array<Author>) : void;
//獲取指定書籍的作者
static function getAuthors(book : Book) : Array<Author>;
//獲取指定作者的書籍
static function getBooks(author : Author) : Array<Book>;
}
//作者類型
class Author
{
//作者名稱
property name : String;
//獲取作者出版過的書籍
function getBooks() : Array<Book>
{
return AuthorBookRelationshipManager.getBooks(this);
}
}
//書籍類型
class Book
{
//書籍名稱
property name : String;
//獲取書籍的作者信息
function getAuthors():Array<Author>
{
return AuthorBookRelationshipManager.getAuthors(this);
}
}
上面的代碼可以看出,使用了一個AuthorBookRelationshipManager類型來管理書籍和作者的關系。然后實體類型不再保存之間的關系,要獲取相關的實體需要通過AuthorBookRelationshipManager類型來獲取。
上面所說的實體劃分和關系劃分在面向對象中有著非常重要的意義。平時要多加練習才能夠在面臨實際項目開發時做出合理的分類,讓自己開發的架構更加靈活更加強大。
以上對于類型的設計基本上是講完了,是時候要說說繼承方面的事情了,這是面向對象的一個很強大的特性。它可以改善代碼結構的問題,節省代碼的書寫工作量。下面將會圍繞它來聊聊我的看法。
萬物的始祖
其實不管是寫C++、C#還是寫Java等等這些高級語言,我們都會發現只要是創建的類它們都會直接或者間接地繼承自Object這個類。而這個Object類就是我這要說的萬物的始祖,其被所有的事物所繼承是一個根類型。Object這個名字也起得相當好,泛指了所有的物體,給予了一種無形態的抽象的定義。意味著我們作為程序中的造物主,需要將這種無形態的物體,變化成各種具體形態的事物,這就需要繼承。
那么,Object中所做的功能其實并不多,因為它不涉及具體的功能,因此它很多時候在實現上面僅僅能區分是否是同一個對象isEqual,又或者是告訴我們它的描述toString。但是這樣已經是有很大的引導意義,因為只要繼承了Object類型,那么你的類型將會得到這樣功能,闡述了繼承功能的強大;同時,你還可以把你的子類型對象賦予給一個Object的實例,因為Object是根類,它可以保留任意的子類類型對象,這是面向對象多態的基本體現。
有了Object這樣的一個例子,更加表明了我們在開發項目過程中也應該為項目的代碼設計基類。而這里的基類要比Object的功能稍微具體一點。因為它涉及到具體要實現的功能需求,而且還應該不止是一個基類。下面來說一下我是怎么樣設計基類的。
和基類來個約定
要怎么設計基類,其實還是要根據實際的項目需求而定。假設你所開發的項目包含很多的后臺服務,那么就應該定義一個叫BaseService的基類,然后通過這個基類來分化成多種服務類型。如果你的項目涉及到多種UI的處理,那么你就應該考慮可能需要一個基礎的UI管理類型(通常UI組件有自己的基類,這里需要的是對UI進行管理和維護的基礎類型),如:UIController。然后根據這個基類分化出各種的UI展現。
基本上是根據如果程序存在多種特性相近或相似的功能模塊,那么就應該為這些模塊建立基類,以便日后更好的進行擴展。這里不建議要給未來有可能會有多種近似功能的模塊定義基類,還是老話,你的假設不一定成立。而且等到出現這樣的需求在進行定義也不遲,畢竟要改造的也只是一個類型而已。
上面我們知道如何去抽象出基類,那么基類應該要如何封裝屬性和方法,其實完全可以視其子類而定,因為這樣是最貼近需求的。如前面所說的多種后臺服務,假設有如下服務類型:
class ServiceA
{
//服務名稱
property name:String;
//調用服務
function exec(config:Dicationary):void
{
//執行服務
}
}
class ServiceB
{
//服務名稱
property name:String;
//調用服務
function call():void
{
//執行服務
}
}
上面所描述的服務從代碼結構上面其實不大相同,特別是在執行服務的行為上接收參數也不一樣。如果要從它們身上抽象出基類,確實會讓人有所困惑。但是,我們既然要抽象基類,那么就肯定會改造子類。我自己總結一些抽象的規則:
子類中相同的屬性和方法,要封裝到基類中。
對于大多數子類存在的屬性和方法(如5個子類中有3個以上類型存在相同的屬性和方法),可以考慮封裝到基類中,沒用到類型可以對基類的屬性和方法進行忽略。
對于意義相近的屬性,可以考慮將屬性合并或替換(如:子類中分別用id或者name屬性來表示對象的唯一,那么基類可以考慮只取其中一項屬性或者創建一個集合兩者的屬性定義)。
對于子類相同行為的方法,如果聲明的方法參數的數量或類型不同時,可以考慮基類的方法集合子類該方法中的所有參數(即取方法參數的并集)或者考慮定義共有參數,特殊參數則由子類轉換為屬性來實現(一般可以持續持有的參數可以這樣設計,如系統的配置)。
基于上面所說的,我們可以把ServiceA和ServiceB,通過抽象基類改成下面的樣子:
class BaseService
{
//服務名稱
property name:String;
//調用服務
function exec():void;
}
class ServiceA extends BaseService
{
//這里將參數設定為屬性
property config:Dictionary;
function exec():void
{
//執行服務
}
}
class ServiceB extends BaseService
{
function exec():void
{
//執行服務
}
}
類似這樣,我們就可以把基類給抽象出來了。
讓類型進化
在開發和維護代碼的過程里面,難免會碰到由于產品的迭代,導致需求發生重大的變更。那么可能會出現某些功能的廢棄或者功能的合并或演進。這時候,之前定義的類型就要面臨著重構或者調整的命運。
遇到這樣的問題我們先別急著把之前的東西全盤否定,然后徹底重寫。而是先考慮之前的定義中是否還存在可用的東西。要把可以重用的模塊給提取出來,那么,模塊的取舍就是我們需要評估的事情。
如果新的需求中已經廢棄的功能,那么模塊所涉及的類型都可以進行廢棄。對于這些模塊的處理,我個人比較喜歡對相關的類型進行直接刪除,然后進行編譯,報錯后直接修改引用到該類型的代碼,直到編譯成功為止。對于缺失該類型后需要改造的方法,我會暫時不進行改造,而是打上標記(如TODO或者warning宏),等待正式開發新功能時再尋找會這些標記一一進行解決。
如果新需求中包含該類功能,但是又融合了一些新的元素,那么,我們的類型還不能直接拿來使用。要評估新的元素是否作為類型的一部分,還是作一種新的類型。例如:一款聊天應用,剛開始的時候只有私聊(一對一聊天),那么,發送消息就在User類型中:
//用戶類型
class User
{
//給某個用戶發送消息
function sendMessage(user : User, message : Message) : void;
}
到了第二個版本的時候,可能支持用戶給好友群發消息。那么,明顯上面的代碼不能滿足需求,這時候需要對類型進行改造:
//用戶類型
class User
{
//給某個用戶發送消息
function sendMessage(user : User, message : Message) : void;
//給多個用戶發送消息
function sendMessage(users : Arrray<User>, message : Message) : void
{
for (User user in users)
{
this.sendMessage(user, message);
}
}
}
增加了一個sendMessage多態版本的方法用于群發消息。這樣既能保證方法的出口統一,又能保證之前的方法不被修改,就可以輕松地解決群發問題。
那么,到了第三個版本的時候出現了聊天室的概念,這時候需求中多了一個聊天室的關鍵字,運用之前提到的方法,這里應該需要新增一個類型,不能直接在User類型中進行擴展。那么,可以設計為:
//聊天室
class ChatRoom
{
//聊天室名稱
property name:String;
//聊天室用戶
property users:Array<User>;
//發送消息
function sendMessage(user:User, message:Message):void;
}
這里增加了一個ChatRoom的類型,主要是用來記錄那些人在哪個聊天室中。該類型有一個sendMessage的方法,用于表示哪個用戶要在聊天室中發言,發言的內容是什么。
由上面的例子可以看出來,產品迭代有時候需要讓類型進行,有時候也需要誕生新的類型。要做那種選擇則需要根據產品需求來決定。
只要這種多態
多態在我的理解中就是多種狀態。就好比我們用手拿東西的時候,如果拿的是球,那么我們可能是想要打球,如果拿的是蘋果,那么我們可能要把它吃掉。根據不同東西我們可能會作出不同的反映。在面向對象中多態就是用于解決這類的問題。我們來假設一下沒有多態的時候,我們的代碼看起來會是多挫:
function doSomething(obj:Object):void
{
if (obj instanceof ObjectA)
{
//do something by ObjectA
}
else if (obj instanceof ObjectB)
{
//do something by ObjectB
}
}
要判斷的類型越多,if的語句就越長。那么有了多態后,我們可以很優雅地設計:
function doSomething(obj:ObjectA):void
{
//do something by ObjectA
}
function doSomething(obj:ObjectB):void
{
//do something by ObjectB
}
多態可以保證代碼設計的出口名字統一,不需要外部調用的人要根據不同的類型調用不同名字的方法,對于外部調用只需要傳入的類型不同即可決定要調用哪個方法。
如果不是解決上面所說的問題,我不建議使用多態。因為多態會使到類型變得復雜,如果這個使用多態的類型被繼承,然后繼承的子類進行了多態處理,那么會影響到程序的質量,并且一旦出現問題,排查的難度會有所增加。
后話
以上說的都是我個人在這些年的開發中所理解的東西,面向對象這東西已經存在我腦海里面許多個日夜了,總想著寫些什么,今天總算把它給完成了。后續我會繼續寫下其他的一些關于程序思維的文章,希望大家支持。
寫這篇文章的時候沒有參考其他資料,可能存在錯漏的地方。如果你是一位好心的猿/媛,麻煩給我指正一下。
數據分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
CDA數據分析師證書考試體系(更新于2025年05月22日)
2025-05-26解碼數據基因:從數字敏感度到邏輯思維 每當看到超市貨架上商品的排列變化,你是否會聯想到背后的銷售數據波動?三年前在零售行 ...
2025-05-23在本文中,我們將探討 AI 為何能夠加速數據分析、如何在每個步驟中實現數據分析自動化以及使用哪些工具。 數據分析中的AI是什么 ...
2025-05-20當數據遇見人生:我的第一個分析項目 記得三年前接手第一個數據分析項目時,我面對Excel里密密麻麻的銷售數據手足無措。那些跳動 ...
2025-05-20在數字化運營的時代,企業每天都在產生海量數據:用戶點擊行為、商品銷售記錄、廣告投放反饋…… 這些數據就像散落的拼圖,而相 ...
2025-05-19在當今數字化營銷時代,小紅書作為國內領先的社交電商平臺,其銷售數據蘊含著巨大的商業價值。通過對小紅書銷售數據的深入分析, ...
2025-05-16Excel作為最常用的數據分析工具,有沒有什么工具可以幫助我們快速地使用excel表格,只要輕松幾步甚至輸入幾項指令就能搞定呢? ...
2025-05-15數據,如同無形的燃料,驅動著現代社會的運轉。從全球互聯網用戶每天產生的2.5億TB數據,到制造業的傳感器、金融交易 ...
2025-05-15大數據是什么_數據分析師培訓 其實,現在的大數據指的并不僅僅是海量數據,更準確而言是對大數據分析的方法。傳統的數 ...
2025-05-14CDA持證人簡介: 萬木,CDA L1持證人,某電商中廠BI工程師 ,5年數據經驗1年BI內訓師,高級數據分析師,擁有豐富的行業經驗。 ...
2025-05-13CDA持證人簡介: 王明月 ,CDA 數據分析師二級持證人,2年數據產品工作經驗,管理學博士在讀。 學習入口:https://edu.cda.cn/g ...
2025-05-12CDA持證人簡介: 楊貞璽 ,CDA一級持證人,鄭州大學情報學碩士研究生,某上市公司數據分析師。 學習入口:https://edu.cda.cn/g ...
2025-05-09CDA持證人簡介 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度、美團、阿里等 ...
2025-05-07相信很多做數據分析的小伙伴,都接到過一些高階的數據分析需求,實現的過程需要用到一些數據獲取,數據清洗轉換,建模方法等,這 ...
2025-05-06以下的文章內容來源于劉靜老師的專欄,如果您想閱讀專欄《10大業務分析模型突破業務瓶頸》,點擊下方鏈接 https://edu.cda.cn/g ...
2025-04-30CDA持證人簡介: 邱立峰 CDA 數據分析師二級持證人,數字化轉型專家,數據治理專家,高級數據分析師,擁有豐富的行業經驗。 ...
2025-04-29CDA持證人簡介: 程靖 CDA會員大咖,暢銷書《小白學產品》作者,13年頂級互聯網公司產品經理相關經驗,曾在百度,美團,阿里等 ...
2025-04-28CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-27數據分析在當今信息時代發揮著重要作用。單因素方差分析(One-Way ANOVA)是一種關鍵的統計方法,用于比較三個或更多獨立樣本組 ...
2025-04-25CDA持證人簡介: 居瑜 ,CDA一級持證人國企財務經理,13年財務管理運營經驗,在數據分析就業和實踐經驗方面有著豐富的積累和經 ...
2025-04-25