日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > windows >内容正文

windows

《OEA - 实体扩展属性系统 - 设计方案说明书》

發(fā)布時(shí)間:2023/11/29 windows 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《OEA - 实体扩展属性系统 - 设计方案说明书》 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? ? 這篇設(shè)計(jì)文檔是 12 月份寫來參加公司的研發(fā)峰會(huì)的,自己倒是信心滿滿,不過最后還是沒有入圍。現(xiàn)在想想也沒啥大用,所以貼出來,期待與園友交流。

??? 文檔有點(diǎn)長,沒全部貼在博客中,有興趣的可以下載附件中的 PDF。


?附件:《實(shí)體擴(kuò)展屬性系統(tǒng)-系統(tǒng)設(shè)計(jì)說明書.pdf》


================= 分隔線 ======================

?

?

目錄

前言... 4

1 背景與需求... 5

1.1 產(chǎn)品 721 客戶化開發(fā)的需要... 5

1.2 實(shí)體動(dòng)態(tài)列... 6

1.3 分離只讀/視圖屬性... 6

1.4 提升框架性能... 6

1.5 支持 WPF 綁定... 6

1.6 其它需求... 7

2 分析... 8

2.1 主要功能需求... 8

2.2 非功能需求分析... 8

2.3 約束... 9

2.4 風(fēng)險(xiǎn)... 9

3 設(shè)計(jì)方案... 10

3.1 一些決策... 10

3.2 風(fēng)險(xiǎn)點(diǎn)驗(yàn)證... 10

3.2.1 支持 WPF 綁定... 10

3.2.2 性能關(guān)鍵點(diǎn)... 12

3.3 方案描述... 12

3.3.1 結(jié)構(gòu)說明... 13

3.3.2 相關(guān)UML圖... 14

3.3.3 如何支撐需求... 18

3.4 重點(diǎn)實(shí)現(xiàn)細(xì)節(jié)... 18

4 設(shè)計(jì)驗(yàn)證... 24

4.1 功能需求驗(yàn)證... 24

4.2 WPF綁定驗(yàn)證... 24

4.3 性能驗(yàn)證... 25

5 使用手冊(cè)... 25

5.1 使用場景介紹(單元測試)... 25

5.1.1 屬性默認(rèn)值... 26

5.1.2 強(qiáng)制替換屬性值... 27

5.1.3 屬性值設(shè)置時(shí)的取消與強(qiáng)制替換... 27

5.1.4 引用實(shí)體屬性的設(shè)置取消... 28

5.1.5 屬性變更事件... 29

5.1.6 產(chǎn)品721擴(kuò)展屬性... 30

5.1.7 所有擴(kuò)展屬性的界面生成... 31

5.1.8 只讀屬性的使用方法... 31

5.1.9 只讀擴(kuò)展屬性的使用方法... 32

5.1.10 運(yùn)行時(shí)動(dòng)態(tài)屬性(動(dòng)態(tài)列)... 33

5.1.11 序列化... 34

5.1.12 WPF綁定驗(yàn)證... 35

5.1.13 擴(kuò)展屬性O(shè)RM驗(yàn)證... 35

5.1.14 實(shí)體狀態(tài)的相關(guān)屬性... 35

5.2 代碼生成 – CodeSnippets. 36

5.3 其它問題... 37

5.3.1 擴(kuò)展屬性的CLR屬性編寫注意點(diǎn)... 37

5.3.2 何時(shí)使用屬性擴(kuò)展,何時(shí)使用繼承擴(kuò)展?... 38

前言

在產(chǎn)品線開發(fā)中,支持產(chǎn)品的客戶化在產(chǎn)品規(guī)模化開發(fā)中是非常重要的一部分。而客戶化中的非常重要的一部分則是屬性值的客戶化,包括屬性值的添加、刪除、修改及屬性對(duì)應(yīng)的界面的客戶化。由于產(chǎn)品對(duì)屬性值的擴(kuò)展方案一直是使用類繼承的方案來完成,導(dǎo)致了產(chǎn)品出現(xiàn)了許多的問題:

l 其中最重要一個(gè)問題是有時(shí)候無法給一個(gè)客戶兩個(gè)可選的的功能包,而為了解決這個(gè)問題,開發(fā)人員又不得不做大量的代碼移植,把可選包的代碼都移植到主干版本中,導(dǎo)致了臨時(shí)代碼過多,維護(hù)成本太大。

l 另外,我們的產(chǎn)品基于實(shí)體開發(fā),為實(shí)現(xiàn)動(dòng)態(tài)列的需求繞了許多路,最終決定使用數(shù)據(jù)表的模式來編寫,同樣造成大量重復(fù)代碼,開發(fā)人員開發(fā)效率低下。

基于歷史遺留的這些問題,我們?cè)O(shè)計(jì)了全新的屬性系統(tǒng)。本系統(tǒng)設(shè)計(jì)完成之后,解決了許多歷史遺留問題,也帶來了許多意想不到的價(jià)值。例如:

l 支持簡單地完成客戶化開發(fā)中屬性的擴(kuò)展。

l 支持更簡單地實(shí)現(xiàn)領(lǐng)域?qū)嶓w的動(dòng)態(tài)屬性(界面中的動(dòng)態(tài)列,原來要100行代碼,現(xiàn)在只要20行。)

l 視圖屬性分離(更好的可維護(hù)性)

l 屬性性能提升(性能提升)

l 減少了序列化數(shù)據(jù)(傳輸效率提升,性能提升)

l 統(tǒng)一的屬性接口(平臺(tái)可提供更加強(qiáng)大的功能支持)

本次設(shè)計(jì)是在歷史代碼上進(jìn)行重構(gòu),但是本質(zhì)上是設(shè)計(jì)一個(gè)完全獨(dú)立功能的子系統(tǒng)。本文從需求、分析、方案、實(shí)現(xiàn)、驗(yàn)證等角度說明了整個(gè)設(shè)計(jì)是如何完成的。并在最后,給出了系統(tǒng)的使用手冊(cè)以幫助開發(fā)人員日常應(yīng)用。

備注:

本文檔中,為了方便起見,將會(huì)把“實(shí)體擴(kuò)展屬性系統(tǒng)”簡稱為 EMPS。(Entity Managed Property System,意為實(shí)體托管屬性系統(tǒng))

