基于OEA框架的客户化设计(一) 总体设计

简介:

设计目标

  1. 支持实体类的扩展。
  2. 支持实体扩展包的动态加载。
  3. 支持界面扩展及界面扩展包的动态加载。
  4. 各版本间自定义界面元素,可以基于现有的特定版本修改一些内容。
  5. 各版本间支持自定义内容文件,如果没有使用,则使用默认版本的内容文件。(内容文件是指:图片、帮助文档等。)

    解释一下,基于OEA框架的GIX4项目是以领域实体为中心的架构。主版本中的领域实体,代表了产品功能“7、2、1”中的7和2 。7是所有版本都应该有的领域实体,2是可以进行配置以说明是否具备的领域实体,而1就是在主干之外,为特定版本开发的实体。所以以上目标中,支持对“2”的定制和对“1”的扩展是最重要的。

    由于时间仓促,目前只能以上述内容为目标,以后可能还会添加一些内容。如,枚举值的客户化,DailyBuild客户化等。


方案设计

    本次设计经过组内讨论,确定了具体的设计方向。这里主要对最重要的两项进行详细的叙述。

配置?

    一般来说,要实现客户化,使用配置可能是最直接的想法。一开始我想也没想就觉得可能客户化的内容需要存储在配置文件中,可能是一个自定义的XML文档。但是,后来和朋友聊天过程中灵光一闪,真的要采用配置吗?这里根本不需要在运行时动态改变应用程序的行为,只要在编译期能够编译出不同的版本即可,所以我决定使用“应用程序定义”的方式来完成“配置”。而“定义”与配置不同点在于,定义是用代码写死的,程序运行期间不可更改。编译期根据定义编译不同的版本。

    其实后来知道,产品线工程中的重点之一就是对产品的“可变性”进行管理。而可变性的实现机制有很多种,主要分三类:适配、替换、扩展,具体内容见:《软件产品线工程方法:如何在OpenExpressApp做客户化工作》。

    设计之初,我认为客户化的应用程序配置应该满足:

  1. 可以有公共的配置,子配置如果设置了同样的项,则重写公共的配置。
  2. 简单可用的配置API

    最后,我定出了以下的实现目标:

主干版本中有应用程序定义类ConfigMain,客户A和客户B分别有自定义的配置类ConfigA,ConfigB。

各客户的版本中,分别把他自己的配置类和主配置类结合,然后以配置文件的方式注入到整个应用程序中。

当应用程序读取某个配置项时,直接从注入的配置类中获取;此时,按照一定的寻找顺序,定位该配置项。如客户A的配置类为ConfigA + ConfigMain,则在寻找时,应该先在ConfigA中寻找,如果找不到,则在ConfigMain中寻找。

 

文件组织方式

    各客户版本需要不同的文件来运行,这些文件主要是一些内容文件,如图片,xml,也包含少量的DLL。毫无疑问地,客户化工作需要对它们进行管理。

    DLL文件的组织比较简单,只需要各客户版本把自己的DLL放在一个版本特定的目录下,程序动态加载就行了。

    这里我定出了以下规则:所有需要客户化的DLL都放在客户各自的文件夹根目录下。

    但是这里需要注意,这些代码文件需要在应用程序定义被加载之后,才会被应用程序加载。所以应用程序定义类需要被直接DI进来,这样,客户版本信息就可以在这些DLL加载之前被访问到,也就可以继续加载这些DLL了。

    内容文件的组织不同于代码,这些文件很可能在运行时也需要被替换。所以这里的策略不能再使用“定义”的方式。需要有一定的文件寻址算法。以下是暂定方案:

    所有需要客户化的文件都放在/Files/中。版本通用文件,则直接放在/Files/Common/中。各客户有自己的文件夹,如客户A有文件夹/Files/A/。文件夹名在配置类中标明。
程序中,可以文件寻找引擎指定要使用的文件的相对路径,如使用LOGO,则指定/Images/Logo.jpg。如果客户A在A中已经建立了/Files/A/Images/Logo.jpg文件,则返回此文件;否则返回的应该是/Files/Common/Images/Logo.jpg。

 

方案总结

    使用定义而不使用配置的方式,防止了不必要的程序代码的开发。但是要注意定义的API的简便和易用性。

    文件组织方式使得各客户文件完全分离,简化了Buid 版本的代码开发。这里主要注意路径寻址的实现。


具体设计

应用程序定义类的实现

    为支持属性值的重写和融合,应用程序定义类直接使用OO的继承实现,通用的定义类作为基类,分支版本直接从它派生下来并重写新的属性。使用OO的方式可以很好地实现属性值扩展,例如,我们可以使用装饰模式来实现复杂的属性定义。

    应用程序定义类中,应该组合一些分支对象,来进行更细粒度的定义。

    下图是本次客户化中应用程序定义类的结构:

