
我眼中的面向?qū)ο蠓治?/span>
面向?qū)ο?
似乎我也沒學過其他的編程思維方式了,面向?qū)ο笫俏揖幊虝r常用的思維方式,因為我覺得它更加貼近于我們的生活,更加容易地去理解和定義程序想要表達的內(nèi)容。正是因為如此,每當項目要開啟的時候,我都會使用該種思維來分析和設計程序。多年下來發(fā)現(xiàn)它確實有著它的魅力,幫助我解決了很多設計中的問題。于是我總結(jié)了一下,在下面章節(jié)中說說面向?qū)ο笫褂蒙系囊恍┬牡谩?
給你的程序分“類”
面向?qū)ο蟮幕A就是“類”的設計,其設計好壞直接影響到程序的結(jié)構(gòu)。那么,如何才能設計出合理的類型呢?本人覺得應該從實際需求出發(fā),提取需求中各種實體對象,最終形成程序中使用的對象。某些同學喜歡為自己的程序埋點,添加很多為日后作擴展的類型,其實我是不建議過度設計,我覺得可以在需求中提及的實體預留一些功能性的擴展,但是不建議為需求中沒有的功能點設計類型,因為,你永遠都把握不準產(chǎn)品日后的發(fā)展會是什么樣子的,最終形態(tài)是怎么樣,也有可能你埋下的點還沒來得及實現(xiàn),程序就需要進行重構(gòu)了等等的情況都會導致你的額外工作變成無用功。
假如公司要求你做一個附近的陌生人交友的App。從這個App的需求,可以提取出附近、陌生人以及交友三個關鍵字,附近描述的是一種相對的地理位置所以它應該是一個類的屬性,而陌生人也是相對于某個人來說的一種人與人的關系,所以這里會被分解為一個人的實體,還有一個與其它人實體的關系。而最后一個交友關鍵字其實隱含了一種建立關系的行為,這里可以是聊天,也可以是其它的交友方式。我們再進一步分析,如果App里面提供了聊天功能,那么需求中還隱含了一個實體,那就是聊天的內(nèi)容。所以,根據(jù)需求這些點,我們可以設計出兩個類型:
這就由需求所驅(qū)動產(chǎn)生的類型,以這些類型為核心類圍繞需求進行補充和擴展來不斷滿足后續(xù)的需求。
讓類型也符合數(shù)據(jù)庫實體設計
關系數(shù)據(jù)庫設計中常會提及第三范式,其特點是去除表中直接依賴。這意思是說兩個實體對象不應該被設計在同一個表中,如:評論表中有記錄是某個用戶發(fā)表的評論,可能會把用戶名稱等信息歸類到評論表中,那這樣是不符合第三范式要求的,正確的做法是把用戶信息放入用戶表,評論表中可以保留一個用戶id來標識用戶表中的用戶記錄,從而取消在評論表中直接依賴用戶信息,導致用戶信息的冗余。
其實我個人覺得這種范式要求很好地劃分了實體之間的界線,對于類的設計也同樣適用。譬如:你定義了一個人類,該類型有四肢屬性和說話的行為。這樣很好理解,因為現(xiàn)實中人就具備這些特性。但是在后續(xù)的開發(fā)過程中由于需求的變動,出現(xiàn)了人類因為有飛行道具可以進行飛行。而把飛行的行為直接賦予人類,這種圖省事的做法其實是存在很多弊端的:
首先是不容易讀懂,因為常識性的認為人是不會飛的,人會飛的話那肯定是個鳥人。所以會讓人摸不著頭腦。
再者就是這樣的定義往往會限制其它跟你協(xié)作開發(fā)的同事。因為大多數(shù)情況下我們都會秉持著能盡量不動舊代碼就不要去動,以免動出問題來。那么你這樣的設計就讓人不得不按照現(xiàn)有的思維去進行擴展。直到哪一天是在沒有辦法了,再進行徹底的重構(gòu)。
所以更優(yōu)的做法應該是定義出一個道具類。至于道具實現(xiàn)什么功能,由其子類實現(xiàn):
// 道具基類
class Item
{
//道具的行為
function action(target : Person);
}
//飛行道具
class FlyingItem :Item
{
function action(target : Person)
{
//飛行的實現(xiàn)代碼
}
}
類似這樣的實現(xiàn)就可以分離人類和道具類型的耦合,而且也提高了道具類型的擴展。所以,在設計類型時一定要給實體分清界線。
另外一個數(shù)據(jù)庫設計時使用較多的就是實體的關系描述。實體的關系不同,在設計表存儲數(shù)據(jù)時也有所不同。通常有三種實體關系:
一對一,即兩個實體之間的關系是一一對應的。如:一個中國公民跟他的身份證是唯一綁定的,知道身份證就能夠找到對應的人,同樣對應的人也能夠找到與其對應的唯一的身份證。類似這種關系,通常是建立兩張實體數(shù)據(jù)表,然后關系可以同時放兩個實體表中。
一對多,即兩種實體之間,第一種實體可以對應多個第二種實體,但是第二種實體只能對應唯一的第一種實體。如用戶評論的例子,用戶可以發(fā)多條評論,但是一條評論只能夠是特定的一個用戶發(fā)出來。對于這種關系,通常也是建立兩張實體數(shù)據(jù)表,然后會在對應多個實體的數(shù)據(jù)表中記錄關系,拿上面的例子來說,就是用戶表和評論表,然后評論表會放入一個userId之類的標識來記錄與用戶的對應關系。
多對多,即兩種實體之間,第一種實體可以對應多個第二種實體,第二種實體也可以對應多個第一種實體。例如作者和書籍的關系就是一種多對多的關系,一個作者可以寫多本書,同時,一本書有可能有多個作者共同寫成。對于這種關系,則需要建立兩種實體數(shù)據(jù)表以及一張關系表來進行描述。
說了這么多實體關系的東西,其實最想說的一點就是,在進行面向?qū)ο蠓治龊驮O計時,關系的分析也是不可缺少的。但是需要怎么來評估關系的劃分和歸屬,我覺得按照數(shù)據(jù)庫的關系設計標準來進行也是一種不錯的方法。
一對一,在該種關系下,其實關系的描述可以同時放在兩個類型上,如上面所說的中國公民與身份證的例子,用類可以描述為:
class Chinese
{
//姓名
property name:String;
//對應的身份證
property idCard:IDCard;
}
class IDCard
{
//身份證號
property id:String;
//對應的人
property person:Chinese;
}
一對多,該種關系下,稍微與數(shù)據(jù)庫表設計有點不同,由于數(shù)據(jù)表可以通過查詢的方式來找出實體對應的多個另一種實體,這已經(jīng)超出了類設計階段的范疇。因此,對應多個類實例的類型需要定義一個集合的屬性來保留對應的多個類實例。這樣在閱讀代碼時也能夠很好理解,拿用戶評論的例子來說:
class User
{
property comments:Set<Comment>;
}
class Comment
{
property user:User;
}
類似這樣能夠很好的描述用戶進行過多少的評論,然后哪條評論是哪個用戶評論的。
多對多,這是一種很復雜的關系,主要是很多情況下,我們都會忽略這樣的一種關系,把它直接給變成了一對多的關系設計。其實對于種情況我們都可以將其關系的處理交由另外一個類型進行處理,與數(shù)據(jù)庫表設計一樣,兩個實體類型,一個關系類型。拿剛才作者跟書籍的關系為例,可以設計成下面的樣子:
//作者書籍關系管理類型
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類型來獲取。
上面所說的實體劃分和關系劃分在面向?qū)ο笾杏兄浅V匾囊饬x。平時要多加練習才能夠在面臨實際項目開發(fā)時做出合理的分類,讓自己開發(fā)的架構(gòu)更加靈活更加強大。
以上對于類型的設計基本上是講完了,是時候要說說繼承方面的事情了,這是面向?qū)ο蟮囊粋€很強大的特性。它可以改善代碼結(jié)構(gòu)的問題,節(jié)省代碼的書寫工作量。下面將會圍繞它來聊聊我的看法。
萬物的始祖
其實不管是寫C++、C#還是寫Java等等這些高級語言,我們都會發(fā)現(xiàn)只要是創(chuàng)建的類它們都會直接或者間接地繼承自Object這個類。而這個Object類就是我這要說的萬物的始祖,其被所有的事物所繼承是一個根類型。Object這個名字也起得相當好,泛指了所有的物體,給予了一種無形態(tài)的抽象的定義。意味著我們作為程序中的造物主,需要將這種無形態(tài)的物體,變化成各種具體形態(tài)的事物,這就需要繼承。
那么,Object中所做的功能其實并不多,因為它不涉及具體的功能,因此它很多時候在實現(xiàn)上面僅僅能區(qū)分是否是同一個對象isEqual,又或者是告訴我們它的描述toString。但是這樣已經(jīng)是有很大的引導意義,因為只要繼承了Object類型,那么你的類型將會得到這樣功能,闡述了繼承功能的強大;同時,你還可以把你的子類型對象賦予給一個Object的實例,因為Object是根類,它可以保留任意的子類類型對象,這是面向?qū)ο蠖鄳B(tài)的基本體現(xiàn)。
有了Object這樣的一個例子,更加表明了我們在開發(fā)項目過程中也應該為項目的代碼設計基類。而這里的基類要比Object的功能稍微具體一點。因為它涉及到具體要實現(xiàn)的功能需求,而且還應該不止是一個基類。下面來說一下我是怎么樣設計基類的。
和基類來個約定
要怎么設計基類,其實還是要根據(jù)實際的項目需求而定。假設你所開發(fā)的項目包含很多的后臺服務,那么就應該定義一個叫BaseService的基類,然后通過這個基類來分化成多種服務類型。如果你的項目涉及到多種UI的處理,那么你就應該考慮可能需要一個基礎的UI管理類型(通常UI組件有自己的基類,這里需要的是對UI進行管理和維護的基礎類型),如:UIController。然后根據(jù)這個基類分化出各種的UI展現(xiàn)。
基本上是根據(jù)如果程序存在多種特性相近或相似的功能模塊,那么就應該為這些模塊建立基類,以便日后更好的進行擴展。這里不建議要給未來有可能會有多種近似功能的模塊定義基類,還是老話,你的假設不一定成立。而且等到出現(xiàn)這樣的需求在進行定義也不遲,畢竟要改造的也只是一個類型而已。
上面我們知道如何去抽象出基類,那么基類應該要如何封裝屬性和方法,其實完全可以視其子類而定,因為這樣是最貼近需求的。如前面所說的多種后臺服務,假設有如下服務類型:
class ServiceA
{
//服務名稱
property name:String;
//調(diào)用服務
function exec(config:Dicationary):void
{
//執(zhí)行服務
}
}
class ServiceB
{
//服務名稱
property name:String;
//調(diào)用服務
function call():void
{
//執(zhí)行服務
}
}
上面所描述的服務從代碼結(jié)構(gòu)上面其實不大相同,特別是在執(zhí)行服務的行為上接收參數(shù)也不一樣。如果要從它們身上抽象出基類,確實會讓人有所困惑。但是,我們既然要抽象基類,那么就肯定會改造子類。我自己總結(jié)一些抽象的規(guī)則:
子類中相同的屬性和方法,要封裝到基類中。
對于大多數(shù)子類存在的屬性和方法(如5個子類中有3個以上類型存在相同的屬性和方法),可以考慮封裝到基類中,沒用到類型可以對基類的屬性和方法進行忽略。
對于意義相近的屬性,可以考慮將屬性合并或替換(如:子類中分別用id或者name屬性來表示對象的唯一,那么基類可以考慮只取其中一項屬性或者創(chuàng)建一個集合兩者的屬性定義)。
對于子類相同行為的方法,如果聲明的方法參數(shù)的數(shù)量或類型不同時,可以考慮基類的方法集合子類該方法中的所有參數(shù)(即取方法參數(shù)的并集)或者考慮定義共有參數(shù),特殊參數(shù)則由子類轉(zhuǎn)換為屬性來實現(xiàn)(一般可以持續(xù)持有的參數(shù)可以這樣設計,如系統(tǒng)的配置)。
基于上面所說的,我們可以把ServiceA和ServiceB,通過抽象基類改成下面的樣子:
class BaseService
{
//服務名稱
property name:String;
//調(diào)用服務
function exec():void;
}
class ServiceA extends BaseService
{
//這里將參數(shù)設定為屬性
property config:Dictionary;
function exec():void
{
//執(zhí)行服務
}
}
class ServiceB extends BaseService
{
function exec():void
{
//執(zhí)行服務
}
}
類似這樣,我們就可以把基類給抽象出來了。
讓類型進化
在開發(fā)和維護代碼的過程里面,難免會碰到由于產(chǎn)品的迭代,導致需求發(fā)生重大的變更。那么可能會出現(xiàn)某些功能的廢棄或者功能的合并或演進。這時候,之前定義的類型就要面臨著重構(gòu)或者調(diào)整的命運。
遇到這樣的問題我們先別急著把之前的東西全盤否定,然后徹底重寫。而是先考慮之前的定義中是否還存在可用的東西。要把可以重用的模塊給提取出來,那么,模塊的取舍就是我們需要評估的事情。
如果新的需求中已經(jīng)廢棄的功能,那么模塊所涉及的類型都可以進行廢棄。對于這些模塊的處理,我個人比較喜歡對相關的類型進行直接刪除,然后進行編譯,報錯后直接修改引用到該類型的代碼,直到編譯成功為止。對于缺失該類型后需要改造的方法,我會暫時不進行改造,而是打上標記(如TODO或者warning宏),等待正式開發(fā)新功能時再尋找會這些標記一一進行解決。
如果新需求中包含該類功能,但是又融合了一些新的元素,那么,我們的類型還不能直接拿來使用。要評估新的元素是否作為類型的一部分,還是作一種新的類型。例如:一款聊天應用,剛開始的時候只有私聊(一對一聊天),那么,發(fā)送消息就在User類型中:
//用戶類型
class User
{
//給某個用戶發(fā)送消息
function sendMessage(user : User, message : Message) : void;
}
到了第二個版本的時候,可能支持用戶給好友群發(fā)消息。那么,明顯上面的代碼不能滿足需求,這時候需要對類型進行改造:
//用戶類型
class User
{
//給某個用戶發(fā)送消息
function sendMessage(user : User, message : Message) : void;
//給多個用戶發(fā)送消息
function sendMessage(users : Arrray<User>, message : Message) : void
{
for (User user in users)
{
this.sendMessage(user, message);
}
}
}
增加了一個sendMessage多態(tài)版本的方法用于群發(fā)消息。這樣既能保證方法的出口統(tǒng)一,又能保證之前的方法不被修改,就可以輕松地解決群發(fā)問題。
那么,到了第三個版本的時候出現(xiàn)了聊天室的概念,這時候需求中多了一個聊天室的關鍵字,運用之前提到的方法,這里應該需要新增一個類型,不能直接在User類型中進行擴展。那么,可以設計為:
//聊天室
class ChatRoom
{
//聊天室名稱
property name:String;
//聊天室用戶
property users:Array<User>;
//發(fā)送消息
function sendMessage(user:User, message:Message):void;
}
這里增加了一個ChatRoom的類型,主要是用來記錄那些人在哪個聊天室中。該類型有一個sendMessage的方法,用于表示哪個用戶要在聊天室中發(fā)言,發(fā)言的內(nèi)容是什么。
由上面的例子可以看出來,產(chǎn)品迭代有時候需要讓類型進行,有時候也需要誕生新的類型。要做那種選擇則需要根據(jù)產(chǎn)品需求來決定。
只要這種多態(tài)
多態(tài)在我的理解中就是多種狀態(tài)。就好比我們用手拿東西的時候,如果拿的是球,那么我們可能是想要打球,如果拿的是蘋果,那么我們可能要把它吃掉。根據(jù)不同東西我們可能會作出不同的反映。在面向?qū)ο笾卸鄳B(tài)就是用于解決這類的問題。我們來假設一下沒有多態(tài)的時候,我們的代碼看起來會是多挫:
function doSomething(obj:Object):void
{
if (obj instanceof ObjectA)
{
//do something by ObjectA
}
else if (obj instanceof ObjectB)
{
//do something by ObjectB
}
}
要判斷的類型越多,if的語句就越長。那么有了多態(tài)后,我們可以很優(yōu)雅地設計:
function doSomething(obj:ObjectA):void
{
//do something by ObjectA
}
function doSomething(obj:ObjectB):void
{
//do something by ObjectB
}
多態(tài)可以保證代碼設計的出口名字統(tǒng)一,不需要外部調(diào)用的人要根據(jù)不同的類型調(diào)用不同名字的方法,對于外部調(diào)用只需要傳入的類型不同即可決定要調(diào)用哪個方法。
如果不是解決上面所說的問題,我不建議使用多態(tài)。因為多態(tài)會使到類型變得復雜,如果這個使用多態(tài)的類型被繼承,然后繼承的子類進行了多態(tài)處理,那么會影響到程序的質(zhì)量,并且一旦出現(xiàn)問題,排查的難度會有所增加。
后話
以上說的都是我個人在這些年的開發(fā)中所理解的東西,面向?qū)ο筮@東西已經(jīng)存在我腦海里面許多個日夜了,總想著寫些什么,今天總算把它給完成了。后續(xù)我會繼續(xù)寫下其他的一些關于程序思維的文章,希望大家支持。
寫這篇文章的時候沒有參考其他資料,可能存在錯漏的地方。如果你是一位好心的猿/媛,麻煩給我指正一下。
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
LSTM 模型輸入長度選擇技巧:提升序列建模效能的關鍵? 在循環(huán)神經(jīng)網(wǎng)絡(RNN)家族中,長短期記憶網(wǎng)絡(LSTM)憑借其解決長序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報考條件詳解與準備指南? ? 在數(shù)據(jù)驅(qū)動決策的時代浪潮下,CDA 數(shù)據(jù)分析師認證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計的實用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實施重大更新。 此次更新旨在確保認 ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務的價值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡稱 BI)深度融合的時代,BI ...
2025-07-10SQL 在預測分析中的應用:從數(shù)據(jù)查詢到趨勢預判? ? 在數(shù)據(jù)驅(qū)動決策的時代,預測分析作為挖掘數(shù)據(jù)潛在價值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點,而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報考到取證的全攻略? 在數(shù)字經(jīng)濟蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢性檢驗:捕捉數(shù)據(jù)背后的時間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢性檢驗如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時間維度的精準切片? ? 在數(shù)據(jù)的世界里,時間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準 ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應用與實戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗:數(shù)據(jù)趨勢與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領域中,準確捕捉數(shù)據(jù)的趨勢變化以及識別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應對策略? 長短期記憶網(wǎng)絡(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(RNN)的一種變體,憑借獨特的門控機制,在 ...
2025-07-07統(tǒng)計學方法在市場調(diào)研數(shù)據(jù)中的深度應用? 市場調(diào)研是企業(yè)洞察市場動態(tài)、了解消費者需求的重要途徑,而統(tǒng)計學方法則是市場調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準確性的基礎 ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03