关于开闭原则的理解

开闭原则:设计一个模块的时候,应当使这个模块可以在不修改原有代码的前提下被扩展。

这个原则是保证系统具有扩展性的基本原则。我理解有几个要点:1、要能够复用;2、扩展时只增加新方法、新类;3、不得不修改代码时,修改的范围必须是局部的、隐藏的;

通常变更有三种方式,一种是横向变更,例如售票系统,原来只能售火车票,现在要可以售机票;第二种是纵向变更,例如在某个流程里插入新活动或跳过活动;第三种是局部修改,就是原有功能的业务规则发生了变化。对于前两种变更比较容易处理,只要在设计时注意抽象,通过接口、继承、override或event即可扩充。对于第三种变更,估计要修改代码了。虽然可以这样分类,但实际上以上三种变更通常是同时发生的、相互交织的。

以库存管理业务单据为例,有出库单、入库单、移库单等。通常单据结构都很相似,包含头表,行表,但个别字段有差异。新增一个单据,先在头表插入一条记录,然后在行表插入若干记录。更新单据时,先更新头表记录,然后清空词单据在行表里的原有记录,再插入新的行表记录,删除单据的过程也非常相似。此外,在单据增删改时要记录日志,在单据提交时,还要修改库存。所有这些操作十分相似,可以抽象出来。

现在要设计一个单据处理通用业务类,负责单据暂存、修改、提交、删除几个基本业务,以及相关的日志和库存操作。

1、分析
   单据操作: Save()/Submit()/Delete()/Log()/ChangeStock()
   相关数据: 头表实体、行表实体集;与日志有关的一些属性;与库存有关的一些属性;插入头表时,要有一个接口获取单据编号;因为插入行表时外键值需要用到头表记录的主键值,所以需要有个接口获取头表主键值,并有一个接口给行表实体的外键赋值;
   可能的变更: 1、将来可能增加审批功能,单据提交后审判通过才改变库存;2、增加新的单据类型;3、将来增加订单管理,那么库存操作除了出入库、还会增加在途转入库存、库存转出在途等功能。
  