image

图1 应用程序定义类的结构

    Freeable表示所有定义都是可以被冻结的。这些定义在一开始被设置好版本的值后,将会被冻结,所以内容不再改变,变为“不可变类”。一,这是其运行期不需要改变的体现;二,不可变类是高效的类。

    PathDefinition是所有内容文件的路径定义,它使用了PathProvider类来为其提供内容文件路径寻址算法,同时,它使用内容文件的相对路径从PathProvider中获取真实路径。

    UIInfo是视图信息的载体,该类是定义的重点,留待下一篇中介绍。

    AppDefinition是整个应用程序定义类的基类,以DI实现单例模式,作为全局唯一的访问点。目前,它包含了一个UIInfo对象来提供视图信息和一个PathDefinition来提供文件路径。

   

以下主要给出AppDefinition类具体的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/// <summary>
/// 应用程序的主干版本定义。
/// 同时,也是分支版本定义的基类。
/// </summary>
public  abstract  class  AppDefinition : Definition
{
     #region SingleTon
 
     private  static  AppDefinition _instance;
 
     /// <summary>
     /// 提供一个静态字段,作为全局唯一的访问地址。
     /// 注意:本类并没有直接设计为单例模式!
     /// </summary>
     public  static  AppDefinition Instance
     {
         get
         {
             if  (_instance == null ) throw  new  InvalidOperationException( "请先设置该属性。" );
             return  _instance;
         }
         set
         {
             if  (value == null ) throw  new  ArgumentNullException( "value" );
             if  (_instance != null ) throw  new  InvalidOperationException( "该属性只能被设置一次。" );
             _instance = value;
         }
     }
 
     #endregion
 
     /// <summary>
     /// 查找文件路径的查找算法提供器。
     /// </summary>
     private  PathProvider _pathProvider;
 
     /// <summary>
     /// 在使用所有属性前,需要主动调用此方法来进行初始化。
     /// </summary>
     protected  override  void  InitCore()
     {
         base .InitCore();
 
         this .CheckUnFrozen();
 
         this ._pathProvider = new  PathProvider();
         if  (! string .IsNullOrWhiteSpace( this .BranchAppName))
         {
             this ._pathProvider.AddBranch( this .BranchAppName);
         }
 
         //初始化所有文件路径
         this .Pathes = this .CreatePathes();
         this .Pathes.SetPathProvider( this ._pathProvider);
         this .Pathes.Initialize();
 
         //创建元数据库
         this .UIInfo = this .DefineUI();
         this .UIInfo.Initialize();
     }
 
     protected  override  void  OnFrozen()
     {
         base .OnFrozen();
 
         FreezeChildren( this .UIInfo, this .Pathes);
     }
 
     /// <summary>
     /// 分支版本名。
     /// 同时,这个也是客户化文件夹的名字。
     /// 分支版本定义,需要重写这个属性。
     /// </summary>
     protected  virtual  string  BranchAppName
     {
         get
         {
             return  null ;
         }
     }
 
     #region 文件
 
     /// <summary>
     /// 创建所有路径的定义。
     /// 子类重写此方法,用于添加更多的路径信息定义。
     /// </summary>
     /// <returns></returns>
     protected  virtual  PathDefinition CreatePathes()
     {
         return  new  PathDefinition();
     }
 
     /// <summary>
     /// 应用程序中所有使用到的需要客户化的路径集。
     /// </summary>
     public  PathDefinition Pathes { get ; private  set ; }
 
     #endregion
 
     #region DLL
 
     /// <summary>
     /// 获取所有此版本中需要加载的实体类Dll集合。
     /// </summary>
     /// <returns></returns>
     public  string [] GetEntityDlls()
     {
         return  this ._pathProvider.MapAllPathes( "Library" , true );
     }
 
     /// <summary>
     /// 获取所有此版本中需要加载的模块Dll集合。
     /// </summary>
     /// <returns></returns>
     public  string [] GetModuleDlls()
     {
         return  this ._pathProvider.MapAllPathes( "Module" , false );
     }
 
     #endregion
 
     #region UIInfo
 
     /// <summary>
     /// 应用程序中所有可用的视图信息。
     /// </summary>
     public  UIInfo UIInfo { get ; private  set ; }
 
     /// <summary>
     /// 子类重写此方法,用于初始化产品视图定义。
     /// 重点实现!
     /// </summary>
     /// <returns></returns>
     protected  virtual  UIInfo DefineUI()
     {
         return  new  UIInfo();
     }
 
     #endregion
}

 

    子版本的定义需要重写父类的DefineUI方法进行自己的版本信息定义,如,通用版本的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
