Martin04年写的书,15年后的我看了之后,感觉之前看的书都白看了,哈哈!有点夸张,废话不多说,开始!
1、案例一 团体
假设有个需求,让你设计两个类,一个是用户类,一个是公司类你会怎么设计,大多数人会这么设计,代码如下:
public class User { public string UserName { get; set; } public string Adress { get; set; } public string Email { get; set; } } public class Company { public string CompanyName { get; set; } public string Adress { get; set; } public string Email { get; set; } }
ok,代码能很好的完成需求,但是不完美,里面的Adress和Email是重复的概念.so,Martin引入了"团体"一词,实际上就是对两个类型进行了抽象,将重复的概念抽象到一个类中,代码如下:
/// <summary> /// 通过团体来封装共有的属性 /// </summary> public class Group { public string Adress { get; set; } public string Email { get; set; } } public class User { public string UserName { get; set; } /// <summary> /// 这里通过值对象来实现共有的属性,更合适,避免继承的强依赖,而且在C#中只能单继承 /// 而且这样的代码更容易理解,表示User中存在一个团体类,里面封装了该团体的所有属性 /// </summary> public Group Group { get; set; } } public class Company { public string CompanyName { get; set; } public Group Group { get; set; } }
类图可以这样表示.
2、案例二 组织层次
假设需要设计一个权限系统,该系统包含用户、角色、权限、部门,大多数人听到这个需求会这么编写代码,如下:
/// <summary> /// 部门 /// </summary> public class Department { public string DepartmentName { get; set; } public ICollection<Role> Roles { get; set; } } /// <summary> /// 角色 /// </summary> public class Role { public string RoleName { get; set; } public ICollection<User> Users { get; set; } public ICollection<Action> Actions { get; set; } } /// <summary> /// 用户 /// </summary> public class User { public string UserName { get; set; } } /// <summary> /// 权限 /// </summary> public class Action { public string ActionName { get; set; } }
ok,代码能很好的完成需求,但是这个时候boss告诉你,我们不需要角色这个概念了,所有部门下的用户一律平等,对boss来说他们都是员工,那么这个时候我们需要修改模型,修改模型往往是不好的,那么怎么规避这种操作呢?代码如下:
/// <summary> /// 部门 /// </summary> public class Department: IDepartment { public string DepartmentName { get; set; } public ICollection<IRole> Roles { get; set; } } /// <summary> /// 角色 /// </summary> public class Role: IRole { public string RoleName { get; set; } public ICollection<IUser> Users { get; set; } } /// <summary> /// 用户 /// </summary> public class User: IUser { public string UserName { get; set; } public ICollection<IAction> Actions { get; set; } } /// <summary> /// 权限 /// </summary> public class Action: IAction { public string ActionName { get; set; } } public interface IDepartment { } public interface IRole { } public interface IUser { } public interface IAction { }
上面的代码通过接口来约束层级关系,这个时候如果boss提出角色类(Role)不要了,那么我们不必修改模型,直接将关联的约束修改掉,如下:
/// <summary> /// 部门 /// </summary> public class Department: IDepartment { public string DepartmentName { get; set; } public ICollection<IUser> Roles { get; set; } }
这个时候部门类就不再需要Role类了,但是它还是保存下来了,通过接口约束的转变,部门类这个时候只需要用户类就好了,通常情况,修改约束比修改模型结构要容易.
或者你可以像下面这样:
/// <summary> /// 用户 /// </summary> public class User: IUser, IRole { public string UserName { get; set; } public ICollection<IAction> Actions { get; set; } }
只需改变对应的实现,拿掉Role类,也能完成需求.总之修改约束跟灵活,虽然上面的例子可能不那么适合.
虽然通过接口约束,能解决上面的问题,但是上面的设计还是有问题,结构层次单一。
还是上面的例子,假设boss说为了满足用户的需求,我们需要给部门类增加单独的权限,同时用户类又持有对应的权限,那么这个时候权限就同时为部门类和用户类负责,这个时候当一个用户登陆进来.我们去数据库查找到他对应的部门,然后拿到这个部门的所有权限,然后查到当前用户对应的角色,将该角色下的所有权限拿出来和部门权限做一个并集,去重,得出最终的权限.那么随着需求的增多,Action需要服务的层次增多,那么到最后代码将变得难以理解,所以我们必须重构代码.
重构版本,换一个例子,假设有一个子公司、区域子公司、部门、销售办事处、新增的零时服务小组,服务小组必须同时服务于部门、销售办事处,模型图如下:
代码如下:
/// <summary> /// 子公司 /// </summary> public class ChildCompany:INode { public string ChildCompanyName { get; set; } } /// <summary> /// 区域子公司 /// </summary> public class AreaChildCompany : INode { public string ChildAreaChildCompany { get; set; } } /// <summary> /// 部门 /// </summary> public class Department : INode { public string DepartmentName { get; set; } } /// <summary> /// 销售办事处 /// </summary> public class SaleOffice: INode { public string SaleOfficeName { get; set; } } /// <summary> /// 新增的零时服务小组 /// </summary> public class ServiceGroup : INode { } /// <summary> /// 组织节点接口 /// </summary> public interface INode { } /// <summary> /// 组织 /// </summary> public class Organization { /// <summary> /// 父节点 /// </summary> public INode ParentNode; /// <summary> /// 子节点 /// </summary> public INode ChildNode; /// <summary> /// 组织的开始生效时间 /// </summary> public DateTime StartTime { get; set; } = DateTime.Now; /// <summary> /// 组织的生命结束期 /// </summary> public DateTime EndTime { get; set; } } public class Pragram { static void Main(string[] args) { var organizationOne = new Organization() { ParentNode = new Department(), ChildNode = new ServiceGroup(), EndTime=DateTime.Now.AddDays(1) }; var organizationTwo = new Organization() { ParentNode = new SaleOffice(), ChildNode = new ServiceGroup(), EndTime = DateTime.Now.AddDays(1) }; //将两个组织持久化到数据库,这样ServiceGroup就同时服务于Department部门和SaleOffice销售办事处 //接着当零食服务小组登录系统后,我们先去数据库查到对应的没有过期的组织对象,一般根据ServiceGroup的Guid去查找 //拿到对应的服务对象后,调用该对象的通知方法,通知他们对应的信息 } }
上面的代码,将组织抽象成一种类型,通过数据库持久化,这样的形式去连接目标对象和服务对象.这样就能将这种复杂的关联关系从原先的强类型耦合种解放出来.这样的设计更加的柔化,和不具有侵入性.所影响的范围也更小,模型的改动也最小,基本是通过扩展的方式,而不是像上面的代码那样去修改模型种的代码.