Disclaimer: нашел статью у себя в черновиках. Писал года полтора-два назад, почему не опубликовал — не помню. Просмотрел, вроде, не совсем бесполезная, пусть будет в открытом доступе.
В последнее время стало достаточно актуальным использование domain specific languages (DSL) — языков «заточенных» под конкретную предметную область. Слово «язык» в данном контексте не обязательно подразумевает именно новый язык программирования, зачастую можно обойтись и старым добрым.
На хабре не замечено ни одной статьи про fluent interface в контексте джавы, так что хотел бы поделиться своим опытом применения.
Спасибо Алепару (alepar) за наводку на статью Фаулера (большого фаната DSL)
Идея fluent interface в том, что API представляет собой некоторое подмножество домен-ориентированного языка описания. Причем это счастье доступно из базового языка программирования.
Текст Фаулера можно почитать по ссылке, я же приведу свой пример.
Допустим, нам нужно программно сконструировать объект соответствующий вот этому xml:
<Results>
<unit>123</unit>
<unit>321</unit>
<ResultSet>
<ResultsType>
<ResultType>ABC</ResultType>
<resAllTime>4.000000000000000</resAllTime>
<resAllTime>5.000000000000000</resAllTime>
<ResultsTimeBucket>
<bucketDate>777</bucketDate>
<value>3.000000000000000</value>
<value>0.000000000000000</value>
</ResultsTimeBucket>
<ResultsTimeBucket>
<bucketDate>888</bucketDate>
<value>1.000000000000000</value>
<value>5.000000000000000</value>
</ResultsTimeBucket>
</ResultsType>
</ResultSet>
</Results>
Смысл написанного в том, что у юнита 123 общее значение 4, которое складывается из 3 на дату 777 и еще 1 на дату 888. А у юнита 321 общее значение 5, которое целиком пришлось на дату 888.
API которое используется для построения такого объекта является надстройкой над плюсами (JNI):
Results r = new Results();
r.CallocResults(new int[] {123, 321}, new int[] {777, 888});
r.SetValue(4.0, "ABC", 0); // 0 - unit index in this case corresponds to "123"
r.SetValue(5.0, "ABC", 1);
r.SetBucketedValue(3.0, "ABC", 0, 0);
r.SetBucketedValue(1.0, "ABC", 1, 0); // 1 - bucketDate index (corresponds to 888), 0 - unit index
r.SetBucketedValue(5.0, "ABC", 1, 1);
В общем все по делу, придраться не к чему, но читать и представлять себе что будет в результате практически нереально …
Изначально это нужно было для юнит теста, поэтому использовать код с неочевидным результатом — не очень удачная идея, т.к. теряется наглядность юнит теста и его документирующая составляющая.
Я крепко подумал и попытался изобразить fluent interface для того же самого. Его реализация оказалась на удивление нетривиальной (из-за особенностей underlying JNI интерфейса), но зато выглядит он очень симпатично:
Results r = new ResultsBuilder()
.withUnits(123, 321)
.result("ABC")
.value(123, 4.)
.value(321, 5.)
.on(777)
.value(123, 3.)
.on(888)
.value(123, 1.)
.value(321, 5.)
.build();
В общем-то, освоив несколько приемов построения DSL на Java, задача становится технической: глаза боятся, руки делают.
Автор: pyatigil