另外,文中說到的版本號(hào):歷史的OEA版本是2.5,升級(jí)到EMPS之后,OEA版本是2.6。

1 背景與需求

本節(jié)主要說明整個(gè)系統(tǒng)設(shè)計(jì)之初,設(shè)計(jì)的背景及最終整理出來的需求列表。這些需求是前期不斷收集、累積的結(jié)果。接下來,將會(huì)詳細(xì)說明一些主要的需求:

1.1 產(chǎn)品 721 客戶化開發(fā)的需要

部門的幾個(gè)產(chǎn)品都是基于 OEA 平臺(tái)開發(fā)的。OEA 平臺(tái)主要解決產(chǎn)品開發(fā)模式下客戶化開發(fā)、以及在產(chǎn)品開發(fā)過程中如何提高開發(fā)效率兩大問題。

(關(guān)于產(chǎn)品開發(fā)中的721概念及OEA中的客戶化設(shè)計(jì),參見:《基于OEA框架的客戶化設(shè)計(jì)(三) “插件式”DLL》。關(guān)于 OEA 的了解,參見:《OEA 框架演示 - 快過原型的產(chǎn)品開發(fā)》。)

客戶化開發(fā)中,主要解決的問題是如何在客戶化版本中對(duì)主干版本中的產(chǎn)品進(jìn)行擴(kuò)展。各種擴(kuò)展一般都依托于底層的元數(shù)據(jù),這些元數(shù)據(jù)描述整個(gè)系統(tǒng)。當(dāng)我們對(duì)元數(shù)據(jù)進(jìn)行修改時(shí),整個(gè)應(yīng)用程序也就發(fā)生了相應(yīng)的變化。這些產(chǎn)品的擴(kuò)展可以簡單分為:模塊級(jí)別的擴(kuò)展、實(shí)體級(jí)別的擴(kuò)展、屬性級(jí)別的擴(kuò)展。模塊的擴(kuò)展在此不進(jìn)行討論。

先說屬性擴(kuò)展:我們一般會(huì)對(duì)產(chǎn)品中定義好的類的屬性進(jìn)行以下擴(kuò)展:添加一個(gè)屬性、刪除一個(gè)屬性、修改一個(gè)屬性。(所以,擴(kuò)展并不只是意味著添加。)添加屬性意味著我們需要為已經(jīng)定義完成的類添加一個(gè)額外的屬性,這個(gè)屬性可以映射到數(shù)據(jù)庫,可以在產(chǎn)品界面中顯示,行為和直接定義的屬性是一致的。刪除屬性則意味著,數(shù)據(jù)庫中不再有對(duì)應(yīng)的字段,界面不再顯示。修改屬性一般只會(huì)修改屬性的各種元數(shù)據(jù),例如,修改它映射數(shù)據(jù)庫的字段元數(shù)據(jù),修改它在界面中顯示的列的元數(shù)據(jù)等;這些修改其實(shí)已經(jīng)在元數(shù)據(jù)的設(shè)計(jì)方案中解決,相關(guān)內(nèi)容可以查看:《基于OEA框架的客戶化設(shè)計(jì)(一) 總體設(shè)計(jì)》、《基于OEA框架的客戶化設(shè)計(jì)(二) 元數(shù)據(jù)設(shè)計(jì)》以及《基于OEA框架的客戶化設(shè)計(jì)(三) “插件式”DLL》。

實(shí)體的擴(kuò)展一般可以通過繼承的方法實(shí)現(xiàn),當(dāng)繼承出新的子類后,在元數(shù)據(jù)中用它將原來的父類進(jìn)行覆蓋即可。有些時(shí)候,我們還會(huì)為某個(gè)類擴(kuò)展一些聚合父子關(guān)系,例如:我們可以為某一個(gè)建設(shè)項(xiàng)目擴(kuò)展出其相關(guān)的合同列表,這樣,原來只顯示項(xiàng)目的界面中,就能緊接著顯示每一個(gè)項(xiàng)目相應(yīng)的合同列表。而這種聚合父子關(guān)系的擴(kuò)展,雖然是實(shí)體級(jí)別的添加,但是實(shí)質(zhì)上是對(duì)實(shí)體添加新的一對(duì)多關(guān)系。也就是說,這種實(shí)體的擴(kuò)展,可以轉(zhuǎn)換為屬性擴(kuò)展,即在原有實(shí)體的基礎(chǔ)上擴(kuò)展一個(gè)一對(duì)多關(guān)系的屬性。

基于以上分析,我們知道,一個(gè)可擴(kuò)展的屬性系統(tǒng),幾乎是客戶化軟件產(chǎn)品運(yùn)行時(shí)的最基礎(chǔ)設(shè)施。

在 2.6 版本之前的 OEA,屬性擴(kuò)展主要使用繼承的方式來實(shí)現(xiàn)。簡單地說,就是繼承需要擴(kuò)展的實(shí)體,添加新的屬性,然后使用這個(gè)實(shí)體替換掉原來的類。該方案主要是為了實(shí)現(xiàn)屬性的添加,但是屬性的刪除以及修改都是通過修改屬性的元描述來實(shí)現(xiàn)的。這樣的方式導(dǎo)致了許多問題:屬性的刪除只是刪除了界面,而數(shù)據(jù)庫、運(yùn)行時(shí)實(shí)體也都還存在該屬性;屬性的修改不能修改屬性中的行為代碼;重點(diǎn)說下屬性的添加造成的缺點(diǎn):

經(jīng)常需要對(duì)某個(gè)類擴(kuò)展一兩個(gè)屬性,而現(xiàn)在只能繼承出子類,同時(shí)把父類隱藏起來,或者直接覆蓋父類,用進(jìn)來比較復(fù)雜; 同時(shí),類型變多,開發(fā)人員的學(xué)習(xí)成本,維護(hù)成本都隨之變大。

更重要的是,.NET 中 CLR 單繼承體系的限制,使得通過繼承無法實(shí)現(xiàn)這樣的擴(kuò)展: 兩個(gè)獨(dú)立的擴(kuò)展包“2”以可選的形式對(duì)主包“7”進(jìn)行擴(kuò)展,也就是說,產(chǎn)品 721 客戶化開發(fā)中,兩個(gè)“2”的擴(kuò)展包是兩個(gè)單獨(dú)的程序集,但是單繼承的限制,我們不能同時(shí)使用它們。對(duì)于這種情況,我們目前的處理方式是把兩個(gè)“2”的包都放到了主包中,而使用元數(shù)據(jù)的方式對(duì)不需要的功能來進(jìn)行隱藏,這種實(shí)現(xiàn)方式是臨時(shí)的、錯(cuò)誤的。

