分析模式一

2022-03-24 08:34:20 浏览数 (2)

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去查找
            //拿到对应的服务对象后,调用该对象的通知方法,通知他们对应的信息

        }
    }

上面的代码,将组织抽象成一种类型,通过数据库持久化,这样的形式去连接目标对象和服务对象.这样就能将这种复杂的关联关系从原先的强类型耦合种解放出来.这样的设计更加的柔化,和不具有侵入性.所影响的范围也更小,模型的改动也最小,基本是通过扩展的方式,而不是像上面的代码那样去修改模型种的代码.

0 人点赞