模式应用:自定义匹配

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

需求

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

图1 界面原型

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

设计-总体结构

图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类)可以用于匹配就行了,如下图:

图3 集成过程

其它-界面

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

图4 界面代码

    最后的界面如下:

图5 最终界面

其它-所有代码图

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

图6 所有代码

后话

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏软件开发 -- 分享 互助 成长

模式分解是否为无损连接的判断方法

方法一:无损连接定理 关系模式R(U,F)的一个分解,ρ={R1<U1,F1>,R2<U2,F2>}具有无损连接的充分必要条件是: U1∩U2→U1-U2 €F...

3117
来自专栏H2Cloud

C++使用ffpython嵌入和扩展python

摘要: 在服务器编程中,经常会用到python脚本技术。Python是最流行的脚本之一,并且python拥有定义良好的C API接口,同时又有丰富的文档,与C+...

3554
来自专栏java、Spring、技术分享

JVM学习笔记

  java引用类型分为四种:类、接口、数组类和泛型参数。其中泛型参数会在编译过程中被擦除。因此 Java 虚拟机实际上只有前三种。在类、接口和数组类中,数组类...

1142
来自专栏ImportSource

JVM中的“同步”到底是怎么实现的?

JVM中的Synchronization是使用monitor entry和exit来实现的。不管是显式的还是隐式的。显式的是通过使用monitorenter和m...

2885
来自专栏PHP在线

良好的书写规范提高PHP代码执行效率

用单引号代替双引号来包含字符串,这样做会更快一些。因为 php 会在双引号包围的字符串中搜寻变量,单引号则不会,注意:只有 echo 能这么做,它是一种可以把多...

2905
来自专栏技术墨客

JVM与字节码——类的方法区模型 原

这是一段平凡得不能再平凡的Java代码,稍微有点编程语言入门知识的人都能理解它表达的意思:

692
来自专栏chenssy

【死磕Java并发】—–Java内存模型之重排序

在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件: 在单线程环境下不能改...

2566
来自专栏Java职业技术分享

可能是把Java内存区域讲的最清楚的一篇文章

对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不...

1240
来自专栏JavaQ

高并发编程-synchronized深入解析深挖

Java虚拟机的运行时数据区中的堆和方法区是所有线程共享的区域,如果多个线程需要同时使用共享的对象或类变量,则必须要正确协调它们对数据的访问。否则,程序将具有不...

911
来自专栏chenssy

【死磕Java并发】-----Java内存模型之重排序

在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,但是不能随意重排序,不是你想怎么排序就怎么排序,它需要满足以下两个条件: 1. 在单线程环境...

1462

扫码关注云+社区

领取腾讯云代金券