新的需求
在《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表示层设计。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述