新聞中心
使用貧血領(lǐng)域模型通常被認(rèn)為是一種反模式,因?yàn)樗膭?lì)程序員無(wú)意義地重復(fù)編寫(xiě)代碼。下面我將簡(jiǎn)短(而瑣碎)地用一個(gè)例子來(lái)闡述這個(gè)是如何產(chǎn)生的。我們可以通過(guò)細(xì)致的規(guī)劃以及嚴(yán)格的編碼規(guī)范來(lái)避免其發(fā)生,但是同樣可以獲得較好的封裝。防止陷入貧血領(lǐng)域模型深坑的難度隨項(xiàng)目人數(shù)呈指數(shù)級(jí)增長(zhǎng)。

創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都做網(wǎng)站、網(wǎng)站建設(shè)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿足客戶于互聯(lián)網(wǎng)時(shí)代的寧縣網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
我相信所有人對(duì)面向?qū)ο蠖加兴J(rèn)識(shí),但我卻有趣地發(fā)現(xiàn)一些看似毫無(wú)意義的小舉措?yún)s導(dǎo)致了最終一場(chǎng)大災(zāi)難。
第一步:編寫(xiě)貧血實(shí)體
在軟件開(kāi)發(fā)的某些情況下,我們會(huì)在一個(gè)領(lǐng)域?qū)嶓w之外實(shí)現(xiàn)一些邏輯。這可能是由于一個(gè)明確的設(shè)計(jì)決定或者,更有可能,持久類不能引用外部服務(wù)造成了不能將這段邏輯實(shí)現(xiàn)在領(lǐng)域?qū)ο蟮膬?nèi)部。把外部服務(wù)(依賴)添加到實(shí)體對(duì)象中將會(huì)造成與數(shù)據(jù)庫(kù)的交互變的復(fù)雜而晦澀難懂。
- public class User {
- private final String name;
- private final String emailAddress;
- public User(final String name, final String emailAddress) {
- this.name=name;
- this.emailAddress=emailAddress;
- }
- public String getName() {
- return this.name;
- }
- public String getEmailAddress() {
- return this.emailAddress;
- }
- }
第二部:邏輯被實(shí)現(xiàn)在外部類中
一個(gè)開(kāi)發(fā)組的成員決定他們需要一個(gè)用來(lái)操作這個(gè)實(shí)體的方法。這個(gè)方法(在我們的例子中)要調(diào)用到User對(duì)象,但它還需要用到一個(gè)User類所不知道的外部服務(wù)。這段邏輯被實(shí)現(xiàn)在一個(gè)幫助類(helper)或者說(shuō)一個(gè)服務(wù)類(service)的方法中,并且以某種方式協(xié)助了這個(gè)實(shí)體。這個(gè)幫助類不包含自帶的數(shù)據(jù),并且僅僅從這個(gè)實(shí)體中獲取數(shù)據(jù)、修改其狀態(tài)。
- public class UserReminderService { // 用戶提醒服務(wù)
- private IMailService mailService; // 郵件服務(wù)
- private IMessageGeneratorService messageGeneratorService; // 消息生成服務(wù)
- public void sendReminderMessage(final IUser user) { // 發(fā)送一個(gè)提醒
- String reminderMessage = this.messageGeneratorService.generateReminderMessage(user.getName);
- this.mailService.sendMessage(user.getEmailAddress(), reminderMessage);
- }
- ...
- }
這個(gè)并不能實(shí)現(xiàn)在User實(shí)體中,因?yàn)槲覀兏緹o(wú)法在實(shí)體中取得郵件服務(wù)或者是消息生成器。到目前為止,這個(gè)看起來(lái)還不算很糟糕(我們很好地封裝了消息的創(chuàng)建以及郵件發(fā)送過(guò)程),但是這僅僅是“敗壞”的開(kāi)始,然后馬上開(kāi)始讓這些不警惕的開(kāi)發(fā)者陷入災(zāi)難。
哪里錯(cuò)了呢?
UserReminderService是一個(gè)游手好閑的類(它掌管了太多其他類的活動(dòng))。消息的創(chuàng)建、把它發(fā)送出去這些都應(yīng)該是User類自己的業(yè)務(wù)邏輯。
第三步:重復(fù)代碼產(chǎn)生
在此期間,另一個(gè)開(kāi)發(fā)者開(kāi)發(fā)了一個(gè)全新的組件,同樣也使用了User實(shí)體。這個(gè)新的服務(wù)被用來(lái)決定注冊(cè)用戶是真的用戶而不是一個(gè)機(jī)器人。
- public class SignupVerificationService { // 注冊(cè)確認(rèn)服務(wù)
- private IMailService mailService; // 郵件服務(wù)
- private IMessageGeneratorService messageGeneratorService; // 消息生成服務(wù)
- public void sendVerificationEmail(final IUser user) { // 發(fā)送確認(rèn)郵件
- String verificationMessage = this.messageGeneratorService.generateVerificationMessage(user.getName);
- this.mailService.sendMessage(user.getEmailAddress(), reminderMessage);
- }
- }
這個(gè)開(kāi)發(fā)者可能會(huì)發(fā)現(xiàn)這個(gè)方法與之前的sendReminderMessage方法十分的相似。在這個(gè)情況下,他覺(jué)得他把驗(yàn)證功能與其他組件分開(kāi)來(lái)的做法十分精明,看上去沒(méi)有必要為這短短兩行代碼重用之前的實(shí)現(xiàn)。
哪里錯(cuò)了呢?
這兩個(gè)方法看上去十分相似,但是又是不同的,使得開(kāi)發(fā)者認(rèn)為是兩個(gè)不同的活動(dòng)。這里有一種冗余的感覺(jué),但還沒(méi)有造成問(wèn)題。
第四步:邏輯變更
從長(zhǎng)遠(yuǎn)來(lái)看,越簡(jiǎn)單的代碼會(huì)變的越復(fù)雜。在這個(gè)迭代后期,我們的開(kāi)發(fā)者在sendReminderMessage方法中添加了一些更復(fù)雜的邏輯(預(yù)處理用戶名和校驗(yàn)郵箱地址)。
- public void sendReminderMessage(final IUser user) {
- String formattedUserName = formatUserNameForMessage(user.getName());
- String reminderMessage = this.messageGeneratorService.generateReminderMessage(formattedUserName);
- if (isEmailAddressValid(user.getEmailAddress()) {
- this.mailService.sendMessage(user.getEmailAddress(), reminderMessage);
- }
- }
- public boolean isEmailAddressValid(final String emailAddress) { // 是否郵箱地址有效
- return emailAddress.contains('@');
- }
- public String formatUserNameForMessage(final String userName) { // 為消息格式化用戶名
- return userName.toUpperCase();
- }
我們現(xiàn)在有了sendReminderMessage方法的新版本(雖然是一個(gè)很簡(jiǎn)陋的驗(yàn)證系統(tǒng)),使得(曾經(jīng)相似的)UserReminderService變得相當(dāng)不同。
哪里錯(cuò)了呢?
用來(lái)給向用戶發(fā)送消息的過(guò)程發(fā)生了變化 (需要進(jìn)行校驗(yàn)). 由于該過(guò)程沒(méi)有包含在User類內(nèi)部,我們就必須追蹤它在所有不同形式下的所有實(shí)現(xiàn),然后對(duì)它們進(jìn)行修改。假設(shè)我們意識(shí)到SignupVerificationService也需要校驗(yàn),然后我們?yōu)樗砑恿诵r?yàn),我們?nèi)匀恍枰环N能夠重復(fù)使用這端校驗(yàn)代碼的方法.在需要校驗(yàn)的情況下,我們可能會(huì)把方法封裝到mailService中,但對(duì)于其他的邏輯,比如用戶姓名格式化,已經(jīng)被加入到不同的helper/service類中了,該怎么辦呢?這些代碼可能會(huì)被多個(gè)service類所需要,也可能只有一個(gè)service需要。
- AbstractUserService
- /\
- |
- |
- ------------------------------------
- | |
- UserValidationService UserReminderService
與此同時(shí)另一個(gè)開(kāi)發(fā)者也寫(xiě)了另一個(gè)service,這個(gè)service是用來(lái)給某個(gè)Department實(shí)體(同樣也使用email地址)發(fā)送消息的.這位開(kāi)發(fā)者想要使用AbstractUserService中的郵箱驗(yàn)證和名字格式化功能,但他的代碼是為Departments服務(wù)的,而不是Users,因此,代碼結(jié)構(gòu)中另一層又出現(xiàn)了:AbstractEntiryService.
哪里錯(cuò)了呢?
我們已經(jīng)失去了對(duì)我們程序結(jié)構(gòu)的控制,我們的開(kāi)發(fā)團(tuán)隊(duì)開(kāi)始發(fā)現(xiàn)很難再寫(xiě)出干凈的代碼. 我們的類需要比實(shí)際需求更多的公共方法來(lái)維護(hù)復(fù)雜的類關(guān)系
總結(jié)
通過(guò)貧血的領(lǐng)域模型來(lái)保持代碼結(jié)構(gòu)整潔并且可維護(hù)是當(dāng)然不可能的.然而,當(dāng)我們能夠使用充血領(lǐng)域模型的時(shí)候,維護(hù)代碼并且保持類接口簡(jiǎn)潔就變得非常容易了.
- public class User {
- //Dependencies
- private IMailService mailService;
- private IMessageService messageService;
- private final String name;
- private final String emailAddress;
- public User(final String name, final String emailAddress) {
- this.name=name;
- this.emailAddress=emailAddress;
- }
- public void sendReminderMessage() {
- deliverMessage( this.messageGeneratorService.generateReminderMessage(this.getName));
- }
- public void sendVerificationEmail() {
- deliverMessage(this.messageGeneratorService.generateVerificationMessage(this.getName));
- }
- private void deliverMessage(final String message) {
- if (isEmailAddressValid(user.getEmailAddress()) {
- this.mailService.sendMessage(user.getEmailAddress(), reminderMessage);
- }
- }
- public String getName() {
- return this.name;
- }
- }
注意,我們不再需要email地址的get方法,而且,如果你能原諒我玩數(shù)字游戲,我們?cè)黾恿藘蓚€(gè)User類的公共方法二不是引入兩個(gè)(至少)額外的類. 當(dāng)我們?cè)谶m當(dāng)?shù)膶?duì)象上執(zhí)行方法的時(shí)候比在一個(gè)不自然的service對(duì)象上執(zhí)行方法看起來(lái)更直觀.
MailService和MessageServices仍被允許留在系統(tǒng)中因?yàn)樗鼈兊慕巧苊鞔_. 傳送郵件是一個(gè)清晰的架構(gòu)問(wèn)題,應(yīng)該被從領(lǐng)域?qū)ο笾型ㄟ^(guò)接口(IMailService)抽象出來(lái).生成消息應(yīng)該被如何抽象/封裝可能是更值得商榷的,但這篇文章就會(huì)比我與其的更長(zhǎng)了.
我希望你會(huì)喜歡這篇文章.
英文原文:How Anaemic Domain Models Cause Bad Software
譯文鏈接:http://www.oschina.net/translate/how-anaemic-domain-models-cause-bad-software
新聞名稱:貧血領(lǐng)域模型是如何導(dǎo)致糟糕的軟件產(chǎn)生
文章起源:http://fisionsoft.com.cn/article/cdojppj.html


咨詢
建站咨詢
