一句台词引发的思考 updated 1.15 21:59
昨天晚上看了一部电视剧,其中一句台词说得不错:"其实很多的事情非常复杂,包括爱情,有时候甚至可以在仇恨的尸体上茁壮成长"。爱情是微妙的,它是所有感情家族里最年轻、最可爱、最单纯、最善良、最柔弱、最自私的一员,所以它害怕受到、也容易受到伤害。这正是我们常说的"爱之深,恨之切"。
有一个真实的故事。话说有一出身贫困地区的初中生(小A),他性格比较内向,成绩也很差。有一次,他跟一名同班同学(小B)发生口角。在吵架中,小B取笑小A,说:“象你这样的笨猪如果能考上大学,我认你做老子”。或许有些人听到这句不经意的话会一笑了之,然而对小A来说确实是一种侮辱,自尊心受到极大伤害。从那以后,小A开始痛恨小B,下定决心一定要考上大学,开始非常刻苦学习。多少年来,小B的“侮辱”成了小A不断进步的动力。工夫不负有心人,小A如愿以偿的考上了大学。参加工作以后,小A一次回家探亲,偶然遇见小B,可他当时心里已经不再是痛恨,取而代之是无限的感激。
在2007年的中国互联网界里,最引人瞩目的应该是阿里巴巴,而在阿里巴巴集团上下,最引人瞩目的应该是素有"网络狂人"之称的马云。从父亲拳脚下长大的差生到大学英语教师,从不懂电脑到全球著名的网络公司的掌门人,马云这样不断创造出奇迹。昔日的笑柄现已成了马云吹牛的资本。其实,从马云在媒体里表露出来的这种狂妄的语言中,我们不难发现,在马云身上有一种对那些看不起他、鄙视他的人的“仇恨”。
或许人就必须在不断遭受挫折、不断遭受打击的过程中慢慢的走向成熟。黑人领袖马丁·路德金说过;“这个世界上,没有人能够使你倒下,如果你自己的信念还站立的话。”
IT? 还是挨踢?
IT人作为计算机技术的开拓者、推动者、传播者,早已成为我们的代名词。然后,当我们在拥有知识的同时,却常常自嘲为"挨踢人"。挨风云变幻的技术之所“踢”,挨用户的不满之所“踢”,挨旁人的冷言冷语之所“踢”。可不管怎么"痛",我们还是能感受到浩瀚的知识带给我的快乐和满足。所以,我们仍然毫无怨言的、顽强的站立着,并且将永远的走下去。
在软件开发过程中,我们常常遇到这样的情况:开发工作已经进行到了后期,这时客户打来一个电话,推翻了之前与客户再三讨论而确定下来的需求,但又无法拒绝客户的要求,这时又得重新跟客户进行新一轮的需求讨论,甚至是无休止的讨论,最后导致必须重新设计现有的架构。我相信,每一个开发人员都饱受过需求变化带来的痛苦。《解析极限编程拥抱变化》写道:“任何情况下,变化是绝对的,不变是相对的,我们不要抱怨变化的发生,重要的是要有应付变化的能力”。
说了这么多费话,现在言归正传。在《SmartPublisher设计之旅 - 优秀的设计才是让软件永葆青春的秘诀》中,我们已经对SmartPublisher数据访问层进行了设计,下面我们来探讨的业务逻辑层的设计。
业务逻辑层(Business Logic Layer)设计
业务逻辑层:在三层架构中,业务逻辑层是所有业务逻辑的集中地,是整个系统的核心。它关注业务规则的制定,并为表示层提供服务。业务逻辑层主要负责数据的合法性检查,如企业注册时录入的帐号是否已被注册;还有业务操作组合处理,比如在某一个功能中需要对多个数据表进行分步操作(如订单保存Master和Detail),但是这些步骤是一个完整的业务逻辑。所以业务逻辑层设计应该按照功能需求进行设计,与业务紧密联系,所以在复杂的系统的中有时需要邀请业务专家参与设计。为了保证业务逻辑层的可扩展性,我将定义业务接口(IBLL)和具体业务类(BLL),并由具体业务类与数据访问层的接口集通讯。
从《SmartPublisher设计之旅 - 软件开发之前先做个梦》的需求分析中可以看出,SmartPublisher的功能主要包括两个层面:一是面向Web的应用;二是面向Windows Service引擎的应用。
首先,我们考虑Web应用的功能。
1、对企业会员需要实现的功能有:
·企业注册基本信息;
·企业会员登陆;
·企业修改基本信息;
·企业选择站点,发布基本信息;
·企业会员修改密码;
·管理员审核企业会员;
·管理员禁用/启用企业会员帐号;
·管理员删除企业会员基本信息;
·按照企业ID、企业会员帐号、企业会员名称、企业行业分类、是否会员、是否禁用等条件查询企业会员基本信息;
根据以上功能定义IEnterpriseBLL
using System;
using System.Text;
using System.Data;
using PublisherLib.DataAccessLayer;
namespace PublisherLib.BusinessLogicLayer
{
public interface IEnterpriseBLL
{
bool RegisterEnterprise(Enterprise model);
bool Login(string enterpriseCode, string enterprisePassword);
bool UpdateEnterprise(Enterprise model);
bool UpdatePassword(string enterpriseID, string newEnterprisePassword);
bool VerifyEnterprise(string enterpriseID, bool ismember);
bool LockEnterprise(string enterpriseID, bool islock);
bool DeleteEnterprise(string enterpriseID);
Enterprise GetEnterpriseByID(string enterpriseID);
DataSet QueryEnterpriseList(string strCondition);
}
}
2、对产品信息需要实现的功能有:
·企业会员录入产品信息;
·企业会员获取所有产品信息;
·企业会员修改产品信息;
·企业会员删除产品信息;
·企业会员选择站点,发布产品信息;
·按照条件查询产品信息;
根据以上功能定义IProductBLL
using System;
using System.Text;
using System.Data;
using PublisherLib.DataAccessLayer;
namespace PublisherLib.BusinessLogicLayer
{
public interface IProductBLL
{
bool AddProduct(Product model);
bool UpdateProduct(Product model);
bool DelelteProduct(string productID);
Product GetProduct(string productIDid);
DataSet GetProductsByEnterprise(string enterpriseID);
DataSet GetProductList(string strCondition);
}
}
大家仔细看IEnterpriseBLL和IProductBLL方法定义,我在这里并没有定义发布企业基本信息和发布产品信息两个方法。因为不关是企业基本信息,还是产品信息,其发布过程都是一样的,只是信息内容不一样,发布之后写入T_RouterData表(详见《SmartPublisher设计之旅 — 让梦想成为理想》)。所以我抽象出一个信息发布接口,当然还考虑其他原因,请看后叙。
using System;
namespace PublisherLib.BusinessLogicLayer
{
public interface IPublishData
{
void Publish(string dataID, string[] aryWebSite);
}
} 接下来,定义定义EnterpriseBLL,并继承IEnterprise和IPublishData
EnterpriseBLL
using System;
using System.Text;
using System.Data;
using PublisherLib.DataAccessLayer;
namespace PublisherLib.BusinessLogicLayer
{
public class EnterpriseBLL:IEnterpriseBLL,IPublishData
{
private static readonly ITableOperator<Enterprise> dal = DataFactory.CreateEnterprise();
IEnterpriseBLL 成员#region IEnterpriseBLL 成员
public bool RegisterEnterprise(Enterprise model)
{
return dal.Add(model);
}
public bool Login(string enterpriseCode, string enterprisePassword)
{
DataSet ds = dal.GetList("Code='" + enterpriseCode+"' AND password='"+enterprisePassword+"'");
if (ds == null || ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0) return false;
return true;
}
public bool UpdateEnterprise(Enterprise model)
{
return dal.Update(model);
}
public bool UpdatePassword(string enterpriseID, string newEnterprisePassword)
{
Enterprise model = dal.GetModel(enterpriseID);
model.Password = newEnterprisePassword;
return dal.Update(model);
}
public bool VerifyEnterprise(string enterpriseID, bool ismember)
{
Enterprise model = dal.GetModel(enterpriseID);
model.IsMember = Convert.ToByte(ismember);
return dal.Update(model);
}
public bool LockEnterprise(string enterpriseID, bool islock)
{
Enterprise model = dal.GetModel(enterpriseID);
model.IsLock = Convert.ToByte(islock);
return dal.Update(model);
}
public bool DeleteEnterprise(string enterpriseID)
{
return dal.Delete(enterpriseID);
}
public Enterprise GetEnterpriseByID(string enterpriseID)
{
return dal.GetModel(enterpriseID);
}
public DataSet QueryEnterpriseList(string strCondition)
{
return dal.GetList(strCondition);
}
#endregion
IPublishData 成员#region IPublishData 成员
public void Publish(string dataID, string[] aryWebSite)
{
ITableOperator<RouterData> dal = DataFactory.CreateRouterData();
foreach (string websiteid in aryWebSite)
{
RouterData model = new RouterData();
model.DataID = dataID;
model.DataType = "ENTERPRISE";
model.RouterID = websiteid;
model.RouterType = "WEBSITE";
dal.Add(model);
}
}
#endregion
}
} 同理,定义定义ProductBLL,并继承IProduct和IPublishData,代码略。
3、对商务站点需要实现的功能有:
·新增创建站点;
·修改站点信息;
·禁用站点;
·删除站点信息;
·获取站点信息;
·按照条件查询站点信息;
根据以上功能定义IWebSiteBLL和WebSiteBLL,代码略。
4、对发布日志需要实现的功能有:
·新增日志;
·按照条件查询日志;
·按照被发布信息(企业/产品)统计成功条数和成功率;
·按照企业会员统计发布信息成功条数和成功率;
根据以上功能定义IPublishLogBLL和PublishLogBLL,代码略。
接下来,我们考虑发布引擎的功能。在讨论之前,请先看看SmartPublisher的网络图。就目前需求来说,比较简单,主要做两件事情:发布信息;记录发布日志。引擎的性能直接决定了SmartPublisher软件的性能。所以在这里,我采用MSMQ,主要原因如下:
1) 会员在发布企业/产品信息的同时,把信息发送到MessgeQ中,这也是为什么需要定义IPublishData的原因。
2) 发布引擎从MessgeQ接受消息比从数据库查询效率更高,特别是数据库的数据量大的时候更明显。
3) 发布引擎采用MSMQ技术以后,减少了对BLL的依赖,即在运行时只需监控MessgeQ即可。
既然采用MSMQ技术,IPublishData分别在EnterpriseBLL和ProductBLL中所实现的Publish方法明显有问题。这时,我们最直接的解决办法是在EnterpriseBLL和ProductBLL的Publish方法在写入数据库的同时,把信息发送到MessgeQ。从合理设计的角度来说,这种方法非常不可取,因为这样一来我们就已经把消息发送的功能固化在EnterpriseBLL和ProductBLL的Publish方法中,无法撤消。所以在这里我们可以通过装饰模式(Decorator)为Publish方法附加发送信息的功能。消息发送接口如下:
using System;
namespace PublisherLib.BusinessLogicLayer
{
public interface ISendMessage
{
void Send(string dataID, string[] aryWebSiteID);
}
}
然后定义SendEnterpriseMessageBLL和SendProductMessagegBLL进行发送消息。
SendEnterpriseMessageBLL
using System;
using System.Collections;
using System.Messaging;
using PublisherLib.DataAccessLayer;
namespace PublisherLib.BusinessLogicLayer
{
public class SendEnterpriseMessageBLL:EnterpriseBLL,ISendMessage
{
private string _QueuePath;
public SendEnterpriseMessageBLL(string queuePath)
{
this._QueuePath = queuePath;
}
ISendMessage 成员#region ISendMessage 成员
public void Send(string dataID, string[] aryWebSiteID)
{
//创建XML消息,包含发布信息和发布站点
string dataXML = "";
MessageQueue messageQueue = new System.Messaging.MessageQueue(this._QueuePath);
messageQueue.Send(dataXML);
}
#endregion
}
}
SendProductMessageBLL
using System;
using System.Collections;
using System.Messaging;
using PublisherLib.DataAccessLayer;
namespace PublisherLib.BusinessLogicLayer
{
public class SendProductMessageBLL:ProductBLL,ISendMessage
{
private string _QueuePath;
public SendProductMessageBLL(string queuePath)
{
this._QueuePath = queuePath;
}
ISendMessage 成员#region ISendMessage 成员
public void Send(string dataID, string[] aryWebSiteID)
{
//创建XML消息,包含发布信息和发布站点
string dataXML = "";
MessageQueue messageQueue = new System.Messaging.MessageQueue(this._QueuePath);
messageQueue.Send(dataXML);
}
#endregion
}
}
然后定义装饰类(DataDecorator),并继承IPublishData接口
DataDecorator
using System;
namespace PublisherLib.BusinessLogicLayer
{
public class DataDecorator:IPublishData
{
private IPublishData _PublishData;
public IPublishData PublishData
{
get { return _PublishData; }
}
public DataDecorator(IPublishData publishdata)
{
this._PublishData = publishdata;
}
IPublishData 成员#region IPublishData 成员
public virtual void Publish(string dataID, string[] aryWebSite)
{
this._PublishData.Publish(dataID, aryWebSite);
}
#endregion
}
}
定义具体装饰类(DataMessageDecorator),继承(DataDecorator)
DataMessageDecorator
using System;
namespace PublisherLib.BusinessLogicLayer
{
public class DataMessageDecorator : DataDecorator
{
private ISendMessage _SendMessage;
public ISendMessage SendMessage
{
get { return _SendMessage; }
set { _SendMessage = value; }
}
public DataMessageDecorator(IPublishData publishdata)
: base(publishdata)
{
}
public override void Publish(string dataID, string[] aryWebSite)
{
_SendMessage.Send(dataID, aryWebSite);
base.Publish(dataID, aryWebSite);
return;
}
}
}
发布引擎设计
SmartPublisher发布引擎以控制台程序或Windows Service方式运行。目前,SmartPublisher是将信息发布到商务站点上,考虑到以后有其他发布途径,我们可以定义一个发布路由(IRouter)
using System;
namespace PublisherEngine.Engine
{
public interface IRouter
{
string ID { get;set;}
string Address { get;set;}
string Parameters { get;set;}
void PublishTo(Data data);
}
}
站点路由类(SiteRouter)继承IRouter,如果有其他如邮件群发则定义EmailRouter,也继承IRouter。
SiteRouter
SiteRouter
using System;
namespace PublisherEngine.Engine
{
public class SiteRouter:IRouter
{
IRouter 成员IRouter 成员#region IRouter 成员
public string ID
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public string Address
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public string Parameters
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public void Publish(Data data)
{
//发布信息,并且保存发布日志
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
由于企业会员发布信息时需要根据行业多个同行业的站点,所以我们定义一个路由集合类(RouterList)。当MessageQ有消息,则通过遍历路由集合类调用IRouter的Publish方法进行发布,这正是迭代器模式(ITerator)的运用场景。Publish方法的参数Data为待发布信息。迭代器接口定义如下:
using System;
namespace PublisherEngine.Engine
{
public interface IRouterIterator
{
int index { set;get;}
void Add(IRouter router);
void Remove(IRouter router);
void First();
void Last();
bool MovePrevous();
bool MoveNext();
IRouter Current { get;}
int Count { get;}
void Clear();
}
}
路由集合类(RouterList)继承IRouterITerator。
RouterList
using System;
namespace PublisherEngine.Engine
{
public class RouterList : IRouterIterator
{
IRouterIterator 成员#region IRouterIterator 成员
public int index
{
get
{
throw new Exception("The method or operation is not implemented.");
}
set
{
throw new Exception("The method or operation is not implemented.");
}
}
public void Add(IRouter router)
{
throw new Exception("The method or operation is not implemented.");
}
public void Remove(IRouter router)
{
throw new Exception("The method or operation is not implemented.");
}
public void First()
{
throw new Exception("The method or operation is not implemented.");
}
public void Last()
{
throw new Exception("The method or operation is not implemented.");
}
public bool MovePrevous()
{
throw new Exception("The method or operation is not implemented.");
}
public bool MoveNext()
{
throw new Exception("The method or operation is not implemented.");
}
public IRouter Current
{
get { throw new Exception("The method or operation is not implemented."); }
}
public int Count
{
get { throw new Exception("The method or operation is not implemented."); }
}
public void Clear()
{
throw new Exception("The method or operation is not implemented.");
}
#endregion
}
}
发布信息类Data定义如下:
Data
using System;
namespace PublisherEngine.Engine
{
public class Data
{
private string _DataID;
public string DataID
{
get { return this._DataID; }
set { this._DataID = value; }
}
private string _DataType;
public string DataType
{
get { return this._DataType; }
set { this._DataType = value; }
}
private string _Title;
public string Title
{
get{ return this._Title;}
set{ this._Title=value;}
}
private string _Content;
public string Content
{
get { return this._Content; }
set { this._Content = value; }
}
private string _WebSiteList;
public string WebSiteList
{
get { return this._WebSiteList; }
set { this._WebSiteList = value; }
}
}
}
发布信息在MessageQ中以XML格式存在,约定格式为:
<?xml version="1.0" encoding="utf-8" ?>
<Data>
<datatype></datatype>
<dataid></dataid>
<title></title>
<content></content>
<websitelist></websitelist>
</Data>
最后是通过Windows Service实现的PublisherEngine。具体代码如下:
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);
}
}
}
}
如何应对需求变化
引用李建忠老师教程上一句话:“设计模式描述了软件设计过程中某一类常见问题的一般性的解决方案”。的确,每一种设计模式都是为了解决某一特定问题的,需求的变化会导致解决问题的方法变化,这就要求我们要在设计过程中能尽量适应未来的变化,这也是面向对象程序设计中“封装变化点”的出发点。如果软件设计能一步到位,那么我们讨论设计模式就没有任何意义了。
其实到目前为止,SmartPublisher的设计还没有多少可借鉴之处。但是接下来,我想做一个假设,假设SmartPublisher需要添加以下功能会对以上设计有什么影响。SmartPublisher作为一种网络营销工具,对软件所有者来说,希望能给公司带来新的利润,要求软件需要增加以下功能:
1)对企业会员发布信息进行收费,并不同会员类型有不同的按照收费标准。
普通型会员:企业注册审核通过后即为普通会员,普通型会员发布1条信息按照1分钱/有效站点标准收取。
商务型会员:普通型会员消费满10000元即成为商务型会员,商务型会员发布1条信息按照0.5分钱/有效站点标准收取。
豪华型会员:商务型会员消费满100000元即成为豪华型会员。商务型会员发布1条信息按照0.1分钱/有效站点标准收取。
2)企业会员缴费有三种方式:手机缴费,购买冲值卡,银行转帐。
根据以上需求,我将在上述设计基础上进行,具体设计待续。。。也欢迎各位高手支招。