namespace  Common.Definition
{
     /// <summary>
     /// 通用版本的产品定义
     /// </summary>
     public  class  AppDefinition : OpenExpressApp.MetaModel.Customizing.AppDefinition
     {
         /// <summary>
         /// 子类重写此属性以指定是否包含合同。
         /// </summary>
         protected  virtual  bool  IncludeContract
         {
             get
             {
                 return  true ;
             }
         }
 
         /// <summary>
         /// 这里加入东方版本需要的特定的视图信息
         /// </summary>
         /// <returns></returns>
         protected  override  UIInfo DefineUI()
         {
             var  ui = base .DefineUI();
 
             ui.Entity<CBFGQBQItemTitle>()
                 .EntityProperty(t => t.Code).ShowInLookup().ShowInList().Set_ListMinWidth(200);
 
             ui.Entity<CBSummary>()
                 .EntityProperty(t => t.PBSId).Show().Set_ListMinWidth(500);
 
             ui.Entity<CBNormSummary>()
                 .EntityProperty(t => t.PBSId).Show().Set_ListMinWidth(100);
 
             //在基类上定义视图信息,这个类的所有子类如果没有显式设置其它的值,则会使用基类的属性。
             ui.Entity( typeof (BQNormItemTitleBase))
                 .EntityProperty( "AllCode" ).Show().Set_ListMinWidth(200);
 
             this .DefineContractDll(ui);
 
             return  ui;
         }
 
         /// <summary>
         /// 定义合同模块的显示。
         /// </summary>
         /// <param name="ui"></param>
         private  void  DefineContractDll(UIInfo ui)
         {
             if  ( this .IncludeContract)
             {
                 ui.Entity<ContractBudget>().UnVisible();
                 ui.Entity<RealContractBudget>().Visible();
                 ui.UnVisible(
                     CCN.ShowPCIBQitemCommand, CCN.EntityShowBQCommand,
                     CCN.MeasureShowBQCommand, CCN.ResourceShowBQCommand,
                     CCN.ProjectIndicatorsCalculationCommand
                     );
             }
             else
             {
                 ui.UnVisible(
                     typeof (RealContractBudget), typeof (ContractSubjectType),
                     typeof (ProjectContractSubject), typeof (ContractIndicatorQueryObject)
                     );
                 ui.UnVisible(
                     CCCN.ShowPCIBQitemCommand, CCCN.EntityShowBQCommand,
                     CCCN.MeasureShowBQCommand, CCCN.ResourceShowBQCommand
                     );
             }
         }
     }
}

 

程序在启动时,从配置中注入AppDefinition,然后调用其初始化操作和冻结方法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public  partial  class  App : Application
{
     public  App()
     {
         this .InitAppDefinition();
     }
 
     /// <summary>
     /// 定义软件运行时版本
     /// </summary>
     private  void  InitAppDefinition()
     {
         var  appDefType = Type.GetType(AppConfig.Instance.AppDefinitionClass, true , true );
 
         AppDefinition.Instance = Activator.CreateInstance(appDefType) as  AppDefinition;
         AppDefinition.Instance.Initialize();
         AppDefinition.Instance.Freeze();
     }

配置文件:

1
< add  key="AppDefinitionClass" value="Common.Definition.AppDefinition, Common.Definition"/>

 

    限于篇幅,今天就先总结到此。下一篇主要是把客户化框架的设计讲完,然后再下一篇可能是GIX4项目中分离原有DLL的应用。


本文转自BloodyAngel博客园博客,原文链接:http://www.cnblogs.com/zgynhqf/archive/2010/09/20/1832019.html,如需转载请自行联系原作者

相关文章
|
1月前
|
新零售 人工智能 物联网
圣原心享购商城软件系统开发|模式设计
新零售以互联网技术为基础,通过利用数字化信息处理、人工智能等先进技术
|
15天前
|
人工智能 前端开发 JavaScript
【前端设计】HTML+CSS+JavaScript基本特性
【前端设计】HTML+CSS+JavaScript基本特性
|
1月前
|
关系型数据库 Java 数据库连接
WEBGIS系统数据库设计
WEBGIS系统数据库设计
34 2
|
10月前
|
设计模式 IDE Java
【Java设计模式 规范与重构】 四 小型重构的手段:规范的十五条军规
【Java设计模式 规范与重构】 四 小型重构的手段:规范的十五条军规
97 0
|
自然语言处理 Ubuntu 数据管理
LinuxGUI自动化测试框架搭建-总体需求&详细设计&框架设计
LinuxGUI自动化测试框架搭建-总体需求&详细设计&框架设计
103 0
|
移动开发 运维 前端开发
2013 开发与设计交汇之年
下图展示了2013年GitHub每个月新建的CSS仓库的数目。
116 0
2013 开发与设计交汇之年
|
敏捷开发 数据可视化 uml
UML建模工具及开发流程
UML建模工具及开发流程
189 0