首先介绍一下基金申购场景的业务逻辑
我们做的是【基金模拟投资大赛】。
用户申购一只基金时,需要选择申购的基金(FundCode)、申购的金额(Amount)。
现在要对以上两个参数作校验,主要逻辑为:
1.验证基金是否存在
2.封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
3.验证基金是否处于封闭期或暂停申购
4.验证用户是否允许购买此基金
5.验证用户帐户余额是否不足
6.验证申购金额
7.验证用户的资产配置
解释一下各点含义:
1.用户申购基金时选择一支基金(UI的下拉列表中选择一项),即传入FundCode,逻辑代码需要验证传入的FundCode是否是非法的,这要在【基金档案】中查找是否有匹配项,如果没有,则返回【基金不存在】异常。
2.需要规定封闭式、创新型封闭式、理财产品、QDII类型的基金和首发基金是不允许购买的。
3.基金的状态是随时会变的,这取决于基金公司,今天我开放这支基金的申购,明天我关闭它的申购,赎回也一样。
4.根据需求,用户在加入大赛时会选择一个小组,管理员会事先配置每个小组允许购买的基金,如果小组中没有配置某支基金,例如【华夏成长(000001)】,就意味着属于这个小组的用户都是不允许购买【华夏成长】这支基金。
5.用户余额不足会返回【余额不足】的异常。
6.买基金的人都知道,有最低申购金额限制,不同基金最低申购金额是不一样的,一般股票型基金低的100,高的1000,大部分1000。货币基金从200到1000的都有,大部分是1000。系统也会事先设定好这项配置。
7.资产配置的意思是类似要求用户配置投资组合,比如购买的货币型基金不能超过多少金额,债券型的基金不能超过多少金额,仅此而已。
以上用例都是很简单的,所以你会看到有这样一个入口:
public virtual ActionResponse Subscribe( int userId , string fundCode , decimal amount , string remark ) { ActionResponse response = new ActionResponse(); IUserRepository userRepository = RepositoryFactory.CreateUserRepository(); User user = userRepository.GetUser(userId); Fund fund = FundFactory.BuildFund(fundCode); IContext context = new SubscribeContext( new SubscribeUser(user) , new SubscribeFund(fund) , amount ); response = context.Interact(); return response; }
|
哪个用户(UserId)用多少钱(Amount)购买哪支基金(FundCode),remark是备注、理财日志,可不用理会。
再解释一下以上这段代码的业务意义:
根据userId取出用户这个对象,让她扮演【申购用户(SubscribeUser)】这个角色;构造一支基金fund,让她扮演【申购的基金(SubscribeFund)】这个角色,然后构造场景,实现交互(Interact)。所以大家一定很想知道User的原型,然后我就贴上来:
/// <summary> /// 用户 /// </summary> public class User {
/// <summary> /// 用户编号 /// </summary> public int Id { get; set; }
/// <summary> /// 用户名称 /// </summary> public string Name { get; set; }
/// <summary> /// 用户帐户 /// </summary> public Account Account { get; set; }
/// <summary> /// 用户所属小组 /// </summary> public Team Team { get; set; }
/// <summary> /// 用户状态 /// </summary> public bool IsActive { get; set; }
/// <summary> /// 注册日期 /// </summary> public DateTime RegisterDate { get; set; } }
|
然后就是她在【申购场景(SubscribeContext)】中扮演的【申购用户(SubscribeUser)】角色的代码:
/// <summary> /// 申购用户 /// </summary> public class SubscribeUser : IRole {
private User _user;
public SubscribeUser(User user) { this._user = user; }
/// <summary> /// 选手不存在 /// </summary> public bool NotExists { get { return this._user == null; } }
/// <summary> /// 选手被禁赛 /// </summary> public bool Suspended { get { return this._user.IsActive == false; } }
/// <summary> /// 选手是否不允许购买基金 /// </summary> /// <param name="fund"></param> /// <returns></returns> public bool CannotBuyFund(Fund fund) { Team team = this._user.Team; int teamCode = team.Code; ITeamRepository teamRepository = RepositoryFactory.CreateTeamRepository(); IList<Fund> teamFunds = teamRepository.GetTeamFunds(teamCode); return !(teamFunds.Count == 0 || teamFunds.Contains(fund)); }
/// <summary> /// 选手账户余额不足 /// </summary> /// <param name="amount"></param> /// <returns></returns> public bool InsufficientBalance(decimal amount) { return this._user.Account.Balance < amount; } }
|
接着是Fund的原型:
/// <summary> /// 基金 /// </summary> public class Fund : IAggregateRoot {
/// <summary> /// 基金代码 /// </summary> /// <param name="fundCode"></param> public Fund(string fundCode) { this.Code = fundCode; }
/// <summary> /// 基金代码 /// </summary> public string Code { get; set; }
/// <summary> /// 基金名称 /// </summary> public string Name { get; set; }
/// <summary> /// 基金类型 /// </summary> public FundType Type { get; set; }
/// <summary> /// 基金种类(A类、B类、C类) /// </summary> public FundClass Class { get; set; }
/// <summary> /// 基金所属公司 /// </summary> public FundCompany Company { get; set; }
/// <summary> /// 基金成立日期 /// </summary> public DateTime SetupDate { get; set; }
region 重写
public override int GetHashCode() { return this.Code.GetHashCode(); }
public override bool Equals(object obj) { if (ReferenceEquals(obj, null)) { return false; } return this.Code.Equals(((Fund)obj).Code); }
public static bool operator ==(Fund destFund, Fund srcFund) { if (ReferenceEquals(destFund, null) || ReferenceEquals(srcFund, null)) { return false; } return destFund.Code.Equals(srcFund.Code); }
public static bool operator !=(Fund destFund, Fund srcFund) { return !(destFund == srcFund); }
endregion }
|
她在【申购场景(SubscribeContext)】中扮演的【申购基金(SubscribeFund)】角色的代码:
/// <summary> /// 申购基金 /// </summary> public class SubscribeFund : IRole {
private Fund _fund;
public SubscribeFund(Fund fund) { this._fund = fund; }
public Fund Value { get { return this._fund; } }
/// <summary> /// 基金不存在 /// </summary> public bool NotExists { get { return this._fund == null; } }
/// <summary> /// 封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买 /// </summary> public bool CannotSubscribe { get { bool isCannotBuyFundType = false; switch (this._fund.Type.Code) { case 6: case 7: case 9: case 10: isCannotBuyFundType = true; break; } bool isUnSetuppedFund = (this._fund.SetupDate == DateTime.MinValue); return isCannotBuyFundType || isUnSetuppedFund; } }
/// <summary> /// 基金处于封闭期或暂停申购 /// </summary> public bool SubscribeClosed { get { string fundCode = this._fund.Code; return FundService.IsSubscribeClosed(fundCode); } } }
|