1.2 實(shí)體動(dòng)態(tài)列

軟件開發(fā)中常常遇到動(dòng)態(tài)列的需求:表格中的數(shù)據(jù)的列是根據(jù)數(shù)據(jù)本身自動(dòng)生成的,這對(duì)于基于領(lǐng)域?qū)嶓w類型、基于非動(dòng)態(tài)類型的技術(shù)框架來開發(fā)的系統(tǒng)來說,要實(shí)現(xiàn)動(dòng)態(tài)列基本上不可能。所以往往應(yīng)用程序會(huì)另辟捷徑,使用 DataTable 來重新組裝數(shù)據(jù)后再顯示。這導(dǎo)致兩種模式同時(shí)存在于一個(gè)系統(tǒng)中,同樣的代碼會(huì)重復(fù)出現(xiàn),增加維護(hù)成本。界面的代碼不一致,也加大了界面自動(dòng)生成的困難。

如果有了擴(kuò)展屬性,我們則可以在任意實(shí)體上擴(kuò)展各種新的屬性,界面也就相應(yīng)地成了“動(dòng)態(tài)”列。

1.3 分離只讀/視圖屬性

實(shí)體設(shè)計(jì)中常常會(huì)添加一些只讀的屬性,它的值是使用實(shí)體當(dāng)前的值經(jīng)過計(jì)算后得出。在 OEA 中,實(shí)體被設(shè)計(jì)為分布式對(duì)象(簡單地說,就是客戶端和服務(wù)端重用一套實(shí)體代碼。可以參見CSLA框架設(shè)計(jì)書籍《Expert C# 2008 Business Objects》。),這些分布式對(duì)象被直接綁定到界面上。為了界面顯示的需要,常常會(huì)為它們添加許多只讀的視圖屬性,這樣就導(dǎo)致了視圖屬性過多,混雜在領(lǐng)域?qū)嶓w的代碼中,污染了代碼,加大維護(hù)難度。

如果有了擴(kuò)展屬性,我們則可以把這個(gè)只讀屬性都放到一個(gè)單獨(dú)的類中去為這個(gè)實(shí)體做擴(kuò)展,這樣,就可以得到更簡潔、結(jié)構(gòu)更清晰的代碼。

1.4 提升框架性能

對(duì)于框架開發(fā)來說,常常需要在框架中對(duì)實(shí)體的屬性做統(tǒng)一的處理,來向應(yīng)用層提供強(qiáng)大的功能支持。如果使用一般的實(shí)體設(shè)計(jì),那么屬性值的獲取、設(shè)置都不可避免地要使用到反射。而大量的屬性值操作將會(huì)意味著較差的性能。如果有了托管屬性,則在框架層面能夠使用和應(yīng)用一致的屬性 API 來操作屬性,不再使用反射,速度可以有不少提升。

1.5 支持 WPF 綁定

一般情況下,我們使用 WPF 綁定時(shí),都是直接綁定到 CLR 托管屬性上。但是,如果使用擴(kuò)展屬性的話,并不是所有屬性都會(huì)有一個(gè) CLR 屬性封裝器。所以,這些擴(kuò)展屬性必須支持 WPF 綁定也是我們的需求之一。

1.6 其它需求

l 支持屬性反擴(kuò)展

在產(chǎn)品 721 開發(fā)中,常常在 “1” 的客戶化版本中需要?jiǎng)h除 “2”版本中為“7”擴(kuò)展的屬性,這時(shí),需要支持屬性的反擴(kuò)展(或叫反注冊(cè))。

l 獲取屬性值來源

由于目前 OEA 框架中的實(shí)體是分布式對(duì)象,我們常常需要在實(shí)體屬性改變時(shí)分辨屬性值的來源:是數(shù)據(jù)庫,還是UI界面,還是來自程序中的其它代碼。

l 定制序列化的數(shù)據(jù)

實(shí)體屬性被框架管理后,可以很輕易地實(shí)現(xiàn)各種數(shù)據(jù)格式的序列化。

l 需要支持屬性值的驗(yàn)證、強(qiáng)制、更改通知等事件通知。

l 元數(shù)據(jù)重載

屬性的一切行為都將以回調(diào)的形式存放在元數(shù)據(jù)中。而元數(shù)據(jù)是可以被重載的。這樣,子類就才重寫這些行為。同時(shí),我們就可以在進(jìn)行產(chǎn)品客戶化的時(shí)候,為屬性重新定制這些行為。

最后,可以看一下在《實(shí)體擴(kuò)展屬性方案分析腦圖》腦圖文檔中整理出來的需求概況圖,這些需求都是歷史版本中所不能支持的:

圖1. 實(shí)體擴(kuò)展屬性需求列表

2 分析

由于前面已經(jīng)把需求整理得比較明朗了。那么這里,我們首先要分析出主要需求、約束及相關(guān)的風(fēng)險(xiǎn)等。(關(guān)于框架設(shè)計(jì)的整個(gè)過程,可以參考這篇文章:《框架模塊設(shè)計(jì)經(jīng)驗(yàn)總結(jié)》。)

2.1 主要功能需求

其實(shí)在圖一中已經(jīng)把需求按照優(yōu)先級(jí)別進(jìn)行了劃分,后面的整個(gè)設(shè)計(jì)將會(huì)圍繞這些需求進(jìn)行。其中,最主要的功能性需求是以下三個(gè)。而設(shè)計(jì)目標(biāo)則是至少實(shí)現(xiàn)以下三個(gè)需求,其它需求則按優(yōu)先級(jí)盡可能實(shí)現(xiàn)。

l 721客戶化開發(fā)中的屬性擴(kuò)展

l 屬性托管(受框架管理)
意思是需要為上層框架提供統(tǒng)一維護(hù)屬性值的功能。

l 動(dòng)態(tài)列

2.2 非功能需求分析

l 運(yùn)行時(shí)性能

