摘要:袁英杰回顾设计上次在软件匠艺小组上分享了正交设计的基本理论,原则和应用,在活动线下收到了很多朋友的反馈。强迫用户虽然的设计高度可复用性,可由用户根据实际情况,自由拼装组合各种算子。鸣谢正交设计的理论原则及其方法论出自前软件大师袁英杰先生。
回顾设计软件设计是一个「守破离」的过程。 --袁英杰
上次在「软件匠艺小组」上分享了「正交设计」的基本理论,原则和应用,在活动线下收到了很多朋友的反馈。其中有人谈及到了DSL的设计,为此我将继续以find为例,通过「正交设计」的应用,重点讨论DSL的设计过程。
首先回顾一下之前find算子重构的成果。
publicOptional find(Iterable extends E> c, Predicate super E> p) { for (E e : c) if (p.test(e)) return Optional.of(e); return Optional.empty(); }
另外根据需求1~4,抽象了2个变化方向:
比较运算:==, !=
逻辑运算:&&, ||
比较语义public interface Matcher{ boolean matches(T actual); static Matcher eq(T expected) { return actual -> expected.equals(actual); } static Matcher ne(T expected) { return actual -> !expected.equals(actual); } }
查找年龄不等于18岁的学生,可以如此描述。
assertThat(find(students, age(ne(18))).isPresent(), is(true));逻辑语义
public interface Predicate{ boolean test(E e); default Predicate and(Predicate super E> other) { return e -> test(e) && other.test(e); } default Predicate or(Predicate super E> other) { return e -> test(e) || other.test(e); } }
查找名字叫horance的男生,可以表述为:
assertThat(find(students, name(eq("horance")).and(Human::male)).isPresent(), is(true));探索前进
接下来继续通过需求的演进和迭代,完善既有的设计和实现。应用「正交设计」的基本原则,加深对DSL设计的理解。
引入工厂public interface Matcher{ boolean matches(T actual); static Matcher eq(T expected) { return actual -> expected.equals(actual); } static Matcher ne(T expected) { return actual -> !expected.equals(actual); } }
将所有的Static Factory方法都放在接口中,虽然简单,也很自然。但如果方法之间产生重复代码,需要「提取函数」,设计将变得非常不灵活,因为接口内所有方法都将默认为public,这往往不是我们所期望的,为此可以将这些Static Factory方法搬迁到Matchers实用类中去。
public final class Matchers { public static实现大于Matcher eq(T expected) { return actual -> expected.equals(actual); } public static Matcher ne(T expected) { return actual -> !expected.equals(actual); } private Matchers() { } }
需求5: 查找年龄大于18岁的学生
assertThat(find(students, age(gt(18)).isPresent(), is(true));
public final class Matchers { ...... public static> Matcher gt(T expected) { return actual -> Ordering. natural().compare(actual, expected) > 0; } }
其中,natural代表了一种自然的比较规则。
public final class Ordering { public static实现小于> Comparator natural() { return (t1, t2) -> t1.compareTo(t2); } }
需求6: 查找年龄小于18岁的学生
assertThat(find(students, age(lt(18)).isPresent(), is(true));
依次类推,「小于」的规则实现如下:
public final class Matchers { ...... public static提取函数> Matcher gt(T expected) { return actual -> Ordering. natural().compare(actual, expected) > 0; } public static > Matcher lt(T expected) { return actual -> Ordering. natural().compare(actual, expected) < 0; } }
设计产生了明显的重复,可以通过「提取函数」来消除重复。
public final class Matchers { ...... public static> Matcher gt(T expected) { return actual -> compare(actual, expected) > 0; } public static > Matcher lt(T expected) { return actual -> compare(actual, expected) < 0; } private static > int compare(T actual, T expected) { return Ordering. natural().compare(actual, expected); } }
其余比较操作,例如大于等于,小于等于的设计和实现依此类推,在此不再重述。
包含子串需求7: 查找名字中包含horance的学生
assertThat(find(students, name(contains("horance")).isPresent(), is(true));
public final class Matchers { ...... public static Matcher子串开头contains(String substr) { return str -> str.contains(substr); } }
需求8: 查找名字以horance开头的学生
assertThat(find(students, name(starts("horance")).isPresent(), is(true));
public final class Matchers { ...... public static Matcherstarts(String substr) { return str -> str.startsWith(substr); } }
「子串结尾」的逻辑,可以设计ends的关键字,实现依此类推,在此不再重述。
不区分大小写需求9: 查找名字以horance开头,但不区分大小写的学生
assertThat(find(students, name(starts_ignoring_case("horance")).isPresent(), is(true));
public final class Matchers { ...... public static Matcherstarts(String substr) { return str -> str.startsWith(substr); } public static Matcher starts_ignoring_case(String substr) { return str -> lower(str).startsWith(lower(substr)); } private static String lower(String s) { return s.toLowerCase(); } }
starts与starts_ignoring_case之间存在微妙的重复设计,为此需要进一步消除重复。
组合式设计assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));
运用函数的「组合式设计」,达到代码的最大可复用性。从OO的角度看,ignoring_case是对starts, ends, contains的功能增强,是一种典型的「修饰」关系。
public static Matcherignoring_case( Function > m, String substr) { return str -> m.apply(lower(substr)).matches(lower(str)); }
其中,Function
@FunctionalInterface public interface Function强迫用户{ R apply(T t); }
虽然ignoring_case的设计高度可复用性,可由用户根据实际情况,自由拼装组合各种算子。但「方法引用」的语法,给用户给造成了不必要的负担。
assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));
可以提供starts_ignoring_case的语法糖,将用户犯错的几率降至最低,但要保证实现不存在重复设计。
assertThat(find(students, name(starts_ignoring_case("Horance"))).isPresent(), is(true));
此时,ignoring_case也应该重构为private,变为一个「可重用」的函数。
public static Matcher修饰语义starts_ignoring_case(String substr) { return ignoring_case(Matchers::starts, substr); } private static Matcher ignoring_case( Function > m, String substr) { return str -> m.apply(lower(substr)).matches(lower(str)); }
需求13: 查找名字中不包含horance的第一个学生
assertThat(find(students, name(not_contains("horance")).isPresent(), is(true));
public final class Matchers { ...... public static Matchernot_contains(String substr) { return str -> !str.contains(substr); } }
在这之前,也曾遇到过类似的「反义」的操作。例如,查找年龄不等于18岁的学生,可以如此描述。
assertThat(find(students, age(ne(18))).isPresent(), is(true));
public final class Matchers { ...... public staticMatcher ne(T expected) { return actual -> !expected.equals(actual); } }
两者对「反义」的描述存在两份不同的表示,是一种隐晦的「重复设计」,需要一种巧妙的设计消除重复。
提取反义为此,应该删除not_contains, ne的关键字,并提供统一的not关键字。
assertThat(find(students, name(not(contains("horance")))).isPresent(), is(true));
not的实现是一种「修饰」的手法,对既有的Matcher功能的增强,巧妙地取得了「反义」功能。
public final class Matchers { ...... public static语法糖Matcher not(Matcher matcher) { return actual -> !matcher.matches(actual); } }
对于not(eq(18))可以设计类似于not(18)的语法糖,使其更加简单。
assertThat(find(students, age(not(18))).isPresent(), is(true));
其实现就是对eq的一种修饰操作。
public final class Matchers { ...... public static逻辑或Matcher not(T expected) { return not(eq(expected)); } }
需求13: 查找名字中包含horance,或者以liu结尾的学生
assertThat(find(students, name(anyof(contains("horance"), ends("liu")))).isPresent(), is(true));
public final class Matchers { ...... @SafeVarargs public static逻辑与Matcher anyof(Matcher super T>... matchers) { return actual -> { for (Matcher super T> matcher : matchers) if (matcher.matches(actual)) return true; return false; }; } }
需求14: 查找名字中以horance开头,并且以liu结尾的学生
assertThat(find(students, name(allof(starts("horance"), ends("liu")))).isPresent(), is(true));
public final class Matchers { ...... @SafeVarargs public static短路Matcher allof(Matcher super T>... matchers) { return actual -> { for (Matcher super T> matcher : matchers) if (!matcher.matches(actual)) return false; return true; }; } }
allof与anyof之间的实现存在重复设计,可以通过提取函数消除重复。
public final class Matchers { ...... @SafeVarargs private static占位符Matcher combine( boolean shortcut, Matcher super T>... matchers) { return actual -> { for (Matcher super T> matcher : matchers) if (matcher.matches(actual) == shortcut) return shortcut; return !shortcut; }; } @SafeVarargs public static Matcher allof(Matcher super T>... matchers) { return combine(false, matchers); } @SafeVarargs public static Matcher anyof(Matcher super T>... matchers) { return combine(true, matchers); } }
需求15: 查找算法始终失败或成功
assertThat(find(students, age(always(false))).isPresent(), is(false));
public final class Matchers { ...... public static回顾Matcher always(boolean bool) { return e -> bool; } }
通过15个需求的迭代和演进,通过运用「正交设计」和「组合式设计」的基本思想,得到了一套接口丰富、表达力极强的DSL。
这一套简单的DSL是一个高度可复用的Matcher集合,其设计既包含了OO的方法论,也涉及到了FP的思维,整体性设计保持高度的一致性和统一性。
鸣谢「正交设计」的理论、原则、及其方法论出自前ThoughtWorks软件大师「袁英杰」先生。英杰既是我的老师,也是我的挚友;其高深莫测的软件设计的修为,及其对软件设计独特的哲学思维方式,是我等后辈学习的楷模。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/65593.html
摘要:命名模式为了做到自动发现机制,在运行时完成用例的组织,规定所有的测试用例必须遵循的函数原型。在后文介绍,可以将理解为及其的运行时行为其中,对于于子句,对于于子句。将的执行序列行为固化。 There are two ways of constructing a software design. One way is to make it so simple that there are ...
摘要:在这个例子中,我们将整合但您也可以使用其他连接池,如,,等。作为构建和执行。 jOOQ和Spring很容易整合。 在这个例子中,我们将整合: Alibaba Druid(但您也可以使用其他连接池,如BoneCP,C3P0,DBCP等)。 Spring TX作为事物管理library。 jOOQ作为SQL构建和执行library。 一、准备数据库 DROP TABLE IF EXIS...
摘要:转换成为模板函数联系上一篇文章,其实模板函数的构造都大同小异,基本是都是通过拼接函数字符串,然后通过对象转换成一个函数,变成一个函数之后,只要传入对应的数据,函数就会返回一个模板数据渲染好的字符串。 教程目录1.手把手教你从零写一个简单的 VUE2.手把手教你从零写一个简单的 VUE--模板篇 Hello,我又回来了,上一次的文章教会了大家如何书写一个简单 VUE,里面实现了VUE 的...
阅读 1399·2023-04-25 17:18
阅读 1861·2021-10-27 14:18
阅读 2104·2021-09-09 09:33
阅读 1822·2019-08-30 15:55
阅读 2004·2019-08-30 15:53
阅读 3423·2019-08-29 16:17
阅读 3411·2019-08-26 13:57
阅读 1711·2019-08-26 13:46