新的需求
在《SmartPublisher设计之旅 - 让理想在仇恨中茁壮成长》一文中提到:SmartPublisher作为一种网络营销工具,对软件所有者来说,希望能给公司带来新的利润,要求软件需要增加以下功能:
1)对企业会员发布信息进行收费,并不同会员类型有不同的按照收费标准。
普通型会员:企业注册审核通过后即为普通会员,普通型会员发布1条信息按照1分钱/有效站点标准收取。
商务型会员:普通型会员消费满10000元即成为商务型会员,商务型会员发布1条信息按照0.5分钱/有效站点标准收取。
豪华型会员:商务型会员消费满100000元即成为豪华型会员。商务型会员发布1条信息按照0.1分钱/有效站点标准收取。
2)企业会员缴费有三种方式:手机缴费,购买冲值卡,银行转帐。
SmartPublisher并没有设计完成,需要增加以上功能并非难事情。然而,真正困难的是软件设计已经完成甚至整个软件已经进入维护阶段,需要在不改变原来设计的前提下增加功能就必须三思而后行。由于前期设计中多处采用接口,所以很明显不能修改接口,因为这样一来会直接导致继承该接口的所有类都必须更改。接下来,看看在不改变原来设计的前提下能否顺利增加软件功能。似乎有点故弄玄虚的感觉,哈哈。
数据库结构的变化
根据以上需求,需要增加两个数据表:企业会员充值记录表和消费记录表,当然还增加了会员类型字段(MemberType)。根据《SmartPublisher设计之旅 - 让梦想成为理想》文中的数据表设计规律,充值表和消费表与企业会员之间关系均为一对多关系,当然还需要在企业会员表中增加会员型号字段。修改后的数据表关系图如下:
数据访问层设计的变化
由于增加增加了两个数据对象,所有在数据访问层中需要增加以下类。由于在前面设计时,已经提供了一个数据表操作的统一接口,可以很方便的设计两个对象的实体类和访问类。这样并没有对原有的代码进行修改。
EnterpriseFee实体类
using System;
namespace PublisherLib.DataAccessLayer
{
public class EnterpriseFee
{
private string _ID;
private string _EnterpriseID;
private double _FeeAccount;
private DateTime _FeeTime;
public string ID
{
get { return this._ID; }
set { this._ID = value; }
}
public string EnterpriseID
{
get { return this._EnterpriseID; }
set { this._EnterpriseID = value; }
}
public double FeeAccount
{
get { return this._FeeAccount; }
set { this._FeeAccount = value; }
}
public DateTime FeeTime
{
get { return this._FeeTime; }
set { this._FeeTime = value; }
}
}
}
EnterpriseFeeDAL
using System;
using System.Data;
using System.Text;
using PublisherLib.DbAccess;
namespace PublisherLib.DataAccessLayer
{
public class EnterpriseFeeDAL:ITableOperator<EnterpriseFee>
{
ITableOperator 成员#region ITableOperator<EnterpriseFee> 成员
public bool Add(EnterpriseFee model)
{
throw new Exception("The method or operation is not implemented.");
}
public bool Update(EnterpriseFee model)
{
throw new Exception("The method or operation is not implemented.");
}
public bool Delete(string ID)
{
throw new Exception("The method or operation is not implemented.");
}
public int GetCount(string strWhere)
{
throw new Exception("The method or operation is not implemented.");
}
public EnterpriseFee GetModel(string ID)
{
throw new Exception("The method or operation is not implemented.");
}
public DataSet GetList(string strWhere)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
EnterpriseCharge实体类
using System;
namespace PublisherLib.DataAccessLayer
{
public class EnterpriseCharge
{
private string _ID;
private string _EnterpriseID;
private double _ChargeAccount;
private DateTime _ChargeTime;
public string ID
{
get { return this._ID; }
set { this._ID = value; }
}
public string EnterpriseID
{
get { return this._EnterpriseID; }
set { this._EnterpriseID = value; }
}
public double ChargeAccount
{
get { return this._ChargeAccount; }
set { this._ChargeAccount = value; }
}
public DateTime ChargeTime
{
get { return this._ChargeTime; }
set { this._ChargeTime = value; }
}
}
}
EnterpriseChargeDAL
using System;
using System.Data;
using System.Text;
using PublisherLib.DbAccess;
namespace PublisherLib.DataAccessLayer
{
public class EnterpriseChargeDAL:ITableOperator<EnterpriseCharge>
{
ITableOperator 成员#region ITableOperator<EnterpriseCharge> 成员
public bool Add(EnterpriseCharge model)
{
throw new Exception("The method or operation is not implemented.");
}
public bool Update(EnterpriseCharge model)
{
throw new Exception("The method or operation is not implemented.");
}
public bool Delete(string ID)
{
throw new Exception("The method or operation is not implemented.");
}
public int GetCount(string strWhere)
{
throw new Exception("The method or operation is not implemented.");
}
public EnterpriseCharge GetModel(string ID)
{
throw new Exception("The method or operation is not implemented.");
}
public DataSet GetList(string strWhere)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
业务逻辑层设计改进
经过对新需求的分析,我们对业务逻辑层进行改进。对企业会员管理来说,再提供以下功能:
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具体类
EnterpriseMember
using System;
namespace PublisherEngine.Engine
{
public class EnterpriseMember:IMember
{
IMember 成员IMember 成员#region IMember 成员
public int GetCurrentSucceedNumber(string dataID, string dataType)
{
throw new Exception("The method or operation is not implemented.");
}
public string GetMemberType(string enterpriseID)
{
throw new Exception("The method or operation is not implemented.");
}
public double GetChargeSum(string enterpriseID)
{
throw new Exception("The method or operation is not implemented.");
}
public void UpdateMemberType(string enterpriseID, string memberType)
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
} 不同会员类型本质上区别在于不同的收费算法,很明显我们可以采用
策略模式(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普通型会员
using System;
namespace PublisherEngine.Engine
{
public class CommonCharge : ICharge
{
ICharge 成员ICharge 成员#region ICharge 成员
public double _Rate
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public int ScceedNumber
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public void Charge()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
BusinessCharge商务型会员
using System;
namespace PublisherEngine.Engine
{
public class BusinessCharge:ICharge
{
ICharge 成员ICharge 成员#region ICharge 成员
public double _Rate
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public int ScceedNumber
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public void Charge()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
LuxuryCharge豪华会员
using System;
namespace PublisherEngine.Engine
{
public class LuxuryCharge:ICharge
{
ICharge 成员#region ICharge 成员
public double _Rate
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public int ScceedNumber
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public void Charge()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
} 我们约定,普通型会员标识为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
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Messaging;
using System.Text;
using System.Xml;
using PublisherEngine.Engine;
namespace PublisherEngine
{
public partial class PublisherService : ServiceBase
{
private IRouterIterator iterator = new RouterList();
MessageQueue messageQueue = new System.Messaging.MessageQueue();
public PublisherService()
{
InitializeComponent();
messageQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(messageQueue_ReceiveCompleted);
}
protected override void OnStart(string[] args)
{
try
{
//可调用webservice获取站点信息,创建RouterList路由集合类
string queuePath = "";//从配置文件中获取
messageQueue.Path = queuePath;
messageQueue.BeginReceive();
}
catch (System.Messaging.MessageQueueException ex)
{
Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
protected override void OnStop()
{
}
private void messageQueue_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
{
try
{
Message message = messageQueue.EndReceive(e.AsyncResult);
message.Formatter = new XmlMessageFormatter(new String[] { "System.String,mscorlib" });
DoMessage(message);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
messageQueue.BeginReceive();
}
}
private void DoMessage(Message message)
{
XmlDocument docMessage = new XmlDocument();
docMessage.LoadXml(message.Body.ToString());
XmlNode nodeMessage = docMessage.DocumentElement;
//解析获取信息nodeMessage,创建data
Data data = new Data();
iterator.First();
while (iterator.MoveNext())
{
IRouter router = iterator.Current;
if (data.WebSiteList.Contains(router.ID))
router.PublishTo(data);
}
//##########增加收费功能之后增加的代码2008年1月23日##########
Cash cash = new Cash(data.EnterpriseID,data.DataID,data.DataType);
cash.Charge();
//####################
}
}
} SmartPublisher业务逻辑层和发布引擎设计讨论到此为止,下篇将讨论UI表示层设计。