SmartPublisher设计之旅 — 软件设计无需后悔莫及

新的需求

在《SmartPublisher设计之旅 - 让理想在仇恨中茁壮成长》一文中提到:SmartPublisher作为一种网络营销工具,对软件所有者来说,希望能给公司带来新的利润,要求软件需要增加以下功能:


1)对企业会员发布信息进行收费,并不同会员类型有不同的按照收费标准。
普通型会员:企业注册审核通过后即为普通会员,普通型会员发布1条信息按照1分钱/有效站点标准收取。
商务型会员:普通型会员消费满10000元即成为商务型会员,商务型会员发布1条信息按照0.5分钱/有效站点标准收取。
豪华型会员:商务型会员消费满100000元即成为豪华型会员。商务型会员发布1条信息按照0.1分钱/有效站点标准收取。


2)企业会员缴费有三种方式:手机缴费,购买冲值卡,银行转帐。

SmartPublisher并没有设计完成,需要增加以上功能并非难事情。然而,真正困难的是软件设计已经完成甚至整个软件已经进入维护阶段,需要在不改变原来设计的前提下增加功能就必须三思而后行。由于前期设计中多处采用接口,所以很明显不能修改接口,因为这样一来会直接导致继承该接口的所有类都必须更改。接下来,看看在不改变原来设计的前提下能否顺利增加软件功能。似乎有点故弄玄虚的感觉,哈哈。

数据库结构的变化

根据以上需求,需要增加两个数据表:企业会员充值记录表和消费记录表,当然还增加了会员类型字段(MemberType)。根据《SmartPublisher设计之旅 - 让梦想成为理想》文中的数据表设计规律,充值表和消费表与企业会员之间关系均为一对多关系,当然还需要在企业会员表中增加会员型号字段。修改后的数据表关系图如下:

数据访问层设计的变化

由于增加增加了两个数据对象,所有在数据访问层中需要增加以下类。由于在前面设计时,已经提供了一个数据表操作的统一接口,可以很方便的设计两个对象的实体类和访问类。这样并没有对原有的代码进行修改。

EnterpriseFee实体类

EnterpriseFeeDAL

EnterpriseCharge实体类

EnterpriseChargeDAL

业务逻辑层设计改进

经过对新需求的分析,我们对业务逻辑层进行改进。对企业会员管理来说,再提供以下功能:
1)获取企业会员类型
2)修改会员类型

3)企业会员充值
4) 获取企业充值明细
5)获取企业充值总额

6)保存消费记录
7)获取企业消费明细
8)获取企业消费总额

起初我们已经定义了会员管理的接口IEnterpriseBLL,但是又不能在这个接口上增加上述方法。在23种设计模式中,我们想到适配器模式(Adapter)可以很方便的把IEnterpriseBLL接口适配成具有会员管理功能的新接口。我们只需要再定义一个会员管理的接口IEnterpriseMemberBLL,由新的业务层访问类EnterpriseMemberBLL,并且继承IEnterpriseMemberBLL和原来的业务层访问类EnterpriseBLL。这样一来,UI表示层只需要与EnterpriseMemberBLL发生关系就可以了。

using System;
using System.Text;
using System.Data;
using PublisherLib.DataAccessLayer;

namespace PublisherLib.BusinessLogicLayer
{
    
public interface IEnterpriseMemberBLL
    {
        
string GetMemberType(string enterpriseID);
        
bool UpdateMemberType(string enterpriseID, string memberType);

        
bool AddEnterpriseFee(string enterpriseID, double feeAmount);
        DataSet QueryEnterpriseFeeList(
string strCondition);
        
double GetEnterpriseFeeSum(string enterpriseID);

        
bool AddEnterpriseCharge(string enterpriseID, double chargeAmount);
        DataSet QueryEnterpriseChargeList(
string strCondition);
        
double GetEnterpriseChargeSum(string enterpriseID);
    }
}

using System;
using System.Text;
using System.Data;
using PublisherLib.DataAccessLayer;

namespace PublisherLib.BusinessLogicLayer
{
    
public class EnterpriseMemberBLL:EnterpriseBLL,IEnterpriseMemberBLL
    {
        
#region IEnterpriseMemberBLL 成员

        
public string GetMemberType(string enterpriseID)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public bool UpdateMemberType(string enterpriseID, string memberType)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public bool AddEnterpriseFee(string enterpriseID, double feeAmount)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public DataSet QueryEnterpriseFeeList(string strCondition)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public double GetEnterpriseFeeSum(string enterpriseID)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public bool AddEnterpriseCharge(string enterpriseID, double chargeAmount)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public DataSet QueryEnterpriseChargeList(string strCondition)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
public double GetEnterpriseChargeSum(string enterpriseID)
        {
            
throw new Exception("The method or operation is not implemented.");
        }

        
#endregion
    }
}

发布引擎设计改进

首先我们在前期设计中,发布引擎只是提供信息发布的功能,现在需要根据不同的会员类型进行收费。在收费用之前,我们定义对企业会员操作的接口,以提供引擎调用。
using System;

namespace PublisherEngine.Engine
{
    
public interface IMember
    {
        
int GetCurrentSucceedNumber(string dataID,string dataType);//获取指定信息的成功发布数量
        string GetMemberType(string enterpriseID);//获取企业会员类型
        double GetChargeSum(string enterpriseID);//获取企业收费总额
        void UpdateMemberType(string enterpriseID, string memberType);//修改企业会员类型
    }
}

EnterpriseMember具体类

不同会员类型本质上区别在于不同的收费算法,很明显我们可以采用策略模式(Strategy)来实现。首先我们定义一个收费接口。
using System;

namespace PublisherEngine.Engine
{
    
public interface ICharge
    {
        
double _Rate { get;set;}//价格,如0.01\0.005\0.001
        int ScceedNumber { get;set;}//成功发送的信息条数
        void Charge();
    }
}
然后定义三种会员类型具体实现类。
CommonCharge普通型会员

BusinessCharge商务型会员

LuxuryCharge豪华会员

我们约定,普通型会员标识为Common,商务型会员标识为Business,豪华型会员标识为Luxury。可以通过会员ID获取会员类型字符串,然后根据字符串的值来决定调用哪个收费具体类。我们可以定义配置文件(MemberType.xml)配置不同会员类型收费价格及实例化类名。
<?xml version="1.0" encoding="utf-8" ?>
<root>
    
<Common>
        
<Rate>0.01</Rate>
        
<ClassName>PublisherEngine.Engine.CommonMember</ClassName>
    
</Common>
    
<Business>
        
<Rate>0.005</Rate>
        
<ClassName>PublisherEngine.Engine.BusinessMember</ClassName>
    
</Business>
    
<Luxury>
        
<Rate>0.001</Rate>
        
<ClassName>PublisherEngine.Engine.LuxuryMember</ClassName>
    
</Luxury>
</root> 
然后在策略环境类(Cash)中获取企业类型,通过依赖注入(Dependency Injection)实例化具体收费类,并根据条件修改企业会员类型。
using System;
using System.Xml;
using System.Reflection;

namespace PublisherEngine.Engine
{
    
public class Cash
    {
        
private ICharge _charge;
        
private IMember _member;
        
private string _EnterpriseID;
        
private string _DataID;
        
private string _DataType;
        
public Cash(string enterpriseID,string dataID,string dataType)
        {
            _EnterpriseID 
= enterpriseID;
            _DataID 
= dataID;
            _DataType 
= dataType;
        }
        
public void Charge()
        {
            _member 
= new EnterpriseMember();
            
string membertype = _member.GetMemberType(_EnterpriseID);//获取会员类型

            XmlDocument doc 
= new XmlDocument();
            doc.Load(
"MemberType.xml");
            XmlNode root 
= doc.DocumentElement;

            _charge 
= (ICharge)Assembly.GetExecutingAssembly().CreateInstance(root.SelectSingleNode("/Common/ClassName").Value);
            _charge.Rate 
= Convert.ToDouble(root.SelectSingleNode("/Common/Rate").Value);
            _charge.ScceedNumber 
= _member.GetCurrentSucceedNumber(_DataID, _DataType);
            _charge.Charge();

            
//修改会员类型
            if (_member.GetChargeSum(_EnterpriseID) > 10000)
                _member.UpdateMemberType(_EnterpriseID, 
"Business");

            
if (_member.GetChargeSum(_EnterpriseID) > 100000)
                _member.UpdateMemberType(_EnterpriseID, 
"Luxury");
        }
    }
}

这时,增加收费功能后,Windows Service实现的PublisherEngine只增加两行代码
Cash cash = new Cash(data.EnterpriseID,data.DataID,data.DataType);
cash.Charge();


详细代码如下:
PublisherEngine

SmartPublisher业务逻辑层和发布引擎设计讨论到此为止,下篇将讨论UI表示层设计。
posted @ 2008-01-23 13:19  李华星  阅读(2105)  评论(5编辑  收藏  举报