Martin04年写的书,15年后的我看了之后,感觉之前看的书都白看了,哈哈!有点夸张,废话不多说,开始!
1、案例一 团体
假设有个需求,让你设计两个类,一个是用户类,一个是公司类你会怎么设计,大多数人会这么设计,代码如下:
代码语言:javascript复制 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引入了"团体"一词,实际上就是对两个类型进行了抽象,将重复的概念抽象到一个类中,代码如下:
代码语言:javascript复制 /// <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、案例二 组织层次
假设需要设计一个权限系统,该系统包含用户、角色、权限、部门,大多数人听到这个需求会这么编写代码,如下:
代码语言:javascript复制 /// <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来说他们都是员工,那么这个时候我们需要修改模型,修改模型往往是不好的,那么怎么规避这种操作呢?代码如下:
代码语言:javascript复制 /// <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)不要了,那么我们不必修改模型,直接将关联的约束修改掉,如下:
代码语言:javascript复制 /// <summary>
/// 部门
/// </summary>
public class Department: IDepartment
{
public string DepartmentName { get; set; }
public ICollection<IUser> Roles { get; set; }
}
这个时候部门类就不再需要Role类了,但是它还是保存下来了,通过接口约束的转变,部门类这个时候只需要用户类就好了,通常情况,修改约束比修改模型结构要容易.
或者你可以像下面这样:
代码语言:javascript复制 /// <summary>
/// 用户
/// </summary>
public class User: IUser, IRole
{
public string UserName { get; set; }
public ICollection<IAction> Actions { get; set; }
}
只需改变对应的实现,拿掉Role类,也能完成需求.总之修改约束跟灵活,虽然上面的例子可能不那么适合.
虽然通过接口约束,能解决上面的问题,但是上面的设计还是有问题,结构层次单一。
还是上面的例子,假设boss说为了满足用户的需求,我们需要给部门类增加单独的权限,同时用户类又持有对应的权限,那么这个时候权限就同时为部门类和用户类负责,这个时候当一个用户登陆进来.我们去数据库查找到他对应的部门,然后拿到这个部门的所有权限,然后查到当前用户对应的角色,将该角色下的所有权限拿出来和部门权限做一个并集,去重,得出最终的权限.那么随着需求的增多,Action需要服务的层次增多,那么到最后代码将变得难以理解,所以我们必须重构代码.
重构版本,换一个例子,假设有一个子公司、区域子公司、部门、销售办事处、新增的零时服务小组,服务小组必须同时服务于部门、销售办事处,模型图如下:
代码如下:
代码语言:javascript复制 /// <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去查找
//拿到对应的服务对象后,调用该对象的通知方法,通知他们对应的信息
}
}
上面的代码,将组织抽象成一种类型,通过数据库持久化,这样的形式去连接目标对象和服务对象.这样就能将这种复杂的关联关系从原先的强类型耦合种解放出来.这样的设计更加的柔化,和不具有侵入性.所影响的范围也更小,模型的改动也最小,基本是通过扩展的方式,而不是像上面的代码那样去修改模型种的代码.