Programming DSL: JMatchers
ソフトウェア設計は「守破離」のプロセスです.--袁英傑
デザインのレビュー
前回「ソフトウェア職人グループ」で「直交設計」の基本理論、原則と応用を共有し、活動ラインの下で多くの友人からフィードバックを受けた.その中で
まず,これまでの
また、需要比較演算: 論理演算: 意味の比較
18歳以下の年齢の学生を検索するには、このように説明することができます.
ろんりてきいみ
探索して前進する.
次に、需要の進化と反復を通じて、既存の設計と実現を改善し続けます.「直交設計」の基本原則を応用し、
工場にひきこむ
すべての
より大きい実装
需要5:18歳以上の学生を探す
このうち、
実装が小さい
需要6:18歳未満の学生を探す
順に類推すると、「より小さい」という規則は以下のように実現される.
抽出関数
設計は明らかな繰返しを生み出し,「抽出関数」により繰返しを除去できる.
残りの比較動作、例えば
サブストリングを含める
需要7:名前に
サブストリングの先頭
需要8:名前が
「子列末尾」の論理は、
大文字と小文字を区別しない
需要9:名前の検索は
コンビネーションデザイン
関数の「組合せ設計」を用いて,コードの最大多重性を達成する.
ここで、
ユーザーの強制
この場合、
意味を修飾する
需要13:検索名に
それ以前にも、似たような「反義」の操作に遭遇したことがある.例えば、18歳以下の年齢の学生を検索することは、このように説明することができる.
両者は「反義」の記述に対して2つの異なる表現が存在し、暗い「繰り返し設計」であり、巧みな設計が繰り返しを解消する必要がある.
アンチセンスを抽出する
このためには、
こうぶん糖
その実装は、
論理または
需要13:名前に
ロジックと
需要14:名前の中で
たんらく
プレースホルダ
需要15:検索アルゴリズムは常に失敗または成功
レビュー
この簡単な
に感謝
「直交設計」の理論、原則、その方法論は前
デザインのレビュー
前回「ソフトウェア職人グループ」で「直交設計」の基本理論、原則と応用を共有し、活動ラインの下で多くの友人からフィードバックを受けた.その中で
DSL
の設計について話した人がいます.そのため、find
を例に、「直交設計」の応用を通じて、DSL
の設計過程を重点的に議論し続けます.まず,これまでの
find
演算子再構成の成果を振り返る.public Optional 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 Matcher starts(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 Matcher starts(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 Matcher ignoring_case(
Function> m, String substr) {
return str -> m.apply(lower(substr)).matches(lower(str));
}
ここで、
Function>
は1元関数であり、パラメータはString
であり、戻り値はMatcher
である.@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 Matcher not_contains(String substr) {
return str -> !str.contains(substr);
}
}
それ以前にも、似たような「反義」の操作に遭遇したことがある.例えば、18歳以下の年齢の学生を検索することは、このように説明することができる.
assertThat(find(students, age(ne(18))).isPresent(), is(true));
public final class Matchers {
......
public static Matcher ne(T expected) {
return actual -> !expected.equals(actual);
}
}
両者は「反義」の記述に対して2つの異なる表現が存在し、暗い「繰り返し設計」であり、巧みな設計が繰り返しを解消する必要がある.
アンチセンスを抽出する
このためには、
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
ソフトウェア巨匠「袁英傑」さんから出た.英傑は私の先生であり、私の親友でもある.その高深莫測のソフトウェア設計の修成、およびソフトウェア設計に対する独特な哲学的思考方式は、私たちの後輩が勉強した模範である.