• 模式应用:自定义匹配


        本篇博客记录了我在工作过程中的一个设计单元。


    需求

        GIX4项目中需要为非国标清单进行匹配,用户自定义匹配规则。规则可以被存储到数据库中,下次重复使用。界面原型如下:

    image

    图1 界面原型

        用户可以指定对对象的某属性进行某个比较操作。


    设计-总体结构

    image

    图2 总体结构

        看上去会有点晕?懒了一点,就全画一起了。 :o)

        中间的接口就是整个结构的核心所在,下面会详细解释:


    第一组接口:设计匹配概念

        首先,明确匹配的概念,这个概念是与GIX4应用无关的。 一个是可以被匹配的对象,另一个则是主动匹配者。如下:

    /// <summary>
    /// 被匹配的对象
    /// </summary>
    public interface IMatchTarget { }
    
    /// <summary>
    /// 可以实施匹配操作的类。
    /// </summary>
    public interface IMatchable
    {
        /// <summary>
        /// 是否已经匹配过了。
        /// </summary>
        bool HasMatched { get; }
    
        /// <summary>
        /// 开始匹配
        /// </summary>
        /// <param name="target"></param>
        void Match(IMatchTarget target);
    }


    第二组接口:设计过滤规则

        动态规则是需要存储到数据库中的,但是由于它的形式十分灵活,所以这里选用XML这种半结构化的数据格式来存储规则内容,最后再序列化存储到数据库中。这种解决方法适用于一些小型的、结构变化性大的对象,如下:

    /// <summary>
    /// 可以被序列化为XML内容的对象
    /// </summary>
    public interface IXmlSerializable
    {
        /// <summary>
        /// 序列化为XML值。
        /// </summary>
        /// <returns></returns>
        XElement Serialize();
    }
    /// <summary>
    /// 过滤规则
    /// </summary>
    public interface IFilterRule : IXmlSerializable
    {
        /// <summary>
        /// 判断某个可匹配对象是否符合规则。
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        bool IsMatch(IMatchable source);
    }
    /// <summary>
    /// 对IFilterRule序列化的结果进行反序列化。
    /// </summary>
    public interface IFilterRuleDeserializer
    {
        /// <summary>
        /// 反序列化
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        IFilterRule Deserialize(XElement element);
    }

        IFilterRule是这三个中最重要的接口。表示一个动态的过滤规则。在GIX4中,它可以是一个简单的规则:

    “对象的Name属性应该包含***’”

        也可以是由各种简单规则复合而成,如:

    “对象的Name属性应该包含***’”       AND     “对象的Name属性应该满足正则表达式***’”    AND   “对象的Amount属性应该大于0’”

        这里IFilterRule接口及其子类的设计方法,类型“表达式树”。(朋友说其实是解释器模式,不过我自己也没记住解释器模式是什么结构,所以不知道这里到底是不是。) :O)


    第三组接口:元数据-比较操作

        过滤的规则是动态的,但是对于某种数据类型(string、int)进行的比较操作,却是固定的。过滤规则则是由这些固定的操作组合而成。如:我可以对User对象的Name属性(string)进行是否以某字符串开头的判断,可以定义如下:Name BeginWith “王”,这里的BeginWith就是一个比较操作,它针对类型string。操作定义如下:

    /// <summary>
    /// 比较操作
    /// </summary>
    public interface ICompareOperation
    {
        /// <summary>
        /// 同类型下一的名字
        /// </summary>
        string Name { get; }
    
        /// <summary>
        /// 判断两个值是否符合这个配对策略
        /// </summary>
        /// <param name="sourceValue"></param>
        /// <param name="targetValue"></param>
        /// <returns></returns>
        bool IsMatch(object sourceValue, object targetValue);
    }


    第三组接口:元数据-属性规则

        由于项目中最常使用的就是根据属性的值来进行简单的过滤,所以定义了一个“可匹配属性”接口。通过它,可以获得能够对这个属性进行的所有操作。可以获取到指定的可匹配对象IMatchable的该属性值。实现时可以不使用反射而进行快速获取值,加快匹配速度。

    /// <summary>
    /// 用于匹配操作的属性。
    /// </summary>
    public interface IMatchableProperty
    {
        /// <summary>
        /// 属性名
        /// </summary>
        string Name { get; }
        /// <summary>
        /// 用于显示的名字
        /// </summary>
        string Label { get; }
    
        /// <summary>
        /// 属性所在的IMatchable类名
        /// </summary>
        Type MatchableType { get; }
        /// <summary>
        /// 属性类型
        /// </summary>
        Type PropertyType { get; }
        /// <summary>
        /// 允许的操作
        /// </summary>
        IList<ICompareOperation> Operations { get; }
    
        /// <summary>
        /// 获取指定对象的值。
        /// </summary>
        /// <param name="machable"></param>
        /// <returns></returns>
        object GetValue(IMatchable machable);
    }
    
    /// <summary>
    /// 匹配属性元数据工厂
    /// </summary>
    public interface IMatchablePropertyFactory : IStringConverter
    {
        /// <summary>
        /// 根据匹配对象的类名,查询这个类所对应的用于匹配操作的所有属性。
        /// </summary>
        /// <param name="matchableTypeName"></param>
        /// <returns></returns>
        IList<IMatchableProperty> GetProperties(string matchableTypeName);
        //IList<IMatchableProperty> Get(Type matchableType);
    }

        到现在,最基本的接口就已经设计完成。


    集成到GIX4

    1.外观

        模块使用外观模式构建Facade类来降低外部使用的复杂度。

    2.组装

        系统主要是匹配PBS到FGQBQItem。本着“新增优于修改”的原则,不想在原有的类上修改或者、添加新的代码,所以这里为这两个类分别扩充新类FGQBQItemMatch和PBSMatchTarget,并实现IMatchable和IMatchTarget,然后再由实现IMatchable的类FGQBQItemMatch指定哪此属性(FGQBQItemMatchProperty类)可以用于匹配就行了,如下图:

    image

    图3 集成过程


    其它-界面

        有意思的是,由于这次的界面是动态的,实现过程中使用了装饰模式以重用属性规则编辑器。图就不画了,贴下代码图:

    image

    图4 界面代码

        最后的界面如下:

    image

    图5 最终界面

    其它-所有代码图

        除了界面以外,整个方案的代码图如下:

    image

    图6 所有代码


    后话

        做完了,感觉解决得还行。原来发的文章都没什么人搭理,这次真心希望多来几个拍砖的,没人说,就没进步啊。嘿嘿。

        另外,cnblog没有多大的空间传文件,所以就不传代码了。如有谁需要代码,可以留言找我。

  • 相关阅读:
    写给自己的2020年总结
    docker镜像与docker容器
    docker安装&docker简介
    windows 安装linux子系统
    typora设置图床
    让Mysql插入中文
    pip 换源
    Unity中如何将一个场景(Scene)的Light Settings复制给另一个场景
    Windows API开发
    【C#】判断字符串中是否包含指定字符串,contains与indexOf方法效率问题
  • 原文地址:https://www.cnblogs.com/zgynhqf/p/1664630.html
Copyright © 2020-2023  润新知