摘要:袁英杰回顧設(shè)計上次在軟件匠藝小組上分享了正交設(shè)計的基本理論,原則和應(yīng)用,在活動線下收到了很多朋友的反饋。強迫用戶雖然的設(shè)計高度可復(fù)用性,可由用戶根據(jù)實際情況,自由拼裝組合各種算子。鳴謝正交設(shè)計的理論原則及其方法論出自前軟件大師袁英杰先生。
回顧設(shè)計軟件設(shè)計是一個「守破離」的過程。 --袁英杰
上次在「軟件匠藝小組」上分享了「正交設(shè)計」的基本理論,原則和應(yīng)用,在活動線下收到了很多朋友的反饋。其中有人談及到了DSL的設(shè)計,為此我將繼續(xù)以find為例,通過「正交設(shè)計」的應(yīng)用,重點討論DSL的設(shè)計過程。
首先回顧一下之前find算子重構(gòu)的成果。
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(); }
另外根據(jù)需求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歲的學(xué)生,可以如此描述。
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));探索前進
接下來繼續(xù)通過需求的演進和迭代,完善既有的設(shè)計和實現(xiàn)。應(yīng)用「正交設(shè)計」的基本原則,加深對DSL設(shè)計的理解。
引入工廠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方法都放在接口中,雖然簡單,也很自然。但如果方法之間產(chǎn)生重復(fù)代碼,需要「提取函數(shù)」,設(shè)計將變得非常不靈活,因為接口內(nèi)所有方法都將默認(rèn)為public,這往往不是我們所期望的,為此可以將這些Static Factory方法搬遷到Matchers實用類中去。
public final class Matchers { public static實現(xiàn)大于Matcher eq(T expected) { return actual -> expected.equals(actual); } public static Matcher ne(T expected) { return actual -> !expected.equals(actual); } private Matchers() { } }
需求5: 查找年齡大于18歲的學(xué)生
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代表了一種自然的比較規(guī)則。
public final class Ordering { public static實現(xiàn)小于> Comparator natural() { return (t1, t2) -> t1.compareTo(t2); } }
需求6: 查找年齡小于18歲的學(xué)生
assertThat(find(students, age(lt(18)).isPresent(), is(true));
依次類推,「小于」的規(guī)則實現(xiàn)如下:
public final class Matchers { ...... public static提取函數(shù)> 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; } }
設(shè)計產(chǎn)生了明顯的重復(fù),可以通過「提取函數(shù)」來消除重復(fù)。
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); } }
其余比較操作,例如大于等于,小于等于的設(shè)計和實現(xiàn)依此類推,在此不再重述。
包含子串需求7: 查找名字中包含horance的學(xué)生
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開頭的學(xué)生
assertThat(find(students, name(starts("horance")).isPresent(), is(true));
public final class Matchers { ...... public static Matcherstarts(String substr) { return str -> str.startsWith(substr); } }
「子串結(jié)尾」的邏輯,可以設(shè)計ends的關(guān)鍵字,實現(xiàn)依此類推,在此不再重述。
不區(qū)分大小寫需求9: 查找名字以horance開頭,但不區(qū)分大小寫的學(xué)生
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之間存在微妙的重復(fù)設(shè)計,為此需要進一步消除重復(fù)。
組合式設(shè)計assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));
運用函數(shù)的「組合式設(shè)計」,達(dá)到代碼的最大可復(fù)用性。從OO的角度看,ignoring_case是對starts, ends, contains的功能增強,是一種典型的「修飾」關(guān)系。
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的設(shè)計高度可復(fù)用性,可由用戶根據(jù)實際情況,自由拼裝組合各種算子。但「方法引用」的語法,給用戶給造成了不必要的負(fù)擔(dān)。
assertThat(find(students, name(ignoring_case(Matchers::starts, "Horance"))).isPresent(), is(true));
可以提供starts_ignoring_case的語法糖,將用戶犯錯的幾率降至最低,但要保證實現(xiàn)不存在重復(fù)設(shè)計。
assertThat(find(students, name(starts_ignoring_case("Horance"))).isPresent(), is(true));
此時,ignoring_case也應(yīng)該重構(gòu)為private,變?yōu)橐粋€「可重用」的函數(shù)。
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的第一個學(xué)生
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歲的學(xué)生,可以如此描述。
assertThat(find(students, age(ne(18))).isPresent(), is(true));
public final class Matchers { ...... public staticMatcher ne(T expected) { return actual -> !expected.equals(actual); } }
兩者對「反義」的描述存在兩份不同的表示,是一種隱晦的「重復(fù)設(shè)計」,需要一種巧妙的設(shè)計消除重復(fù)。
提取反義為此,應(yīng)該刪除not_contains, ne的關(guān)鍵字,并提供統(tǒng)一的not關(guān)鍵字。
assertThat(find(students, name(not(contains("horance")))).isPresent(), is(true));
not的實現(xiàn)是一種「修飾」的手法,對既有的Matcher功能的增強,巧妙地取得了「反義」功能。
public final class Matchers { ...... public static語法糖Matcher not(Matcher matcher) { return actual -> !matcher.matches(actual); } }
對于not(eq(18))可以設(shè)計類似于not(18)的語法糖,使其更加簡單。
assertThat(find(students, age(not(18))).isPresent(), is(true));
其實現(xiàn)就是對eq的一種修飾操作。
public final class Matchers { ...... public static邏輯或Matcher not(T expected) { return not(eq(expected)); } }
需求13: 查找名字中包含horance,或者以liu結(jié)尾的學(xué)生
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結(jié)尾的學(xué)生
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之間的實現(xiàn)存在重復(fù)設(shè)計,可以通過提取函數(shù)消除重復(fù)。
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個需求的迭代和演進,通過運用「正交設(shè)計」和「組合式設(shè)計」的基本思想,得到了一套接口豐富、表達(dá)力極強的DSL。
這一套簡單的DSL是一個高度可復(fù)用的Matcher集合,其設(shè)計既包含了OO的方法論,也涉及到了FP的思維,整體性設(shè)計保持高度的一致性和統(tǒng)一性。
鳴謝「正交設(shè)計」的理論、原則、及其方法論出自前ThoughtWorks軟件大師「袁英杰」先生。英杰既是我的老師,也是我的摯友;其高深莫測的軟件設(shè)計的修為,及其對軟件設(shè)計獨特的哲學(xué)思維方式,是我等后輩學(xué)習(xí)的楷模。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/65593.html
摘要:命名模式為了做到自動發(fā)現(xiàn)機制,在運行時完成用例的組織,規(guī)定所有的測試用例必須遵循的函數(shù)原型。在后文介紹,可以將理解為及其的運行時行為其中,對于于子句,對于于子句。將的執(zhí)行序列行為固化。 There are two ways of constructing a software design. One way is to make it so simple that there are ...
摘要:在這個例子中,我們將整合但您也可以使用其他連接池,如,,等。作為構(gòu)建和執(zhí)行。 jOOQ和Spring很容易整合。 在這個例子中,我們將整合: Alibaba Druid(但您也可以使用其他連接池,如BoneCP,C3P0,DBCP等)。 Spring TX作為事物管理library。 jOOQ作為SQL構(gòu)建和執(zhí)行l(wèi)ibrary。 一、準(zhǔn)備數(shù)據(jù)庫 DROP TABLE IF EXIS...
摘要:轉(zhuǎn)換成為模板函數(shù)聯(lián)系上一篇文章,其實模板函數(shù)的構(gòu)造都大同小異,基本是都是通過拼接函數(shù)字符串,然后通過對象轉(zhuǎn)換成一個函數(shù),變成一個函數(shù)之后,只要傳入對應(yīng)的數(shù)據(jù),函數(shù)就會返回一個模板數(shù)據(jù)渲染好的字符串。 教程目錄1.手把手教你從零寫一個簡單的 VUE2.手把手教你從零寫一個簡單的 VUE--模板篇 Hello,我又回來了,上一次的文章教會了大家如何書寫一個簡單 VUE,里面實現(xiàn)了VUE 的...
閱讀 1607·2023-04-25 17:18
閱讀 1947·2021-10-27 14:18
閱讀 2192·2021-09-09 09:33
閱讀 1891·2019-08-30 15:55
閱讀 2084·2019-08-30 15:53
閱讀 3497·2019-08-29 16:17
閱讀 3486·2019-08-26 13:57
閱讀 1791·2019-08-26 13:46