xunit设计思路

原创
2020/09/16 19:51
阅读数 167

设计思路

任何一个系统,模块,处理单元的工作模式都可以简化为IPO模型,即把一定的输入通过模块定义的动作,转化为一定的输出:

input --> process --> output。

由于input和output从数据本身的属性来看没有本质区别,xunit把输入数据/输出数据统一抽象为Context接口。

package com.xrosstools.xunit;

public interface Context {

}

接着把输入到输出之间的转化行为定义为接口,这种接口称为行为组件。

最基本的Converter接口,其工作就是把输入Context转化为输出Context。

如果输入与输出是相同的具体类型(Context的具体实现类),则Converter接口可以简化为Processor接口。即仅仅接收输入Context,但没有返回值。Processor一般对输入的Context的内部属性做处理。

由于功能的划分,需要将多个行为组件组合成为更大的结构,这种结构称为结构组件。

将不同的Converter或Processor依次串联起来,让Context从第一个处理单元一直流动到最后一个处理单元,以此完成一系列动作,这种最基本的结构就是结构组件Chain。

如果需要在两个行为组件之间选择一个,则需要对Context进行判断,这种封装了判断的行为组件就是Converter的另外一个变种Validator。Validator对Context进行判断操作,返回的是一个Boolean类型的输出。有了Validator,即可以组合成BiBranch这种结构组件。

同理,有时我们需要基于Context在多个行为组件之间进行选择,这种封装了选择行为的组件就是Converter的另一个变种Locator。Locator对Context进行标识符的识别判断,返回的是一个String类型的输出。有了Locator,即可组合成Branch这种结构组件。

基于Validator和单个行为组件,我们还可以构建While Loop和Do While loop结构组件,完成前置或后置条件判断的循环操作。

如果希望复用某个已有的行为组件,但接口与我们需要的不一至则可以通过Adapter来做适配

如果希望对某个组件做修饰,可以使用Decorator组件。Decorator的行为自动与被修饰的组件保持一致。

组件

xunit的组件可以分为行为组件和结构组件。行为组件定义针对Context能做的处理;结构组件则对行为组件进行组合,将多个行为组件结合为更大,结构更复杂的行为组件。与行为组件不同的是,结构组件的行为模式需要手工指定,缺省是Processor。

行为组件

processor

对Context进行处理,但没有返回值

public interface Processor extends Unit{
	void process(Context ctx);
}

converter

对传入的context进行转换,转变为新的Context。也可以返回原来的Context

public interface Converter extends Unit{
	Context convert(Context inputCtx);
}

validator

对Context进行true或者false的判断

public interface Validator extends Unit{
	boolean validate(Context ctx);
}

locator

对Context进行分类的判断。支持缺省值

public interface Locator extends Unit{
	String locate(Context ctx);
	void setDefaultKey(String key);
	String getDefaultKey();
}

dispatcher

对Context进行复制,分发,以及合并

public interface Dispatcher extends Unit{
    void setCompletionMode(CompletionMode mode);
    CompletionMode getCompletionMode();

    void setTimeout(long timeout);
    long getTimeout();

    void setTimeUnit(TimeUnit timeUnit);
    TimeUnit getTimeUnit();

    Map<String, Context> dispatch(Context inputCtx, Map<String, TaskType> taskInfoMap);

    // For CompletionMode.none, the results will be empty list
    Context merge(Context inputCtx, List<Context> results);

    Context onInvokeError(Map<String, Context> dispatchedContexts, Exception exception);

    Context onTaskError(Context taskCtx, Exception exception);
}

每个分支可以指定Task Type属性,目前一共有三种:

  • normal:普通的任务,是否必须完成根据CompletionMode来决定
  • mandatory:必须完成的任务,与critical CompletionMode配合
  • standalone:单独执行的任务。无需关注是否完成,不参与dispatcher的merge操作

CompletionMode是并发任务的执行方式,目前有四种:

public enum CompletionMode {
    all,
    any,
    critical,
    none;
}
  • all: normal的全部执行完毕
  • any: 任意normal执行完毕
  • critical: 全部mandatory执行完毕
  • none:调用所有的分支并且不等待返回既执行下面的操作

结构组件

chain

对内部的unit顺序调度处理。Context将被顺序处理。

if-else

通过Validator决定调用内部那个unit。也即BiBranch

branch

通过Locator判断调度内部那个unit

parallel branch

通过Dispatcher并发调度部分或全部unit

while loop

通过Validator判断的while结构

do while loop

通过Validator判断的do while结构

decorator

在操作前后处理

public interface Decorator extends Adapter {
	/**
     * Extends this method to provide decoration before decorated unit executed
     * @param ctx
     */
    void before(Context ctx);

	/**
     * Extends this method to provide decoration after decorated unit executed
     * @param ctx
     */
    void after(Context ctx);
}

adapter

将某种unit的行为转换为另一种

public interface Adapter extends Unit{
	void setUnit(Unit unit);
}

单元引用

如果流程图比较复杂,可以把图的某些部分抽离出来成为单独的图,然后再原图上进行引用。xunit会缺省的把与当前xunit文件同一个目录下的其他xunit文件识别出来作为可以选择的模块。

例如:

如果这个单元已经选择了某个xunit文件,则会在已选择的模块前面显示标记。 

如果要选择的模块与当前文件不是同一个目录,可以通过“Reference to module”对话框指定具体路径。

选择了某个模块后,其内部可选的单元会都列出来。已经选上的会有标记

如果没有选择任何外部模块,缺省情况下会显示当前模块所有可以被当前被选中的单元引用的单元 

属性自动识别

任何组件只要定义了PROP_KEY开头的静态String常量,则在通过编辑器打开后,其定义的常量的内容可以被编辑器识别和显示为该组件可配置的属性。这些属性会通过UnitPropertiesAware接口在组件被创建时设置进去。

例如DefaultUnitImpl的属性定义:

 * public static final String constant with name start with PROP_KEY will be displayed 
 * in "Set property" context menu
 */
public static final String PROP_KEY_SHOW_MESSAGE = "showMessage";
public static final String PROP_KEY_SHOW_FIELDS = "showFields";
public static final String PROP_KEY_SHOW_APP_PROP = "showApplicationProperties";

// The method take higher priority
public static final String PROP_KEY_EVALUATE_METHOD = "evaluateMethod";
public static final String PROP_KEY_EVALUATE_FIELD = "evaluateField";

public static final String PROP_KEY_VALIDATE_DEFAULT = "validateDefault";

这些属性在编辑器中可以显示出来:

这样做的好处是可以让组件自解释,方便其他用户集成。当然没有定义,用户也可以在任何时候自行创建。

结构不一致处理

结构组件一般要求其内部包含的行为组件与结构组件自身定义的行为模式一致。不一至的情况下,可以通过Adapter来保持一致。为高效的复用现有组件,避免频繁的配置大量简单重复的Adapter,结构组件内置了简单的适配机制。以Chain为例:

如果Chain的行为模式是Converter,而Chain中包含Processor,则Processor的输入Context会当作Convert的结果传递到下一个单元。

如果Chain的行为模式是Processor,而Chain中包含Converter,则Converter的convert方法会被调用,但是转化的输出Context则会被丢弃。最开始输入的Context会传递到下一个单元。

逻辑实现

展开阅读全文
加载中

作者的其它热门文章

打赏
0
0 收藏
分享
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部