zoukankan      html  css  js  c++  java
  • List<T>列表通用过滤

    List<T>列表通用过滤模块设计

    需求描述

    数据列表如List<Customer> 加载到DataGridView后,如果记录比较多可能需要对其进行二次过滤,即客户端过滤
    过滤条件做成可由用户设置的,如下图:

    在数据源是DataTable时,使用DataView的RowFilter可以轻松按用户的配置拼接出过滤表达式字符串来,
    设置RowFilter就可以实现过滤效果,但是当数据源是List<T>这样由EF,Linq to sql 等框架返回的集合时要实现上面的功能就需要费点力气了。

    问题分析:

    首先参考上面的截图,用户设置好过滤条件后会形成:" (工号 = 222 And 部门=人力) Or 性别=女" 这样的过滤表达式,可以表示成(Exp1 And Exp2) Or Exp3 这样的形式.针对"工号=222"这样的Exp求值我们会转变成针对Employe实体的EmpId属性是否等于222的判断(Employe.EmpId==222),这个可以通过反射方式来实现,将多个Exp求值的结果通过And或Or连接并运算得出最终结果,True表示这一行(Employe)符合.

    不过考虑Exp1 Or Exp2 Or Exp3  这样的条件,如果第一个Exp1是True的话结果必定是True,这个时候还去计算Exp2,Exp3是完全多余的,如果List集合有几万条记录(当然超过几千行的列表对用户来说是没有多少意义的,一般人不会看那么多行,这个时候应该想想过滤条件设置是否合理)那么针对列表的每个实体的每个属性(字段)使用反射的方式计算一遍Exp将是一个比较大的开销,好在And与Or跟算术操作符(+,-,*,/)有所不同,And运算时只要两个操作数中有一个是False就没必要计算另外一个操作数(这里的是Exp)而Or在一个操作数是True时就可以忽略另一个操作数。不过当所的Exp都是false时针对上面"Exp1 Or Exp2 Or Exp3"这样的表达式计算每个Exp是不可避免的

    到这里我们可以看到该问题的本质就是表达式求值,而操作符只限And与Or两个二元操作符,最后结果是True或False.

    设计实现:


    首先我们将用户设置的过滤表达式转变成逆波兰式(后缀表达式),接着传入每个要判断的实体,使用后缀表达式求出该实体是否符合过滤条件,
    当然我们也可以将后缀表达式构建成Expression树,接着将该Expression编译成动态方法(委托),使用该委托对每个实体做出判断,下面的代码给出了这两种实现,
    经过测试发现两种方法速度区别不大。

    自己对逆波兰式求值时需要下面的判定表,如果构建Expression树则Expression.AndAlso或Expression.OrElse会自己判断是否对两个操作数都进行计算(参考下面的代码)

    代码:

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    using System.Reflection;
    namespace FIStudio.WinUI.Core
    {
        public class ExpParser
        {
            
            public ExpParser(List<Elem> midfixList)
            {
                this.MidfixList = midfixList;
    
            }
            public List<Elem> PostfixList { get; private set; }
            public List<Elem> MidfixList { get; private set; }
            private Stack<Elem> CalcStack = new Stack<Elem>();
            private void GuardMidfixListExist()
            {
                if (MidfixList == null || MidfixList.Count <= 0) throw new Exception("中序列表为null或为空!");
            }
            private void EnsurePostfixReady()
            {
                if (PostfixList == null)
                {
                    PostfixList = DoParse(MidfixList);
                    if (PostfixList == null || PostfixList.Count <= 0) throw new Exception("后序列表为null或为空!");
                }
            }
            /// <summary>
            /// 判断元素是否符合要求
            /// </summary>
            /// <param name="ent"></param>
            /// <returns></returns>
            public bool IsSatisfy(object ent)
            {
                GuardMidfixListExist();
                EnsurePostfixReady();
    
    
                CalcStack.Clear();
                foreach (var item in PostfixList)
                {
                    if (item is ExpElem)
                    {
                        CalcStack.Push(item);
                        continue;
                    }
                    #region And 运算
                    if (item is AndElem)
                    {
                        var op1 = CalcStack.Pop() as ExpElem;
                        var op2 = CalcStack.Pop() as ExpElem;
    
                        //任意一个是false则直接压入false
                        if (op1.Result == false || op2.Result == false)
                        {
                            CalcStack.Push(new ExpElem() { Result = false });
                            continue;
                        }
    
                        if (!op1.Result.HasValue && !op2.Result.HasValue)
                        {
                            op1.Compare(ent);
                            if (op1.Result.Value == false)
                            {
                                CalcStack.Push(new ExpElem() { Result = false });
                                continue;
                            }
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                            continue;
                        }
                        if (!op1.Result.HasValue && op2.Result == true)
                        {
                            op1.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op1.Result });
                            continue;
                        }
                        if (op1.Result == true && !op2.Result.HasValue)
                        {
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                            continue;
    
                        }
                        if (op1.Result == true && op2.Result == true)
                        {
                            CalcStack.Push(new ExpElem() { Result = true });
                            continue;
                        }
    
                    }
                    #endregion
                    #region Or 运算
                    if (item is OrElem)
                    {
                        var op1 = CalcStack.Pop() as ExpElem;
                        var op2 = CalcStack.Pop() as ExpElem;
    
                        //任意一个是true则直接压入true
                        if (op1.Result == true || op1.Result == true)
                        {
                            CalcStack.Push(new ExpElem() { Result = true });
                            continue;
                        }
    
                        if (!op1.Result.HasValue && !op2.Result.HasValue)
                        {
                            op1.Compare(ent);
                            if (!op1.Result == true)
                            {
                                CalcStack.Push(new ExpElem() { Result = true });
                                continue;
                            }
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                        }
                        if (!op1.Result.HasValue && op2.Result == false)
                        {
                            op1.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op1.Result });
                            continue;
                        }
                        if (op1.Result == false && !op2.Result.HasValue)
                        {
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
    
                            continue;
                        }
                        if (op1.Result == false && op2.Result == false)
                        {
                            CalcStack.Push(new ExpElem() { Result = false });
                        }
    
                    }
                    #endregion
                }
                return (CalcStack.Pop() as ExpElem).Result.Value;
    
            }
            /// <summary>
            /// 生成判断函数
            /// </summary>
            /// <returns></returns>
            public  Expression<Func<T,bool>> GenIsSatisfyFunc<T>()
            {
                GuardMidfixListExist();
                EnsurePostfixReady();
                Stack<object> stack = new Stack<object>();
    
                ParameterExpression entExp = Expression.Parameter(typeof(T), "ent");
                foreach (var elem in PostfixList)
                {
    
                    if (elem is ExpElem)
                    {
                        stack.Push(elem);
                        continue;
                    }
                    if (elem is AndElem)
                    {
                        var elem1 = stack.Pop();
                        var elem2 = stack.Pop();
    
                        var exp= Expression.AndAlso(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                        
                        stack.Push(exp);
                        continue;
                    }
                    if(elem is OrElem)
                    {
                        var elem1 = stack.Pop();
                        var elem2 = stack.Pop();
    
                        var exp= Expression.OrElse(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                        
                        stack.Push(exp);
                        continue;
                    }
                }
                LambdaExpression lambda= Expression.Lambda<Func<T,bool>>( stack.Pop() as Expression,entExp);
    
                return lambda as  Expression<Func<T,bool>>;
    
            }
            private Expression GetCallExpression(object elem, ParameterExpression entExp)
            {
                if (elem is ExpElem)
                {
                   return Expression.Call(Expression.Constant(elem), typeof(ExpElem).GetMethod("Compare"), entExp);
                }
                return elem as Expression;
            }
            /// <summary>
            /// 中序表达式转后缀表达式
            /// </summary>
            /// <param name="midfix"></param>
            /// <returns></returns>
            private  List<Elem> DoParse(List<Elem> midfix)
            {
                Stack<Elem> stack = new Stack<Elem>();
                var list=new List<Elem>();
                foreach (var elem in midfix)
                {
                    if (elem is ExpElem)
                    {
                        list.Add(elem);
                        continue;
                    }
                    if (elem is LBElem)
                    {
                        stack.Push(elem);
                        continue;
                    }
                    if (elem is RBElem)
                    {
                        var e = stack.Pop();
                        while (!(e is LBElem))
                        {
                            list.Add(e);
                            e = stack.Pop();
                          
    
                        }
                        continue;
                    }
                    if((elem is AndElem) || (elem is OrElem))
                    {
                        if (stack.Count > 0)
                        {
                            var e = stack.Peek();
                            while ( !(e is LBElem) && elem.Priority <= e.Priority)
                            {
                                list.Add(stack.Pop());
                                if (stack.Count <= 0) break;
                                e = stack.Peek();
                            }
                        }
                        stack.Push(elem);
                    }
    
                }
                while (stack.Count > 0)
                {
                    list.Add(stack.Pop());
                }
    
                return list;
    
            }
        }
        #region 节点定义
        public class Elem
        {
            public virtual string Name { get; set; }
            public virtual int Priority { get; set; }
            public Object Data { get; set; }
        }
        /// <summary>
        /// 左括号
        /// 注意stack中只会压入'(','And','Or'
        /// </summary>
        public class LBElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "(";
                }
            }
            public override int Priority
            {
                get
                {
                    return 59;
                }
            }
        }
        /// <summary>
        /// 右括号
        /// </summary>
        public class RBElem : Elem
        {
            public override string Name
            {
                get
                {
                    return ")";
                }
            }
            public override int Priority
            {
                get
                {
                    return 99;
                }
            }
    
        }
    
        public class AndElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "And";
                }
            }
            public override int Priority
            {
                get
                {
                    return 88;
                }
            }
        }
    
        public class OrElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "Or";
                }
            }
            public override int Priority
            {
                get
                {
                    return 77;
                }
            }
        }
        public class ExpElem : Elem
        {
    
            public override int Priority
            {
                get
                {
                    return 66;
                }
            }
            public bool Compare(object ent)
            {
                Console.WriteLine("计算了:" + Name);
                bool? ret=null;
                if (AssertType == Core.CompareType.Equal)
                {
                    ret= string.Compare(GetV(ent),Value,true)==0;
                }
                if (AssertType == Core.CompareType.NotEqual)
                {
                    ret =  string.Compare(GetV(ent), Value, true) != 0;
                }
                if (AssertType == Core.CompareType.Greate)
                {
                    ret = string.Compare(GetV(ent), Value, true) > 0;
                }
                if (AssertType == Core.CompareType.GreateOrEqual)
                {
                    ret = string.Compare(GetV(ent), Value, true) >= 0;
                }
                if (AssertType == Core.CompareType.Less)
                {
                    ret = string.Compare(GetV(ent), Value, true) < 0;
                }
                if (AssertType == Core.CompareType.LessOrEqual)
                {
                    ret = string.Compare(GetV(ent), Value, true) <= 0;
                }
                if (AssertType == Core.CompareType.Contains)
                {
                    ret = GetV(ent).Contains(Value);
                }
                if (AssertType == Core.CompareType.NoContains)
                {
                    ret =! GetV(ent).Contains(Value);
                }
                if (AssertType == Core.CompareType.StartWith)
                {
                    ret = GetV(ent).StartsWith(Value);
                }
                if (AssertType == Core.CompareType.EndWith)
                {
                    ret = GetV(ent).EndsWith(Value);
                }
                if (!ret.HasValue) throw new Exception("未知的CompareType!");
                Result = ret;
                return ret.Value;
    
               
            }
            public bool? Result { get; set; }
            public PropertyInfo Property { get; set; }
            public CompareType AssertType { get; set; }
            public string Value { get; set; }
    
            private string GetV(object ent)
            {
                var tmp= Property.GetValue(ent, null);
                if (tmp == null) tmp = string.Empty;
                return tmp.ToString();
            }
    
            
        }
    
        public enum CompareType { Equal, NotEqual, Less, LessOrEqual, Greate, GreateOrEqual, Contains, NoContains, StartWith, EndWith };
    
    #endregion
    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Linq.Expressions;
    using System.Reflection;
    namespace FIStudio.WinUI.Core
    {
        public class ExpParser
        {
            
            public ExpParser(List<Elem> midfixList)
            {
                this.MidfixList = midfixList;
    
            }
            public List<Elem> PostfixList { get; private set; }
            public List<Elem> MidfixList { get; private set; }
            private Stack<Elem> CalcStack = new Stack<Elem>();
            private void GuardMidfixListExist()
            {
                if (MidfixList == null || MidfixList.Count <= 0) throw new Exception("中序列表为null或为空!");
            }
            private void EnsurePostfixReady()
            {
                if (PostfixList == null)
                {
                    PostfixList = DoParse(MidfixList);
                    if (PostfixList == null || PostfixList.Count <= 0) throw new Exception("后序列表为null或为空!");
                }
            }
            /// <summary>
            /// 判断元素是否符合要求
            /// </summary>
            /// <param name="ent"></param>
            /// <returns></returns>
            public bool IsSatisfy(object ent)
            {
                GuardMidfixListExist();
                EnsurePostfixReady();
    
    
                CalcStack.Clear();
                foreach (var item in PostfixList)
                {
                    if (item is ExpElem)
                    {
                        CalcStack.Push(item);
                        continue;
                    }
                    #region And 运算
                    if (item is AndElem)
                    {
                        var op1 = CalcStack.Pop() as ExpElem;
                        var op2 = CalcStack.Pop() as ExpElem;
    
                        //任意一个是false则直接压入false
                        if (op1.Result == false || op2.Result == false)
                        {
                            CalcStack.Push(new ExpElem() { Result = false });
                            continue;
                        }
    
                        if (!op1.Result.HasValue && !op2.Result.HasValue)
                        {
                            op1.Compare(ent);
                            if (op1.Result.Value == false)
                            {
                                CalcStack.Push(new ExpElem() { Result = false });
                                continue;
                            }
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                            continue;
                        }
                        if (!op1.Result.HasValue && op2.Result == true)
                        {
                            op1.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op1.Result });
                            continue;
                        }
                        if (op1.Result == true && !op2.Result.HasValue)
                        {
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                            continue;
    
                        }
                        if (op1.Result == true && op2.Result == true)
                        {
                            CalcStack.Push(new ExpElem() { Result = true });
                            continue;
                        }
    
                    }
                    #endregion
                    #region Or 运算
                    if (item is OrElem)
                    {
                        var op1 = CalcStack.Pop() as ExpElem;
                        var op2 = CalcStack.Pop() as ExpElem;
    
                        //任意一个是true则直接压入true
                        if (op1.Result == true || op1.Result == true)
                        {
                            CalcStack.Push(new ExpElem() { Result = true });
                            continue;
                        }
    
                        if (!op1.Result.HasValue && !op2.Result.HasValue)
                        {
                            op1.Compare(ent);
                            if (!op1.Result == true)
                            {
                                CalcStack.Push(new ExpElem() { Result = true });
                                continue;
                            }
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
                        }
                        if (!op1.Result.HasValue && op2.Result == false)
                        {
                            op1.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op1.Result });
                            continue;
                        }
                        if (op1.Result == false && !op2.Result.HasValue)
                        {
                            op2.Compare(ent);
                            CalcStack.Push(new ExpElem() { Result = op2.Result });
    
                            continue;
                        }
                        if (op1.Result == false && op2.Result == false)
                        {
                            CalcStack.Push(new ExpElem() { Result = false });
                        }
    
                    }
                    #endregion
                }
                return (CalcStack.Pop() as ExpElem).Result.Value;
    
            }
            /// <summary>
            /// 生成判断函数
            /// </summary>
            /// <returns></returns>
            public  Expression<Func<T,bool>> GenIsSatisfyFunc<T>()
            {
                GuardMidfixListExist();
                EnsurePostfixReady();
                Stack<object> stack = new Stack<object>();
    
                ParameterExpression entExp = Expression.Parameter(typeof(T), "ent");
                foreach (var elem in PostfixList)
                {
    
                    if (elem is ExpElem)
                    {
                        stack.Push(elem);
                        continue;
                    }
                    if (elem is AndElem)
                    {
                        var elem1 = stack.Pop();
                        var elem2 = stack.Pop();
    
                        var exp= Expression.AndAlso(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                        
                        stack.Push(exp);
                        continue;
                    }
                    if(elem is OrElem)
                    {
                        var elem1 = stack.Pop();
                        var elem2 = stack.Pop();
    
                        var exp= Expression.OrElse(GetCallExpression(elem1,entExp),GetCallExpression(elem2,entExp));
                        
                        stack.Push(exp);
                        continue;
                    }
                }
                LambdaExpression lambda= Expression.Lambda<Func<T,bool>>( stack.Pop() as Expression,entExp);
    
                return lambda as  Expression<Func<T,bool>>;
    
            }
            private Expression GetCallExpression(object elem, ParameterExpression entExp)
            {
                if (elem is ExpElem)
                {
                   return Expression.Call(Expression.Constant(elem), typeof(ExpElem).GetMethod("Compare"), entExp);
                }
                return elem as Expression;
            }
            /// <summary>
            /// 中序表达式转后缀表达式
            /// </summary>
            /// <param name="midfix"></param>
            /// <returns></returns>
            private  List<Elem> DoParse(List<Elem> midfix)
            {
                Stack<Elem> stack = new Stack<Elem>();
                var list=new List<Elem>();
                foreach (var elem in midfix)
                {
                    if (elem is ExpElem)
                    {
                        list.Add(elem);
                        continue;
                    }
                    if (elem is LBElem)
                    {
                        stack.Push(elem);
                        continue;
                    }
                    if (elem is RBElem)
                    {
                        var e = stack.Pop();
                        while (!(e is LBElem))
                        {
                            list.Add(e);
                            e = stack.Pop();
                          
    
                        }
                        continue;
                    }
                    if((elem is AndElem) || (elem is OrElem))
                    {
                        if (stack.Count > 0)
                        {
                            var e = stack.Peek();
                            while ( !(e is LBElem) && elem.Priority <= e.Priority)
                            {
                                list.Add(stack.Pop());
                                if (stack.Count <= 0) break;
                                e = stack.Peek();
                            }
                        }
                        stack.Push(elem);
                    }
    
                }
                while (stack.Count > 0)
                {
                    list.Add(stack.Pop());
                }
    
                return list;
    
            }
        }
        #region 节点定义
        public class Elem
        {
            public virtual string Name { get; set; }
            public virtual int Priority { get; set; }
            public Object Data { get; set; }
        }
        /// <summary>
        /// 左括号
        /// 注意stack中只会压入'(','And','Or'
        /// </summary>
        public class LBElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "(";
                }
            }
            public override int Priority
            {
                get
                {
                    return 59;
                }
            }
        }
        /// <summary>
        /// 右括号
        /// </summary>
        public class RBElem : Elem
        {
            public override string Name
            {
                get
                {
                    return ")";
                }
            }
            public override int Priority
            {
                get
                {
                    return 99;
                }
            }
    
        }
    
        public class AndElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "And";
                }
            }
            public override int Priority
            {
                get
                {
                    return 88;
                }
            }
        }
    
        public class OrElem : Elem
        {
            public override string Name
            {
                get
                {
                    return "Or";
                }
            }
            public override int Priority
            {
                get
                {
                    return 77;
                }
            }
        }
        public class ExpElem : Elem
        {
    
            public override int Priority
            {
                get
                {
                    return 66;
                }
            }
            public bool Compare(object ent)
            {
                Console.WriteLine("计算了:" + Name);
                bool? ret=null;
                if (AssertType == Core.CompareType.Equal)
                {
                    ret= string.Compare(GetV(ent),Value,true)==0;
                }
                if (AssertType == Core.CompareType.NotEqual)
                {
                    ret =  string.Compare(GetV(ent), Value, true) != 0;
                }
                if (AssertType == Core.CompareType.Greate)
                {
                    ret = string.Compare(GetV(ent), Value, true) > 0;
                }
                if (AssertType == Core.CompareType.GreateOrEqual)
                {
                    ret = string.Compare(GetV(ent), Value, true) >= 0;
                }
                if (AssertType == Core.CompareType.Less)
                {
                    ret = string.Compare(GetV(ent), Value, true) < 0;
                }
                if (AssertType == Core.CompareType.LessOrEqual)
                {
                    ret = string.Compare(GetV(ent), Value, true) <= 0;
                }
                if (AssertType == Core.CompareType.Contains)
                {
                    ret = GetV(ent).Contains(Value);
                }
                if (AssertType == Core.CompareType.NoContains)
                {
                    ret =! GetV(ent).Contains(Value);
                }
                if (AssertType == Core.CompareType.StartWith)
                {
                    ret = GetV(ent).StartsWith(Value);
                }
                if (AssertType == Core.CompareType.EndWith)
                {
                    ret = GetV(ent).EndsWith(Value);
                }
                if (!ret.HasValue) throw new Exception("未知的CompareType!");
                Result = ret;
                return ret.Value;
    
               
            }
            public bool? Result { get; set; }
            public PropertyInfo Property { get; set; }
            public CompareType AssertType { get; set; }
            public string Value { get; set; }
    
            private string GetV(object ent)
            {
                var tmp= Property.GetValue(ent, null);
                if (tmp == null) tmp = string.Empty;
                return tmp.ToString();
            }
    
            
        }
    
        public enum CompareType { Equal, NotEqual, Less, LessOrEqual, Greate, GreateOrEqual, Contains, NoContains, StartWith, EndWith };
    
    #endregion
    
    }
    复制代码

    参考:

      逆波兰式构建方法

      1、从左至右扫描一中缀表达式。
                    2、若读取的是操作数,则判断该操作数的类型,并将该操作数存入操作数堆栈
                    3、若读取的是运算符
                       (1) 该运算符为左括号"(",则直接存入运算符堆栈。
                       (2) 该运算符为右括号")",则输出运算符堆栈中的运算符到操作数堆栈,直到遇到左括号为止,此时抛弃该左括号。
                       (3) 该运算符为非括号运算符:
                           (a) 若运算符堆栈栈顶的运算符为左括号,则直接存入运算符堆栈。
                           (b) 若比运算符堆栈栈顶的运算符优先级高,则直接存入运算符堆栈。
                           (c) 若比运算符堆栈栈顶的运算符优先级低或相等,则输出栈顶运算符到操作数堆栈,
                                直至运算符栈栈顶运算符低于(不包括等于)该运算符优先级,或为左括号,并将当前运算符压入运算符堆栈。
                    4、当表达式读取完成后运算符堆栈中尚有运算符时,则依序取出运算符到操作数堆栈,直到运算符堆栈为空。

       逆波兰表达式求值算法:

                   1、循环扫描语法单元的项目。
                   2、如果扫描的项目是操作数,则将其压入操作数堆栈,并扫描下一个项目。
                   3、如果扫描的项目是一个二元运算符,则对栈的顶上两个操作数执行该运算。
                   4、如果扫描的项目是一个一元运算符,则对栈的最顶上操作数执行该运算。
                   5、将运算结果重新压入堆栈。
                   6、重复步骤2-5,堆栈中即为结果值。

      资源

    字符串公式解析器——使用“逆波兰式算法”及C#实现

     
     
  • 相关阅读:
    Robot Framework-资源文件的使用方法(7)
    Robot Framework-用户关键字的使用方法(6)
    robotframework 新建UI自动化测试用例实例一(2)
    robotframework--登录接口,post传递多个参数、及获取content中指定属性的值(5)
    robotframework基础知识(2)
    win7如何打开防火墙某个端口的tcp连接
    外观模式
    享元模式
    代理模式
    模板模式
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2708817.html
Copyright © 2011-2022 走看看