實(shí)體屬性可以說是實(shí)體設(shè)計(jì)中最重要的部分。而它的性能好壞則關(guān)系到系統(tǒng)中每一個(gè)實(shí)體的每一個(gè)屬性,這些屬性都直接關(guān)系到應(yīng)用的性能。簡單地說,如果屬性系統(tǒng)慢,上層應(yīng)用的性能必然會(huì)慢。換句話說,屬性系統(tǒng)的代碼開發(fā)是對(duì)性能十分敏感的,在核心代碼上需要十分謹(jǐn)慎。

2.5 版本的OEA框架使用的屬性主要還是 .NET 中的原生 CLR屬性系統(tǒng) + CSLA 開源框架中的屬性系統(tǒng)。主要是為了支持屬性的統(tǒng)一管理。而本次設(shè)計(jì),可以對(duì)系統(tǒng)帶來許多的新功能和支持,加之原有系統(tǒng)的屬性性能并沒有構(gòu)成應(yīng)用層開發(fā)的性能問題,所以,一定的性能消耗是可以接受的。

對(duì)這項(xiàng)的要求是:

使用同樣的代碼,和歷史屬性系統(tǒng)進(jìn)行屬性測試對(duì)比,耗時(shí)不能超過原有的120%。

比較簡單,也比較嚴(yán)格。一旦不滿足此項(xiàng),整個(gè)設(shè)計(jì)不可以被使用。

l 獨(dú)立性

雖然實(shí)體擴(kuò)展屬性系統(tǒng)是作為 OEA 框架的一個(gè)重要組成部分,但是托管屬性、擴(kuò)展屬性的需求在開發(fā)過程中常常會(huì)碰到。所以我們需要把實(shí)體擴(kuò)展屬性系統(tǒng)設(shè)計(jì)為一個(gè)獨(dú)立的 DLL,這樣,它就可以在非 OEA平臺(tái)的環(huán)境中使用。

l 可擴(kuò)展性

EMPS的可擴(kuò)展性并不是指該系統(tǒng)帶來的屬性的可擴(kuò)展性(這其實(shí)是EMPS的功能需求),而是指屬性系統(tǒng)本身需要進(jìn)行一些擴(kuò)展。

當(dāng)前,OEA框架中以產(chǎn)品元數(shù)據(jù)為整個(gè)框架的基礎(chǔ)設(shè)施。也就是說,OEA 框架中有管理應(yīng)用中所有元數(shù)據(jù)的功能。而由圖1中的需求列表可以看到,EMPS也需要元數(shù)據(jù)的支持,例如屬性的默認(rèn)值。但是,獨(dú)立性中已經(jīng)要求EMPS被設(shè)計(jì)為一個(gè)完全獨(dú)立的模塊,也就是說EMPS完全不依賴 OEA。那么,這些屬性的元數(shù)據(jù)如何支持使用 OEA 來進(jìn)行保存呢?這,同樣是EMPS 設(shè)計(jì)過程中需要特殊考慮的一個(gè)擴(kuò)展點(diǎn)。

l 易用性

此項(xiàng)為框架設(shè)計(jì)必須考慮的一個(gè)非功能需求。

2.3 約束

l ORM功能的修改

原來的OEA的ORM中支持使用OEAORM及EntityFramework4.1(CodeFirst)兩種模式,但是這兩種ORM當(dāng)前無疑都只支持對(duì)CLR屬性的映射。而擴(kuò)展屬性是沒有CLR屬性包裝器的,但是這些擴(kuò)展屬性同樣需要映射數(shù)據(jù)庫。

也就是說:如果EMPS開發(fā)完成,要映射新的擴(kuò)展屬性,必須要修改當(dāng)前OEAORM模塊。同時(shí),無法再支持EntityFramework4.1了(EFCodeFirst基于CLR屬性來進(jìn)行映射)。

l 原有屬性功能的兼容

2.5 版本的OEA使用的屬性主要還是 .NET 中的原生 CLR屬性系統(tǒng) + CSLA 開源框架中的屬性系統(tǒng)。這些屬性中已經(jīng)寫了非常多的代碼。屬性的 Get 獲取器及 Set 設(shè)置器中的代碼,可謂五花八門。這些都必須在新的屬性系統(tǒng)中被完全兼容,否則,必須導(dǎo)致業(yè)務(wù)功能出現(xiàn)問題。

l 大量歷史代碼的修改

由于本次設(shè)計(jì)本質(zhì)上是一次在歷史版本上的重構(gòu),而產(chǎn)品開發(fā)截止到目前,已經(jīng)產(chǎn)生了幾萬行的歷史代碼,其中的實(shí)體屬性也是幾千個(gè)。重構(gòu)如此底層的設(shè)計(jì),在盡量保證應(yīng)用層 API 不變的前提下,也必然會(huì)造成較多的修改,同時(shí),很可能會(huì)引起比較多的BUG。這是一個(gè)必須考慮的約束條件。

2.4 風(fēng)險(xiǎn)

l 屬性性能

由非功能需求的描述中知道,性能是至關(guān)重要的。關(guān)系到整個(gè)設(shè)計(jì)是否可用。但是,最終開發(fā)出來的模塊性能,在設(shè)計(jì)時(shí)很難測量的。對(duì)于這個(gè)風(fēng)險(xiǎn)的規(guī)避使用以下方案:分析歷史屬性系統(tǒng)的關(guān)鍵性能影響點(diǎn),在設(shè)計(jì)稿完成后,理論上檢查這些關(guān)鍵點(diǎn)是否能在新設(shè)計(jì)出來的屬性系統(tǒng)下運(yùn)行良好。

l 支持WPF綁定

這是一個(gè)技術(shù)難關(guān)。

當(dāng)前我們只是使用了 WPF 中直接綁定CLR屬性的方案。如何能讓我們?cè)诳蛻艋姹镜某绦蚣袛U(kuò)展的擴(kuò)展屬性也支持WPF綁定,成為了一個(gè)技術(shù)上的難題。

對(duì)這點(diǎn)的規(guī)避很簡單,在整個(gè)設(shè)計(jì)開始之前,先分析WPF綁定中的內(nèi)部機(jī)制,解決這個(gè)問題后,才能開始其它的設(shè)計(jì)。

3 設(shè)計(jì)方案

3.1 一些決策

由于本系統(tǒng)的設(shè)計(jì)比較復(fù)雜。所以先對(duì)兼容性約束做了一個(gè)決策:

在設(shè)計(jì)過程中盡量考慮功能上與原屬性系統(tǒng)保持兼容,接口上保持一致。但是當(dāng)無法兼容或者無法保持一致的接口時(shí),可以不兼容。但是這些不兼容的設(shè)計(jì)點(diǎn),都需要記錄下來,當(dāng)設(shè)計(jì)完成后,逐個(gè)修改。如果改動(dòng)較大,則使用組內(nèi)的重構(gòu)工具完成。

3.2 風(fēng)險(xiǎn)點(diǎn)驗(yàn)證

3.2.1 支持 WPF 綁定

經(jīng)過查閱MSDN及搜索出的網(wǎng)絡(luò)資源,發(fā)現(xiàn)WPF中的綁定機(jī)制支持綁定DataTable數(shù)據(jù)表類型,而表中的字段則是動(dòng)態(tài)的,根據(jù)結(jié)果數(shù)據(jù)的變化而變化。所以只要搞清楚DataTable是如何被WPF綁定支持的,那么EMPS也可以使用同樣的機(jī)制進(jìn)行綁定。

以下是WPF中DataTable的綁定機(jī)制分析:

圖2. WPF中DataTable支持綁定的核心類型分析

圖3. WPF中為DataTable生成視圖模型的流程圖

重點(diǎn)在于DataTable 實(shí)現(xiàn) IListSource接口,并構(gòu)造動(dòng)態(tài)的視圖動(dòng)態(tài)類型 DataRowView并使其實(shí)現(xiàn)ICustomTypeDescriptor。(詳細(xì)過程參見這篇文章:《OEA 擴(kuò)展屬性系統(tǒng) - 任意適配 WPF Binding 的設(shè)計(jì)分析》,以及本系列中的文檔:《任意適配 WPF Binding 的設(shè)計(jì)分析》。)

搞清楚了整個(gè)設(shè)計(jì)及創(chuàng)建流程,那么其實(shí)在設(shè)計(jì)EMPS時(shí),支持這個(gè)機(jī)制就可以了。

3.2.2 性能關(guān)鍵點(diǎn)

需要分析,歷史框架中的屬性系統(tǒng)(CSLA托管屬性系統(tǒng))在做到托管屬性的同時(shí),是如何保證性能的呢?

其實(shí),它其中屬性的核心重點(diǎn)在于使用強(qiáng)類型的FieldData<T>來存儲(chǔ)每一個(gè)屬性,并使用定長的屬性值的數(shù)組來存放:

private IFieldData[] _fieldData;

這樣的好處在于強(qiáng)類型保證了沒有裝箱拆箱操作,同時(shí)定長的數(shù)組支持了以O(shè)(1)的復(fù)雜度來查找指定屬性:

但是,這樣搜索屬性的前提是屬性值數(shù)組定長,而一個(gè)實(shí)體類型到底有多少個(gè)屬性,是在編譯期已經(jīng)完全確定下來的。換句話說,在這個(gè)數(shù)組初始化時(shí)必須知道固定的屬性個(gè)數(shù),這違背了屬性可擴(kuò)展的需求,這也是為什么使用這個(gè)屬性系統(tǒng)很難做到擴(kuò)展的原因。

當(dāng)然,在對(duì)其進(jìn)行較大改動(dòng)的前提下,也不是不可能。但是考慮到CSLA是個(gè)開源框架,其滿足需求與我們的需求有較大的區(qū)別,代碼比較臃腫,也無法實(shí)現(xiàn)我們所需要的一些功能,對(duì)它做大型的改動(dòng)不如重新做一個(gè)完全符合需求的托管屬性框架。

經(jīng)過之前的分析,可以想到,要得到較高性能的托管屬性系統(tǒng),最好也是使用“強(qiáng)類型存儲(chǔ)屬性值”加“定長數(shù)組”的方案。但是如何支持屬性的擴(kuò)展呢?“劃分屬性定義期”是個(gè)較好的解決方案。之后的主體設(shè)計(jì)中會(huì)對(duì)這個(gè)方案進(jìn)行詳細(xì)的描述。

3.3 方案描述

整個(gè)設(shè)計(jì)中,借鑒了CSLA托管屬性以及WPF依賴屬性的設(shè)計(jì),然后再構(gòu)建出我們自己的屬性系統(tǒng):

3.3.1 結(jié)構(gòu)說明

圖4. EMPS結(jié)構(gòu)說明

腦圖比較簡單,其中的具體內(nèi)容可以參考腦圖《擴(kuò)展屬性方案》。這里只做簡要說明:

l 靜態(tài)結(jié)構(gòu)

總體上,靜態(tài)結(jié)構(gòu)比較簡單,主要分為兩個(gè)層次。底層是抽象的屬性元數(shù)據(jù)提供子系統(tǒng),而另一層則是依賴于前者而構(gòu)建的EMPS核心:運(yùn)行時(shí)擴(kuò)展屬性子系統(tǒng)。

提取抽象的屬性元數(shù)據(jù)提供系統(tǒng)是為了使元數(shù)據(jù)的存儲(chǔ)、提供都抽象化,后面可以和 OEA 中的元數(shù)據(jù)存儲(chǔ)模塊進(jìn)行適配。

而核心的EMPS則實(shí)現(xiàn)了整個(gè)的托管屬性。后面將會(huì)對(duì)其以類圖的形式重點(diǎn)說明。

l 動(dòng)態(tài)結(jié)構(gòu)

在這里比較特殊地提出了屬性生命周期的概念。屬性的生命周期規(guī)定了屬性被定義(或者被反注冊(cè))的時(shí)期(可能叫定義期會(huì)比較正確。),這里主要有編譯期、啟動(dòng)期、運(yùn)行期。

l 編譯期

此階段中定義的屬性主要包括使用代碼編寫的一般屬性、擴(kuò)展屬性。當(dāng)然,也包括“2”和“1”的擴(kuò)展包中編寫的一些對(duì)“7”的包中實(shí)體類進(jìn)行擴(kuò)展的擴(kuò)展屬性。

定義屬性時(shí),一同指定它對(duì)應(yīng)的元數(shù)據(jù)。

l 啟動(dòng)期

此階段主要以客戶化定義的方式來對(duì)編譯期屬性及其相應(yīng)的元數(shù)據(jù)進(jìn)行修改。

l 運(yùn)行期

該階段主要用于附加運(yùn)行時(shí)動(dòng)態(tài)屬性。