2、设计
   方案1、抽象类+子类,只关注操作,不关注数据;
         单据处理类 BillProcess (抽象类):
         公有虚方法 Save()/Submit()/Delete()
         私有虚方法 Log()/ChangeStock()
   扩展:增加审批功能,只需在基类增加新的虚方法Audit,子类实现新的虚方法; 增加单据类型时,只需实现新的单据,增加在途功能,只需修改 ChangeStock;
   评价:此方案虽然很容易扩展,除了需要对ChangeStock作修改外,基本符合开闭原则;但过于抽象,子类需要实现全部操作,基类仅仅起到规范方法名称的作用;此方案的复用度太低;

   方案2、抽象类实现部分模板方法,所有方法都没有参数,抽象类没有任何字段,全部给子类实现;
         单据处理类 BillProcess (抽象类):
         公有虚方法 Save(): 调用IsNew()判断是新增还是更新,如果是新增,设置单据编号SetBillNumber,插入头表InsertHeader,设置行表外键SetBillLineHeaderID;若是更新,则更新头表UpdateHeader,删除行表DeleteLines(),设置行表外键SetBillLineHeaderID,插入行表InsertLines(); 调用 Log();
         公有虚方法 Submit(): 调用 Save(),调用 ChangeStock(); 调用 Log();
         公有虚方法 Delete(): 调用 DeleteHeader(),调用 DeleteLines(); 调用 Log();
        
         保护虚方法 IsNew,判断是新增单据还是更新单据;
         保护虚方法 SetBillNumber,设置头表单据编号;
         保护虚方法 SetBillLineHeaderID, 设置行表所属头表的外键值;
         保护虚方法 InsertHeader/UpdateHeader/DeleteHeader, InsertLines/DeleteLines
         保护虚方法 Log,记录操作员、操作时间、操作类型、单据编号;
         保护虚方法 ChangeStock,改变库存
   扩展:增加审批功能,需在基类增加新的虚方法Audit和NeedAudit虚属性,子类实现新的虚方法和虚属性; 增加单据类型时,只需实现新的单据,增加在途功能,需修改 ChangeStock;
   评价:此方案也很容易扩展,并且基类实现了部分操作,但子类要实现的方法过多,数据库访问方法其实只是表名有点差别,子类却要全部重写;另外子类 ChangeStock 仍要修改;此方案有一定的复用度,但也不高,而且每个子类的ChangeStock都要修改,不满足“开闭原则”的封闭性。

   方案3、一些通用的操作尽量放到 BillProcess,库存操作独立出来成为一个类;抽象出头表实体、行表实体接口;BillProcess可以操作相关数据接口。

         单据头实体接口 IBillHeader:
         BillID 获取或设置主键ID, 如果获取的ID为0,表示为新单据;这样就不需要IsNew()方法了
         BillType 获取单据类型
         BillNumber 获取或设置单据编号, 如果是新单据,BillProcess类可以向单据编号生成器BillNumberGenerator.GetBillNumber(BillType)传入BillType参数,获得单据编号,赋值给此属性;  
         DBField[] 获取实体的字段值数组,便于插入和更新到头表;

         单据行实体接口 IBillLine:
         BillID 设置所属单据头ID
         ProductID 商品ID
         SiteID 库位ID
         Count 商品数量
         DBField[] 获取实体的字段值数组,便于插入到头表;
        
        
         单据处理类 BillProcess (抽象类):
         保护虚属性: 头表名HeaderTableName、行表名LineTableName、头表主键名HeaderTablePKName、行表外键名LineTableFKName、单据类型BillType、头表实体接口IBillHeader、行表集合接口List<IBillLine> BillLines;单据编号 BillNumber;

         公有虚方法 Save(): 根据IBillHeader的BillID判断,如果是新增,调用BillNumberGenerator.GetBillNumber(BillType)获取新单据编号,并把单据号设置到头表实体,通过数据库会话类把头表实体DBField[]插入头表,设置行表外键BillID;若是更新,则数据库会话类用DBField[]更新头表,调用DeleteLines()删除行表记录,设置行表外键IBillLine的BillID,通过数据库会话类把行表实体DBField[]插入行表; 调用 Log();
         公有虚方法 Submit(): 调用 Save(),调用 ChangeStock(); 调用 Log();
         公有虚方法 Delete(): 调用数据库会话类删除头表记录,调用 DeleteLines(); 调用 Log();
         保护虚方法 DeleteLines(): 调用数据库会话类根据LineTableName、LineTableFKName删除行表记录
         保护虚方法 Log,记录操作员、操作时间、操作类型、单据编号;
         保护虚方法 ChangeStock,改变库存, 先调用GenerateStockChanges()把 List<IBillLine> 转为 List<StockChange>, 调用 StockProcess 实现库存操作,便于库存操作扩展
         保护虚方法 GenerateStockChanges(), 把 List<IBillLine> 转为 List<StockChange>;

         单据号生成器 BillNumberGenerator:
         生成单据号 GetBillNumber(BillType)

         库存处理类 StockProcess:
         属性 List<StockChange>, 库存操作数组

         库存改变类 StockChange: 商品ID 库位ID 库存量改变个数 (可扩充增加“在途量改变个数”)

    扩展:增加审批功能和新单据时,方法同方案1;增加在途量,需要扩展StockChange实体类和StockProcess业务类,BillProcess修改 GenerateStockChanges,子类不需要修改.
    评价:基类包含了较多实现,代码复用度高; 子类只需要实现 IBillHeader, IBillLine 和 一些属性即可(这些属性还可以配置到XML文件中,由BillProcess根据BillType读取XML配置信息,这样子类就不必关心这些细节,方便开发); 同时又保留了较强的扩展性,ChangeStock 方法被细化了,在扩充时,要修改的部分也只是底层的、局部的。不足之处是,基类过多地关注了细节,限制了扩展能力和变更的自由度;另外因为是要修改基类,影响的子类较多,需要投入较多的回归测试时间。此方案基本满足开闭原则。

      总体评价,方案3 提供了一个非常强大的业务基类BillProcess,但过于特殊化。目前的支持的流程是 暂存->提交->审批->改变库存,如果再增加一些中间环节,比如审批后增加发货环节,那么就又要修改BillProcess类了。还有,如果不同的单据有不同的流程,那么 BillProcess 就要增加大量属性信息来描述流程。有一种比较理想的设计思路是,把BillProcess变为“业务引擎”,把各种单据的流程配置到文件中,流程引擎读取这些配置信息执行对应的操作。这样增加一个单据或修改一个单据的流程,只需修改配置文件。实际上,这样有点像“依赖倒置”了,用框架来解决问题。


   

   

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值