演示文稿主題怎么設置啊(怎么將演示文稿主題設置)
1038
2022-05-30
1 前言
實體是領域模型中的領域對象。
MVC開發人員總將關注點放在數據,而非領域。因為在軟件開發中,DB占據主導地位。首先考慮的是數據的屬性(即數據庫的列)和關聯關系(外鍵關聯),而不是富有行為的領域概念。
導致將數據模型直接反映在對象模型,那些表示領域模型的實體(Entity)被包含了大量getter/setter。雖然在實體模型中加入getter/setter并非大錯, 但這不是DDD做法。
過于強調實體的作用卻忽視了值對象。受到DB和持久化框架影響,實體被濫用,于是開始討論如何避免大范圍使用實體…
2 為什么使用實體
當我們需要考慮一個對象的個性特征或需要區分不同對象時,就引入實體這個領域概念。
一個實體是一個唯一的東西,可在一段時間內持續變化。
這些對象重要的不是屬性,而是其延續性和標識,會跨越甚至超出軟件生命周期。
也正是 唯一身份標識和可變性(mutability) 特征將實體對象區別于值對象。
實體建模并非總是完美。很多時候,一個領域概念應該建模成值對象,而非實體。這意味著DDD開發CRUD軟件系統時可能更適用。但由于只從數據出發,CRUD系統是不能創建出好的業務模型的。
在可以使用DDD時,我們會將數據模型轉變為實體模型。
通過標識區分對象,而非屬性:
此時應將標識作為主要的模型定義。同時保持簡單類定義,關注對象在生命周期中的連續性和唯一標識性。不應該通過對象的狀態形式和歷史來區分不同實體……對于什么是相同的東西,模型應該給出定義。
如何正確使用和設計實體呢?
3 唯一標識
在實體設計早期,關注能體現實體身份唯一性的主要屬性和行為及如何查詢實體,忽略次要的屬性和行為。
設計實體時,首先考慮實體的本質特征,特別是實體的唯一標識和對實體的查找,而不是一開始便關注實體的屬性和行為。只有在對實體的本質特征有用的情況下,才加入相應的屬性和行為。
找到多種能夠實現唯一標識性的方式,同時考慮如何在實體生命周期內維持唯一性。
實體的唯一標識不見得一定有助對實體的查找和匹配。將唯一標識用于實體匹配通常取決于標識的可讀性。
比如:
若系統提供根據人名查找功能,但此時一個Person實體的唯一標識可能不是人名,因為重名情況很多
若某系統提供根據公司稅號的查找功能,稅號便可作為Company實體的唯一標識
值對象可用于存放實體的唯一標識。值對象是不變(immutable)的,這就保證了實體身份的穩定性,并且與身份標識相關的行為也可得到集中處理。便可避免將身份標識相關的行為泄漏到模型的其他部分或客戶端中去。
3.1 創建實體身份標識的策略
每種技術方案都存在副作用。比如將關系型DB用于對象持久化時,這樣的副作用將泄漏到領域模型。創建前需考慮標識生成的時間、關系型數據的引用標識和ORM在標識創建過程中的作用等,還會考慮如何保證唯一標識的穩定性。
參見
DDD領域驅動設計實戰 - 創建實體身份標識的常用策略
3.2 標識穩定性
絕大多數場景不應修改實體的唯一標識,可在實體的整個生命周期中保持標識的穩定性。
可通過一些簡單措施確保實體標識不被修改。可將標識的setter方法向用戶隱藏。也可在setter方法種添加邏輯以確保標識在已存在時不再更新,比如可使用一些斷言:
username屬性是User實體的領域標識,該屬性只能進行一次修改,并且只能在User對象內修改。setter方法setUsername實現了自封裝性且對客戶端不可見。當實體的public方法自委派給該setter方法時,該方法將檢查username屬性,看是否已被賦值。若是,表明該User對象的領域標識已經存在,程序將拋異常。
這個setter方法并不會阻礙Hibernate重建對象,因對象在創建時,它的屬性都是使用默認值,且采用無參構造器,因此username屬性的初始值為null。然后,Hibernate將調用setter方法,由于username屬性此時為null,該 setter方法得以正確地執行,username屬性也將被賦予正確的標識值。
4 實體的形態
4.1 業務形態
戰略設計時,實體是領域模型的一個重要對象。領域模型中的實體是多個屬性、操作或行為的載體。
事件風暴中,可根據命令、操作或者事件,找出產生這些行為的業務實體對象,進而按業務規則將依存度高和業務關聯緊密的多個實體對象和值對象進行聚類,形成聚合。
實體和值對象是組成領域模型的基礎單元。
4.2 代碼形態
即實體類,包含實體的屬性和方法,通過這些方法實現實體自身的業務邏輯。
采用充血模型:
該實體相關的所有業務邏輯都在實體類的方法中實現
跨多個實體的領域邏輯則在領域服務中實現
4.3 運行形態
實體以DO(領域對象)形式存在,每個實體對象都有唯一ID。可對實體做多次修改,所以一個實體對象可能和它之前狀態存在較大差異。但它們擁有相同身份標識(identity),所以始終是同一實體。
比如商品是商品上下文的一個實體,通過唯一的商品ID標識,不管這商品的數據(比如價格)如何變,商品ID不會變,始終是同一商品。
4.4 數據庫形態
DDD先構建領域模型,針對業務場景構建實體對象和行為,再將實體對象映射到數據持久化對象。
在領域模型映射到數據模型時,一個實體可能對應0、1或多個數據庫持久化對象。大多數情況下實體與持久化對象是一對一。在某些場景中,有些實體只是暫駐靜態內存的一個運行態實體,無需持久化。比如,基于多個價格配置數據計算后生成的折扣實體。
有些復雜場景,實體與持久化對象可能是一對多或多對一:
一對多
用戶user與角色role兩個持久化對象可生成權限實體,一個實體對應兩個持久化對象
多對一
有時為避免DB的聯表查詢,會將客戶信息customer和賬戶信息account兩類數據保存至同一張數據庫表,客戶和賬戶兩個實體可根據需要從一個持久化對象中生成
實體本質的探索
一開始在Java代碼中建模大量實體關系。將太多關注點放在數據庫、表、列和對象映射上。導致所創建的模型實際上只是含有大量getter/setter的貧血領域模型。
我們應該在DDD 上有更多的思考。
如果我們認為對象就是一組命名的類和在類上定義的操作,除此之外并不包含其他內容,那就錯了。
如果一些特定的領域場景會在今后繼續使用,這時可以用一個輕量的文檔將它們記錄下來。簡單形式的通用語言可以是一組術語和一些簡單的用例場景。 但是,如果我們就此認為通用語言只包含術語和用例場景,那么我們又錯了。在最后,通用語言應該直接反映在代碼中,而要保持設計文檔的實時更新是非常困難的,甚至是不可能的。
5 創建實體
新建一個實體時,我們總期望通過構造器就能初始化足夠多的實體狀態,因為這樣更容易通過各種條件查找到該實體。
在使用及早生成唯一標識的策略時,構造器至少需接受唯一標識參數。若還有可能通過其他方式查找實體,比如名字或描述信息,那應該將這些參數一并傳給構造器。
有時一個實體維護一或多個不變條件(Invariant,在整個實體生命周期中都必須保持事務一致性的一種狀態) 。
不變條件主要是聚合所關注的,但聚合根也是實體。
如果實體的不變條件要求該實體所包含的對象都不能為null或必須由其他狀態計算所得,那么這些狀態也需作為參數傳遞給構造器。
public class User extends Entity { ... // 每一個User對象都必須包含tenantld、username, password和person屬性。 // 即在User對象得到正確實例化后,這些屬性不能為null // 由User對象的構造器和實例變量對應的setter方法保證這點 protected User (Tenantld aTenantld String aUsername, String aPassword, Person aPerson) ( this(); this.setPassword(aPassword); this.setPerson(aPerson); this.setTenantld(aTenantld); this.setUsername(aUsername); this.initialize(); } ... protected void setPassword(String aPassword) { if (aPassword == null) { throw new 11legalArgumentException( "The password may not be set to null."); ) this.password = aPassword; ) protected void setPerson(Person aPerson) ( if (aPerson == null) ( throw new IllegalArgumentException("The person may not be set to null."); } this.person = aPerson; } protected void setTenantld(Tenantld aTenantld) ( if (aTenantld == null) { throw new IllegalArgumentException("The tenantld may not be set to null."); } this.tenantld = aTenantld; } protected void setUsername(String aUsername) ( if (this.username != null) { throw new IIlegalStateException("The username may not be changed.n); } if (aUsername == null) { throw new IllegalArgumentException("The username may not be set to null."); } this.username = aUsername; }
User對象展示了一種自封裝性。在構造器對實例變量賦值時,把操作委派給實例變量對應的setter方法,便保證了實例變量的自封裝性。實例變量的自封裝性使用setter方法來決定何時給實例變量賦值。
每個setter方法都“代表著實體”對所傳進的參數做非null檢查,這里的斷言稱為守衛(Guard)。setter方法的自封裝性技術可能會變得非常復雜。所以對于復雜的創建實體場景,可使用工廠。
User對象的構造函數被聲明為 protected。 Tenant實體即為User實體的工廠也是同一個模塊中唯一能夠訪問User 構造器的類。這樣一來,只有Tenant能夠創建User實例。
public class Tenant extends Entity { // 該工廠簡化對User的創建,同時保證了Tenantld在User和Person對象中的正確性 // 該工廠能夠反映通用語言。 public User registerUser(String aUsername, String aPassword, Person aPerson) { aPerson.setTenantld(this.tenantld()); User user = new User(this.tenantld(), aUsername, aPassword, aPerson); return user; }
參考
https://tech.meituan.com/2017/12/22/ddd-in-practice.html
《實現領域驅動設計》
實體和值對象:從領域模型的基礎單元看系統設計
https://blog.csdn.net/Taobaojishu/article/details/106152641
Java
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。