這些動(dòng)態(tài)屬性一般只用于顯示,它們會(huì)影響界面的生成。屬性的擴(kuò)展和刪除,要在生成控件之前就能確定,否則,界面沒有對(duì)應(yīng)的列。

由于影響界面生成,所以需要為其指定OEA框架中對(duì)應(yīng)的界面元數(shù)據(jù)。如果不指定,則使用默認(rèn)元數(shù)據(jù)。不過這些元數(shù)據(jù)的設(shè)計(jì)會(huì)在OEA框架中完成,與EMPS的設(shè)計(jì)無關(guān)。

在這個(gè)階段中擴(kuò)展的附加屬性,不會(huì)與服務(wù)端程序有任何關(guān)系。也就是說,不需要為這些擴(kuò)展屬性定義 ORM 等服務(wù)端元數(shù)據(jù)。當(dāng)然了,這些屬性的數(shù)據(jù)也不需要序列化后在網(wǎng)絡(luò)上進(jìn)行傳輸。

劃分出這幾個(gè)周期的主要原因:使得可以判斷出某個(gè)實(shí)體的編譯期、啟動(dòng)期屬性列表長度。這是因?yàn)?#xff0c;編譯期和啟動(dòng)期已經(jīng)定義、修改或者客戶化的屬性,當(dāng)程序進(jìn)入運(yùn)行時(shí)后是不會(huì)再發(fā)生改變的。而這些屬性占據(jù)了應(yīng)用開發(fā)的95%以上。所以我們只要知道了編譯期啟動(dòng)期屬性的長度,也就意味著可以使用O(1)檢索的數(shù)組來存放,而不是更慢的List/HashTable,保證了這些屬性的性能。而對(duì)于運(yùn)行時(shí)屬性來說,雖然它的長度不能固定,根據(jù)業(yè)務(wù)場景而變化,但是使用情況較少,可以不考慮性能。

3.3.2 相關(guān)UML圖

完整的UML圖,參見:《實(shí)體擴(kuò)展屬性UML設(shè)計(jì)圖》。下面將挑選重點(diǎn)進(jìn)行說明。

圖5.擴(kuò)展屬性核心類結(jié)構(gòu)概要設(shè)計(jì)圖

這張是實(shí)現(xiàn)擴(kuò)展屬性的核心類結(jié)構(gòu)概要設(shè)計(jì)圖,其中主要包含 ManagedPropertyObject、ManagedProperty、ManagedPropertyField、ManagedPropertyMeta等。由于是概要設(shè)計(jì)圖,其中的方法、屬性等相對(duì)實(shí)現(xiàn)完成后的系統(tǒng)來比,肯定不完整,但是它的作用主要是說明整個(gè)設(shè)計(jì)的核心思想。其中:

ManagedProperty 表示托管屬性,每定義一個(gè)托管屬性,系統(tǒng)都會(huì)生成一個(gè)此類型的對(duì)象用于標(biāo)記。獲取、設(shè)置屬性的值時(shí),都需要提供此標(biāo)記來進(jìn)行檢索。

ManagedPropertyMeta 表示托管屬性元數(shù)據(jù),其中提供了許多信息,例如:默認(rèn)值、是否只讀、屬性變更邏輯回調(diào)等;這些元數(shù)據(jù)對(duì)屬性值的獲取、設(shè)置的邏輯都有著比較大的影響。

ManagedPropertyField 表示某個(gè)對(duì)象中某個(gè)托管屬性對(duì)應(yīng)的值。其實(shí)這個(gè)類后期在實(shí)現(xiàn)時(shí)會(huì)被定義為泛型類,這樣,值的存儲(chǔ)就不是object而是強(qiáng)類型的,不需要裝箱拆箱操作。

ManagedPropertyObject 表示擁有托管屬性的對(duì)象基類(實(shí)體),其中定義了根據(jù)ManagedProperty來獲取、設(shè)置值的接口,這使得該對(duì)象能夠象一般對(duì)象一樣獲取、存儲(chǔ)各種值。同時(shí),它也提供了統(tǒng)一處理所有托管屬性值的接口,此類統(tǒng)一處理的接口在應(yīng)用開發(fā)時(shí)很少用到,主要給上層的框架使用。上層框架可以應(yīng)用這些接口完成以下的框架任務(wù):統(tǒng)一的對(duì)象值拷貝、統(tǒng)一的序列化、檢索特定類型的值等,這樣的值的獲取、設(shè)置速度,遠(yuǎn)比反射要快。

圖6. 擴(kuò)展屬性倉儲(chǔ)概要設(shè)計(jì)圖

這張圖說明整個(gè)系統(tǒng)中的托管屬性都是被系統(tǒng)中的單例對(duì)象 ManagedPropertyRepository 給管理起來的,為了給上層提供更方便的查詢功能,也方便存儲(chǔ),它使用 TypeIndicators 類來存儲(chǔ)某個(gè)實(shí)體類型的屬性列表。TypeIndicators這個(gè)類也負(fù)責(zé)為上層提供查詢:某一個(gè)類型已經(jīng)定義好的屬性列表、某一類型及其所有父類定義的所有屬性的聯(lián)合屬性列表。同時(shí),這個(gè)類中的屬性都會(huì)生成在類型中的屬性的索引,這樣,在獲取屬性值時(shí)就可以使用這個(gè)索引在屬性值數(shù)組中進(jìn)行屬性值的查找。

圖7.擴(kuò)展屬性元數(shù)據(jù)概要設(shè)計(jì)圖

前面提到過,為了保證 EMPS的獨(dú)立性,我們需要把托管屬性元數(shù)據(jù)的數(shù)據(jù)獲取方案抽象化,這里的 IPropertyMetaProvider 就是抽象的元數(shù)據(jù)提供器接口。而上層的OEA框架則會(huì)實(shí)現(xiàn)自己的提供器 OEAPropertyMetaProvider,并通過自己的元數(shù)據(jù)模塊中的信息(例如圖中的OEAPropertyMeta)來為托管屬性提供元數(shù)據(jù)。

圖8. 擴(kuò)展屬性實(shí)體實(shí)現(xiàn)WPF綁定相關(guān)概要設(shè)計(jì)圖

這張圖看上去會(huì)比較眼熟?沒錯(cuò),它和圖2中的WPF支持DataTable綁定的類圖比較相似。主要也是讓 EntityList 實(shí)現(xiàn) IListSource接口,并添加 EntityView 類實(shí)現(xiàn) ICustomTypeDescriptor 接口,這樣,就可以實(shí)現(xiàn)動(dòng)態(tài)屬性的WPF綁定了。

