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

創(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ì)、移動媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
我相信所有人對面向?qū)ο蠖加兴J(rèn)識,但我卻有趣地發(fā)現(xiàn)一些看似毫無意義的小舉措?yún)s導(dǎo)致了最終一場大災(zāi)難。
第一步:編寫貧血實(shí)體
在軟件開發(fā)的某些情況下,我們會在一個(gè)領(lǐng)域?qū)嶓w之外實(shí)現(xiàn)一些邏輯。這可能是由于一個(gè)明確的設(shè)計(jì)決定或者,更有可能,持久類不能引用外部服務(wù)造成了不能將這段邏輯實(shí)現(xiàn)在領(lǐng)域?qū)ο蟮膬?nèi)部。把外部服務(wù)(依賴)添加到實(shí)體對象中將會造成與數(shù)據(jù)庫的交互變的復(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è)開發(fā)組的成員決定他們需要一個(gè)用來操作這個(gè)實(shí)體的方法。這個(gè)方法(在我們的例子中)要調(diào)用到User對象,但它還需要用到一個(gè)User類所不知道的外部服務(wù)。這段邏輯被實(shí)現(xiàn)在一個(gè)幫助類(helper)或者說一個(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法在實(shí)體中取得郵件服務(wù)或者是消息生成器。到目前為止,這個(gè)看起來還不算很糟糕(我們很好地封裝了消息的創(chuàng)建以及郵件發(fā)送過程),但是這僅僅是“敗壞”的開始,然后馬上開始讓這些不警惕的開發(fā)者陷入災(zāi)難。
哪里錯(cuò)了呢?
UserReminderService是一個(gè)游手好閑的類(它掌管了太多其他類的活動)。消息的創(chuàng)建、把它發(fā)送出去這些都應(yīng)該是User類自己的業(yè)務(wù)邏輯。
第三步:重復(fù)代碼產(chǎn)生
在此期間,另一個(gè)開發(fā)者開發(fā)了一個(gè)全新的組件,同樣也使用了User實(shí)體。這個(gè)新的服務(wù)被用來決定注冊用戶是真的用戶而不是一個(gè)機(jī)器人。
- public class SignupVerificationService { // 注冊確認(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è)開發(fā)者可能會發(fā)現(xiàn)這個(gè)方法與之前的sendReminderMessage方法十分的相似。在這個(gè)情況下,他覺得他把驗(yàn)證功能與其他組件分開來的做法十分精明,看上去沒有必要為這短短兩行代碼重用之前的實(shí)現(xiàn)。
哪里錯(cuò)了呢?
這兩個(gè)方法看上去十分相似,但是又是不同的,使得開發(fā)者認(rèn)為是兩個(gè)不同的活動。這里有一種冗余的感覺,但還沒有造成問題。
第四步:邏輯變更
從長遠(yuǎn)來看,越簡單的代碼會變的越復(fù)雜。在這個(gè)迭代后期,我們的開發(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è)很簡陋的驗(yàn)證系統(tǒng)),使得(曾經(jīng)相似的)UserReminderService變得相當(dāng)不同。
哪里錯(cuò)了呢?
用來給向用戶發(fā)送消息的過程發(fā)生了變化 (需要進(jìn)行校驗(yàn)). 由于該過程沒有包含在User類內(nèi)部,我們就必須追蹤它在所有不同形式下的所有實(shí)現(xiàn),然后對它們進(jìn)行修改。假設(shè)我們意識到SignupVerificationService也需要校驗(yàn),然后我們?yōu)樗砑恿诵r?yàn),我們?nèi)匀恍枰环N能夠重復(fù)使用這端校驗(yàn)代碼的方法.在需要校驗(yàn)的情況下,我們可能會把方法封裝到mailService中,但對于其他的邏輯,比如用戶姓名格式化,已經(jīng)被加入到不同的helper/service類中了,該怎么辦呢?這些代碼可能會被多個(gè)service類所需要,也可能只有一個(gè)service需要。
- AbstractUserService
- /\
- |
- |
- ------------------------------------
- | |
- UserValidationService UserReminderService
與此同時(shí)另一個(gè)開發(fā)者也寫了另一個(gè)service,這個(gè)service是用來給某個(gè)Department實(shí)體(同樣也使用email地址)發(fā)送消息的.這位開發(fā)者想要使用AbstractUserService中的郵箱驗(yàn)證和名字格式化功能,但他的代碼是為Departments服務(wù)的,而不是Users,因此,代碼結(jié)構(gòu)中另一層又出現(xiàn)了:AbstractEntiryService.
哪里錯(cuò)了呢?
我們已經(jīng)失去了對我們程序結(jié)構(gòu)的控制,我們的開發(fā)團(tuán)隊(duì)開始發(fā)現(xiàn)很難再寫出干凈的代碼. 我們的類需要比實(shí)際需求更多的公共方法來維護(hù)復(fù)雜的類關(guān)系
總結(jié)
通過貧血的領(lǐng)域模型來保持代碼結(jié)構(gòu)整潔并且可維護(hù)是當(dāng)然不可能的.然而,當(dāng)我們能夠使用充血領(lǐng)域模型的時(shí)候,維護(hù)代碼并且保持類接口簡潔就變得非常容易了.
- 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ù)字游戲,我們增加了兩個(gè)User類的公共方法二不是引入兩個(gè)(至少)額外的類. 當(dāng)我們在適當(dāng)?shù)膶ο笊蠄?zhí)行方法的時(shí)候比在一個(gè)不自然的service對象上執(zhí)行方法看起來更直觀.
MailService和MessageServices仍被允許留在系統(tǒng)中因?yàn)樗鼈兊慕巧苊鞔_. 傳送郵件是一個(gè)清晰的架構(gòu)問題,應(yīng)該被從領(lǐng)域?qū)ο笾型ㄟ^接口(IMailService)抽象出來.生成消息應(yīng)該被如何抽象/封裝可能是更值得商榷的,但這篇文章就會比我與其的更長了.
我希望你會喜歡這篇文章.
英文原文: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)生
網(wǎng)頁鏈接:http://www.5511xx.com/article/cdojppj.html


咨詢
建站咨詢
