理論四:哪些代碼設(shè)計(jì)看似是面向?qū)ο?,?shí)際是面向過程的?

在用面向?qū)ο缶幊陶Z言進(jìn)行軟件開發(fā)的時(shí)候,我們有時(shí)候會(huì)寫出面向過程風(fēng)格的代碼。有些是有意為之,并無不妥;而有些是無意為之,會(huì)影響到代碼的質(zhì)量。三個(gè)典型的代碼案例:

  1. 濫用 getter、setter 方法
    在項(xiàng)目開發(fā)中有定義完類的屬性之后,就順手把這些屬性的 getter、setter 方法都定義上理由一般是,為了以后可能會(huì)用到,現(xiàn)在事先定義好,類用起來就更加方便,而且即便用不到這些 getter、setter 方法,定義上它們也無傷大雅。實(shí)際上,這樣的做法我是非常不推薦的。它違反了面向?qū)ο缶幊痰姆庋b特性,相當(dāng)于將面向?qū)ο缶幊田L(fēng)格退化成了面向過程編程風(fēng)格。通過下面這個(gè)例子來給解釋一下這句話:

public class ShoppingCart {
private int itemsCount;
private double totalPrice;
private List<ShoppingCartItem> items = new ArrayList<>();

public int getItemsCount() {
return this.itemsCount;
}

public void setItemsCount(int itemsCount) {
this.itemsCount = itemsCount;
}

public double getTotalPrice() {
return this.totalPrice;
}

public void setTotalPrice(double totalPrice) {
this.totalPrice = totalPrice;
}

public List<ShoppingCartItem> getItems() {
return this.items;
}

public void addItem(ShoppingCartItem item) {
items.add(item);
itemsCount++;
totalPrice += item.getPrice();
}
// ...省略其他方法...
}

先來看前兩個(gè)屬性,itemsCount 和 totalPrice。雖然我們將它們定義成 private 私有屬性,但是提供了 public 的 getter、setter 方法,這就跟將這兩個(gè)屬性定義為 public 公有屬性,沒有什么兩樣了。外部可以通過 setter 方法隨意地修改這兩個(gè)屬性的值。除此之外,任何代碼都可以隨意調(diào)用 setter 方法,來重新設(shè)置 itemsCount、totalPrice 屬性的值,這也會(huì)導(dǎo)致其跟 items 屬性的值不一致。

而面向?qū)ο蠓庋b的定義是:通過訪問權(quán)限控制,隱藏內(nèi)部數(shù)據(jù),外部僅能通過類提供的有限的接口訪問、修改內(nèi)部數(shù)據(jù)。

注意:在設(shè)計(jì)實(shí)現(xiàn)類的時(shí)候,除非真的需要,否則,盡量不要給屬性定義 setter 方法。除此之外,盡管 getter 方法相對(duì) setter 方法要安全些,但是如果返回的是集合容器(比如例子中的 List 容器),也要防范集合內(nèi)部數(shù)據(jù)被修改的危險(xiǎn)。

  1. 濫用全局變量和全局方法
    在面向?qū)ο缶幊讨?,常見的全局變量有單例類?duì)象、靜態(tài)成員變量、常量等,常見的全局方法有靜態(tài)方法。單例類對(duì)象在全局代碼中只有一份,所以,它相當(dāng)于一個(gè)全局變量。靜態(tài)成員變量歸屬于類上的數(shù)據(jù),被所有的實(shí)例化對(duì)象所共享,也相當(dāng)于一定程度上的全局變量。而常量是一種非常常見的全局變量,比如一些代碼中的配置參數(shù),一般都設(shè)置為常量,放到一個(gè) Constants 類中。靜態(tài)方法一般用來操作靜態(tài)變量或者外部數(shù)據(jù)。你可以聯(lián)想一下我們常用的各種 Utils 類,里面的方法一般都會(huì)定義成靜態(tài)方法,可以在不用創(chuàng)建對(duì)象的情況下,直接拿來使用。靜態(tài)方法將方法與數(shù)據(jù)分離,破壞了封裝特性,是典型的面向過程風(fēng)格。

如何改進(jìn) Constants 類的設(shè)計(jì)呢?
第一種是將 Constants 類拆解為功能更加單一的多個(gè)類,比如跟 MySQL 配置相關(guān)的常量,我們放到 MysqlConstants 類中;跟 Redis 配置相關(guān)的常量,我們放到 RedisConstants 類中。還有一種我個(gè)人覺得更好的設(shè)計(jì)思路,那就是并不單獨(dú)地設(shè)計(jì) Constants 常量類,而是哪個(gè)類用到了某個(gè)常量,我們就把這個(gè)常量定義到這個(gè)類中。比如,RedisConfig 類用到了 Redis 配置相關(guān)的常量,那我們就直接將這些常量定義在 RedisConfig 中,這樣也提高了類設(shè)計(jì)的內(nèi)聚性和代碼的復(fù)用性。