3.3.3 如何支撐需求

主要的設(shè)計(jì)方案及類圖看完之后,我們需要考慮,整個(gè)方案是否能支撐起前面說到的所有需求。下圖是對(duì)整個(gè)需求的可支撐性進(jìn)行分析。相關(guān)內(nèi)容可以參見:《實(shí)體擴(kuò)展屬性方案分析腦圖》,在此不再贅述。

圖9. 設(shè)計(jì)方案對(duì)需求的支持度分析。

3.4 重點(diǎn)實(shí)現(xiàn)細(xì)節(jié)

在對(duì)需求支持分析之后,再經(jīng)過召開的設(shè)計(jì)評(píng)審會(huì)議,發(fā)現(xiàn)這樣的方案可以實(shí)現(xiàn)基本全部需求。這樣,就進(jìn)入實(shí)現(xiàn)環(huán)節(jié)了。實(shí)現(xiàn)環(huán)節(jié)就關(guān)注更多了細(xì)節(jié)性設(shè)計(jì)。本文檔將會(huì)挑選一部分重點(diǎn)進(jìn)行說明。

首先,先來看看最終完成的代碼中,最核心部分的代碼結(jié)構(gòu)圖:

圖10. 核心代碼結(jié)構(gòu)圖

整個(gè)結(jié)構(gòu)的實(shí)現(xiàn)與設(shè)計(jì)相差無幾。接下來,說明一些相對(duì)重要的代碼:

l 先是ManagedPropertyObject中的屬性值獲取、設(shè)置相關(guān)代碼:

前面的設(shè)計(jì)方案中提到,這個(gè)類主要作為所有實(shí)體類的基類,提供值的獲取、設(shè)置等。而這個(gè)類其實(shí)是把屬性值的管理都放到了內(nèi)部的一個(gè)類ManagedPropertyObjectFieldsManager 中:

并把相關(guān)的操作都代理到這個(gè)類上:

而 ManagedPropertyObjectFieldsManager則實(shí)現(xiàn)了這些邏輯的核心代碼。其中,它的私有字段定義如下:

可以看到,編譯期、啟動(dòng)期屬性值與運(yùn)行期屬性值被分開存放。前者使用數(shù)組,構(gòu)造函數(shù)直接初始化,而后者則在需要時(shí)才會(huì)被序列化。還注意到,它繼承自CustomSerializationObject,使得整個(gè)屬性值列表是可以被自定義序列化的。下面,是泛型的屬性值獲取與設(shè)置邏輯:

internal TPropertyType GetProperty<TPropertyType>(ManagedProperty<TPropertyType> property)

{

var useDefault = true;

??? TPropertyType result = default(TPropertyType);

if (property.IsReadOnly)

??? {

??????? result = (property as ManagedProperty<TPropertyType>).ProvideReadOnlyValue(this._owner);

??????? useDefault = false;

??? }

else

??? {

if (property.LifeCycle == ManagedPropertyLifeCycle.CompileOrSetup)

??????? {

var field = this._compiledFields[property.TypeCompiledIndex] as ManagedPropertyField<TPropertyType>;

if (field != null)

??????????? {

??????????????? result = field.Value;

??????????????? useDefault = false;

??????????? }

??????? }

else

??????? {

if (this._runtimeFields != null)

??????????? {

IManagedPropertyField f;

if (this._runtimeFields.TryGetValue(property, out f))

??????????????? {

var field = f as ManagedPropertyField<TPropertyType>;

??????????????????? result = field.Value;

??????????????????? useDefault = false;

??????????????? }

??????????? }

??????? }

}

var meta = property.GetMeta(this);

if (useDefault) result = meta.DefaultValue;

result = meta.CoerceGetValue(this._owner, result);

return result;

}

internal void SetProperty<TPropertyType>(ManagedProperty<TPropertyType> property, TPropertyType value, ManagedPropertyChangedSource source)

{

??? ForceNotReadOnly(property);

var meta = property.GetMeta(this);

bool cancel = meta.RaisePropertyChanging(this._owner, ref value, source);

if (cancel) return;

var hasOldValue = false;

??? TPropertyType oldValue = default(TPropertyType);

ManagedPropertyField<TPropertyType> field = null;

//這個(gè) if 塊中的代碼:查找或創(chuàng)建對(duì)應(yīng) property 的 field,同時(shí)記錄可能存在的歷史值。

if (property.LifeCycle == ManagedPropertyLifeCycle.CompileOrSetup)

??? {

??????? field = this._compiledFields[property.TypeCompiledIndex] as ManagedPropertyField<TPropertyType>;

if (field == null)

??????? {

//不管是不是默認(rèn)值,都進(jìn)行存儲(chǔ)。

//不需要檢測默認(rèn)值更加快速,但是浪費(fèi)了一些小的空間。

//默認(rèn)值的檢測,在 GetNonDefaultPropertyValues 方法中進(jìn)行實(shí)現(xiàn)。

??????????? field = property.CreateField();

this._compiledFields[property.TypeCompiledIndex] = field;

??????? }

else

??????? {

??????????? oldValue = field.Value;

??????????? hasOldValue = true;

??????? }

??? }

else

??? {

if (this._runtimeFields == null)

??????? {

this._runtimeFields = new Dictionary<IManagedProperty, IManagedPropertyField>();

??????? }

else

??????? {

IManagedPropertyField f;

if (this._runtimeFields.TryGetValue(property, out f))

??????????? {

??????????????? field = f as ManagedPropertyField<TPropertyType>;

??????????????? oldValue = field.Value;

??????????????? hasOldValue = true;

??????????? }

??????? }

if (field == null)

??????? {

??????????? field = property.CreateField();

this._runtimeFields.Add(property, field);

??????? }

??? }

??? field.Value = value;

if (!hasOldValue) { oldValue = meta.DefaultValue; }

if (!object.Equals(oldValue, value))

??? {

//發(fā)生 Meta 中的事件

var args = meta.RaisePropertyChanged(

this._owner, oldValue, value, source

??????????? );

//發(fā)生事件

this._owner.RaisePropertyChanged(args);

??? }

}

可以看到,編譯期屬性主要通過一維數(shù)組進(jìn)行存放,數(shù)組中每一個(gè)元素都是強(qiáng)類型的泛型對(duì)象 ManagedPropertyField<TPropertyType>。

另外,要注意的是,該類提供了同樣的非泛型接口:

非泛型方法主要是為上次框架提供,其中主要考慮裝箱拆箱操作的性能消耗。(關(guān)于接口加泛型類的底層框架設(shè)計(jì)方案,參見:《重構(gòu)實(shí)踐:體驗(yàn)interface的威力(一)》、《重構(gòu)實(shí)踐:體驗(yàn)interface的威力(二)》。)

GetProperty、SetProperty 方法是對(duì)性能最敏感的兩個(gè)方法,其實(shí)現(xiàn)必須特別小心,其內(nèi)部調(diào)用的每一個(gè)方法,如 ManagedProperty.GetMeta(ManagedPropertyObject owner)、ManagedPropertyMetadata.RaisePropertyChanged等,也都必須要做特別的優(yōu)化,需要考慮到裝箱拆箱、屬性檢索、不構(gòu)造多余對(duì)象等。具體代碼設(shè)計(jì)參考實(shí)際工程中的代碼,在此不做過多描述。

l 注冊(cè)屬性(及反注冊(cè)屬性)的方法位于 ManagedPropertyRepository類中。

其主要的職責(zé)是構(gòu)造ManagedProperty對(duì)象,并計(jì)算它的一些重要屬性,例如:用于屬性快速Hash的GlobalIndex、用于編譯期屬性檢索值時(shí)使用的數(shù)組下標(biāo)TypeIndex等。

4 設(shè)計(jì)驗(yàn)證

4.1 功能需求驗(yàn)證

已經(jīng)為EMPS添加了豐富的單元測試,該項(xiàng)驗(yàn)證的內(nèi)容被整合到第5項(xiàng)中,參見:《5.使用手冊(cè)》。

4.2 WPF綁定驗(yàn)證

驗(yàn)證這個(gè)比較簡單,只要基于它的應(yīng)用程序運(yùn)行起來之后,界面上的值都能正常獲取、設(shè)置即可。

不過,我們還是為它加了相應(yīng)的單元測試,這個(gè)在后面會(huì)有描述。

4.3 性能驗(yàn)證

之前說到如果EMPS相比原來的屬性系統(tǒng),如果耗時(shí)超過120%,則該系統(tǒng)不可用。按照之前關(guān)鍵性能點(diǎn)的設(shè)計(jì),應(yīng)該是可以達(dá)到,但是,還是需要做出相應(yīng)的驗(yàn)證工作及最終的數(shù)據(jù)。

具體內(nèi)容可以參見:《實(shí)體擴(kuò)展屬性系統(tǒng)性能測試報(bào)告》。這里說明一下最終結(jié)論:

性能結(jié)論:

新的 OEA 托管屬性系統(tǒng),在帶來眾多新功能的同時(shí),不但沒有降低原有性能,反而因?yàn)閮?yōu)化掉無用的代碼,使得速度提升。故可以放心使用該屬性系統(tǒng)。

5 使用手冊(cè)

5.1 使用場景介紹(單元測試)

由于已經(jīng)為EMPS添加了比較豐富的單元測試,所以本使用手冊(cè)將主要以介紹單元測試的形式,覆蓋所有可能的使用場景,并介紹每一個(gè)場景其對(duì)應(yīng)的使用方法。

單元測試所使用的實(shí)體類包含下圖中的這些類:

右圖是所涉及到的所有單元測試。

“……………………內(nèi)容較多,省略,有興趣的可以看附件中的文檔………………”

5.2 代碼生成 – CodeSnippets

在《OEA 框架演示 - 快過原型的產(chǎn)品開發(fā)》中可以看到,OEA框架的快速開發(fā)能力中,編譯期的代碼生成技術(shù)是一個(gè)重要部分。我們同樣為EMPS的80%使用場景都編寫了CodeSnippets(關(guān)于CodeSnippets的使用方法,參見:《善用VS中的Code Snippet來提高開發(fā)效率》):

導(dǎo)入VS后,只要輸入OEAP……,VS就支持這些代碼片段的生成,如:

5.3 其它問題

5.3.1 擴(kuò)展屬性的CLR屬性編寫注意點(diǎn)

使用EMPS定義的屬性,如果不是擴(kuò)展屬性,都會(huì)定義一個(gè)對(duì)應(yīng)的CLR屬性包裝器,如:

注意,CLR屬性內(nèi),不能添加任何代碼,所有需要對(duì)Code屬性的Get、Set的定制代碼,都需要以回調(diào)的形式編寫在EMPS中,如:

原因是界面框架、ORM框架、WPF綁定等框架內(nèi)容都不會(huì)調(diào)用CLR屬性,而是直接調(diào)用GetProperty、SetProperty方法,而CLR中的代碼只是為了方便類庫的使用。

5.3.2 何時(shí)使用屬性擴(kuò)展,何時(shí)使用繼承擴(kuò)展?

EMPS雖然可以直接對(duì)某個(gè)實(shí)體類型進(jìn)行屬性的擴(kuò)展,但是我們依然老的方案,即使用CLR類繼承機(jī)制擴(kuò)展舊的實(shí)體。那么,我們需要特別注意兩種方案的區(qū)別:

1. 屬性擴(kuò)展是直接對(duì)指定的領(lǐng)域?qū)嶓w進(jìn)行擴(kuò)展,一旦擴(kuò)展,該領(lǐng)域?qū)嶓w類在整個(gè)應(yīng)用程序中的屬性都被擴(kuò)展。

2. 而繼承擴(kuò)展則需要用于不同的領(lǐng)域?qū)嶓w中。

簡單地說,當(dāng)你想在應(yīng)用程序中擴(kuò)展出一個(gè)新的領(lǐng)域?qū)嶓w類或者做一個(gè)全新的界面時(shí),則使用繼承擴(kuò)展。而當(dāng)在做客戶化時(shí),希望對(duì)現(xiàn)有的領(lǐng)域?qū)嶓w類進(jìn)行完全擴(kuò)展時(shí),則應(yīng)該使用EMPS來進(jìn)行屬性擴(kuò)展。

?

總結(jié)

以上是生活随笔為你收集整理的《OEA - 实体扩展属性系统 - 设计方案说明书》的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。