企業管理軟件開發架構之三 系統分層組織結構


我給MIS類型的軟件分四個實現層次,三層架構。

BusinessLogic 業務實體 由LLBL Gen 生成業務實體,代碼生成器生成

Interface 數據訪問接口 根據實體產生的數據訪問接口,由Code Smith生成

Manager 接口實現 根據實體產生的數據訪問接口的實現代碼,由Code Smith生成

UI 界面層 拖拉控件,綁定數據到界面中

image_thumb2

 

Business Logic 業務實體層

以ORM作為數據訪問基礎技術,業務實體中包含數據之間的關系邏輯,而不再是用於填充數據的實體。

image

以上結構由LLBL Gen自動生成,它已經為我們生成了實體,實體驗證類型,數據訪問接口和相關的輔助類型。

公司注冊中的公司實體,它的定義如下代碼所示

[Serializable]
public partial class CompanyEntity : CommonEntityBase
        // __LLBLGENPRO_USER_CODE_REGION_START AdditionalInterfaces
        // __LLBLGENPRO_USER_CODE_REGION_END    
{
        #region Class Member Declarations
        private EntityCollection<ModuleEntity> _modules;

        // __LLBLGENPRO_USER_CODE_REGION_START PrivateMembers
        // __LLBLGENPRO_USER_CODE_REGION_END
        #endregion

        #region Statics
        private static Dictionary<string, string>    _customProperties;
        private static Dictionary<string, Dictionary<string, string>>    _fieldsCustomProperties;

        /// <summary>All names of fields mapped onto a relation. Usable for in-memory filtering</summary>
        public static partial class MemberNames
        {
            /// <summary>Member name Modules</summary>
            public static readonly string Modules = "Modules";
        }
        #endregion
        
        /// <summary> Static CTor for setting up custom property hashtables. Is executed before the first instance of this entity class or derived classes is constructed. </summary>
        static CompanyEntity()
        {
            SetupCustomPropertyHashtables();
        }
        
        /// <summary> CTor</summary>
        public CompanyEntity():base("CompanyEntity")
        {
            InitClassEmpty(null, null);
        }

.....
}

LLBL Gen設計器生成的實體代碼有幾個特點

  • 生成有多個用途的構造方法(ctor)。我們經常用到的是不帶參數的構造方法,和帶有主鍵值參數的方法。
[EditorBrowsable(EditorBrowsableState.Never)]
protected CompanyEntity(SerializationInfo info, StreamingContext context) : base(info, context)
        {
            if(SerializationHelper.Optimization != SerializationOptimization.Fast) 
            {
                _modules = (EntityCollection<ModuleEntity>)info.GetValue("_modules", typeof(EntityCollection<ModuleEntity>));
                this.FixupDeserialization(FieldInfoProviderSingleton.GetInstance());
            }
            // __LLBLGENPRO_USER_CODE_REGION_START DeserializationConstructor
            // __LLBLGENPRO_USER_CODE_REGION_END
}
 

這個構造方法用在序列化對象時發生,比如.net Remoting遠程返回對象時。

 

  • 生成包含自定義屬性的字段 自定義屬性常用用屬性的特殊設置。比如CompanyEntity.CompanyCode,實際中為了不區分CompanyCode的大小寫,統一要求為大寫,我們可以在此添加自定義屬性RequiredCap,再到程序運行時讀取此屬性,並設置控件的字母大小寫特性。
private static Dictionary<string, string>    _customProperties;
private static Dictionary<string, Dictionary<string, string>>    _fieldsCustomProperties;