為什么需要 Utils 類?Utils 類存在的意義是什么?
Utils 類的出現(xiàn)是基于這樣一個(gè)問題背景:如果我們有兩個(gè)類 A 和 B,它們要用到一塊相同的功能邏輯,為了避免代碼重復(fù),我們不應(yīng)該在兩個(gè)類中,將這個(gè)相同的功能邏輯,重復(fù)地實(shí)現(xiàn)兩遍。這個(gè)時(shí)候我們?cè)撛趺崔k呢?
可以定義一個(gè)新的類,實(shí)現(xiàn) URL 拼接和分割的方法。而拼接和分割兩個(gè)方法,不需要共享任何數(shù)據(jù),所以新的類不需要定義任何屬性,這個(gè)時(shí)候,我們就可以把它定義為只包含靜態(tài)方法的 Utils 類了。
類比 Constants 類的設(shè)計(jì),我們?cè)O(shè)計(jì) Utils 類的時(shí)候,最好也能細(xì)化一下,針對(duì)不同的功能,設(shè)計(jì)不同的 Utils 類,比如 FileUtils、IOUtils、StringUtils、UrlUtils 等,不要設(shè)計(jì)一個(gè)過于大而全的 Utils 類。

  1. 定義數(shù)據(jù)和方法分離的類
    面向?qū)ο缶幊踢^程中,常見的面向過程風(fēng)格的代碼。那就是,數(shù)據(jù)定義在一個(gè)類中,方法定義在另一個(gè)類中。
    傳統(tǒng)的 MVC 結(jié)構(gòu)分為 Model 層、Controller 層、View 層這三層。不過,在做前后端分離之后,三層結(jié)構(gòu)在后端開發(fā)中,會(huì)稍微有些調(diào)整,被分為 Controller 層、Service 層、Repository 層。Controller 層負(fù)責(zé)暴露接口給前端調(diào)用,Service 層負(fù)責(zé)核心業(yè)務(wù)邏輯,Repository 層負(fù)責(zé)數(shù)據(jù)讀寫。而在每一層中,我們又會(huì)定義相應(yīng)的 VO(View Object)、BO(Business Object)、Entity。一般情況下,VO、BO、Entity 中只會(huì)定義數(shù)據(jù),不會(huì)定義方法,所有操作這些數(shù)據(jù)的業(yè)務(wù)邏輯都定義在對(duì)應(yīng)的 Controller 類、Service 類、Repository 類中。這就是典型的面向過程的編程風(fēng)格。
    實(shí)際上,這種開發(fā)模式叫作基于貧血模型的開發(fā)模式,也是我們現(xiàn)在非常常用的一種 Web 項(xiàng)目的開發(fā)模式。

重點(diǎn):

  1. 濫用 getter、setter 方法在設(shè)計(jì)實(shí)現(xiàn)類的時(shí)候,除非真的需要,否則盡量不要給屬性定義 setter 方法。除此之外,盡管 getter 方法相對(duì) setter 方法要安全些,但是如果返回的是集合容器,那也要防范集合內(nèi)部數(shù)據(jù)被修改的風(fēng)險(xiǎn)。
    2.Constants 類、Utils 類的設(shè)計(jì)問題對(duì)于這兩種類的設(shè)計(jì),我們盡量能做到職責(zé)單一,定義一些細(xì)化的小類,比如 RedisConstants、FileUtils,而不是定義一個(gè)大而全的 Constants 類、Utils 類。除此之外,如果能將這些類中的屬性和方法,劃分歸并到其他業(yè)務(wù)類中,那是最好不過的了,能極大地提高類的內(nèi)聚性和代碼的可復(fù)用性。
  2. 基于貧血模型的開發(fā)模式關(guān)于這一部分,為什么這種開發(fā)模式是徹徹底底的面向過程編程風(fēng)格的。這是因?yàn)閿?shù)據(jù)和操作是分開定義在 VO/BO/Entity 和 Controler/Service/Repository 中的。為什么這種開發(fā)模式如此流行?如何規(guī)避面向過程編程的弊端?有沒有更好的可替代的開發(fā)模式?相關(guān)的更多問題,在面向?qū)ο髮?shí)戰(zhàn)篇中會(huì)一一講解。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容