private static void SetupCustomPropertyHashtables()
        {
            _customProperties = new Dictionary<string, string>();
            _fieldsCustomProperties = new Dictionary<string, Dictionary<string, string>>();
            Dictionary<string, string> fieldHashtable;
            fieldHashtable = new Dictionary<string, string>();
            _fieldsCustomProperties.Add("CompanyCode", fieldHashtable);
......




Dictionary<string, string> fieldCustomProperties = CompanyEntity.FieldsCustomProperties["CompanyCode"];
string requiredCap = fieldCustomProperties["RequiredCap"];

讀取自定義屬性RequiredCap的值為true時,設置控件的CharachterCasing屬性。

image

 

界面和邏輯分離

再來看業務實體的業務計算如何發生。示例代碼如下所示

  protected override void OnFieldValueChanged(object originalValue, IEntityField2 field)
        {
            base.OnFieldValueChanged(originalValue, field);

            switch ((CompanyFieldIndex)field.FieldIndex)
            {
                case CompanyFieldIndex.DriverAssembly:
                    OnChangeDriverAssembly((string)originalValue);
                    break;
            }
        }

        private void OnChangeDriverAssembly(string originalValue)
        {
            if (this.DriverAssembly == originalValue || String.IsNullOrEmpty(DriverAssembly)) return;

            this.DriverType = BaseCommon.GetProjectName(ModuleType.BusinessLogic, DriverAssembly);
        }

當我在界面中改變當前界面插件程序集時,它會為我自動讀取這個程序集的類型信息,項目命名信息。要理解這種方式,需要先理解.NET開發中的數據綁定技術。數據源控件相當於一個橋梁,連接數據實體和界面控件,當給數據源控件賦值時,控件會讀取數據實體的值,當界面中的控件值發生改變時,借助於數據源控件,自動把更改后的數據回寫到數據實體中。所以,當數據實體中值發生改變后,我們可以注冊相應的改變事件,作出業務邏輯處理,數據源控件會讀取改變之后的數據實體值,呈現在界面上。幾乎所有的業務邏輯是依照此方式編程,也實現了界面和邏輯分離。

界面和邏輯分離后,界面中的作用就是將控件綁定到數據源控件,再以Code Smith來生成數據讀寫接口:

 public override EntityBase2 LoadEntity(string refNo)
        {
            IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
            ItemEntity customer = manager.GetItem(refNo);
            return customer;
        }

        public override void DeleteEntity(EntityBase2 entity)
        {
            ItemEntity user = (ItemEntity)entity;
            IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
            manager.DeleteItem(user);
        }

        public override void SaveEntity(EntityBase2 entity)
        {
            ItemEntity user = (ItemEntity)entity;
            IItemManager manager = ClientProxyFactory.CreateProxyInstance<IItemManager>();
            manager.SaveItem(user);
        }

系統中所有與數據庫讀寫相關的界面代碼均是以此方式實現。

 

Interface/Implementation 接口層和接口實現層

接口與它的實體均以Code Smith模板生成,效率高。如下所示的供應商接口

 public interface IVendorManager
    {
        VendorEntity GetVendor(System.String VendorNo);
        VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath);
        VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket);
        EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression);
        EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath);
        EntityCollection GetVendorCollection(IRelationPredicateBucket filterBucket, ISortExpression sortExpression, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList);

        VendorEntity SaveVendor(VendorEntity vendor);
        VendorEntity SaveVendor(VendorEntity vendor, EntityCollection entitiesToDelete);
        VendorEntity SaveVendor(VendorEntity vendor, EntityCollection entitiesToDelete, string seriesCode);
        void SaveCollection(EntityCollection vendors);

        void DeleteVendor(VendorEntity vendor);

        bool IsVendorExist(System.String VendorNo);
        bool IsVendorExist(IRelationPredicateBucket filterBucket);
        int GetVendorCount(IRelationPredicateBucket filterBucket);

        VendorEntity CloneVendor(System.String VendorNo);
        void PostVendor(System.String VendorNo);
        void PostVendor(VendorEntity vendor);
        void ApprovalItem(EntityCollection vendors);
    }

實現接口的Manager類型代碼例子如下

 public class VendorManager : Foundation.Common.ManagerBase, IVendorManager
    {
        public VendorEntity GetVendor(System.String VendorNo)
        {
            return GetVendor(VendorNo, null);
        }

        public VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath)
        {
            return GetVendor(VendorNo, prefetchPath, null);
        }

        public VendorEntity GetVendor(System.String VendorNo, IPrefetchPath2 prefetchPath, ExcludeIncludeFieldsList fieldList)
        {
            VendorEntity _Vendor = new VendorEntity(VendorNo);
            using (DataAccessAdapterBase adapter = GetCompanyDataAccessAdapter())
            {
                bool found = adapter.FetchEntity(_Vendor, prefetchPath, null, fieldList);
                if (!found) throw new Foundation.Common.RecordNotFoundException("Invalid Vendor");
            }
            return _Vendor;
        }

界面層中或是實體層,使用下面的接口來訪問接口:

ICompanyManager  _companyManager = ClientProxyFactory.CreateProxyInstance<ICompanyManager>();
CompanyEntity _company = _companyManager.GetCompany(“Kingston”)

如果沒有采用分布式技術(.net Remoting,WCF),CreateProxyInstance方法直接返回ICompanyManager接口的實體類型的實例,供接口調用。如果有應用.net Remoting技術,則先以下面的方法產生服務器對象:客戶端產生的實體對象會是一個遠程代理,指向遠程對象:

RemotingConfiguration.RegisterActivatedServiceType(type);

接口與實現分離的好處在這里體現的很明顯,簡單的切換部署模式(單機,分布式)不需要改變代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM