Pull to refresh

Пример использования fluent interface в java для описания объектов предметной области

Reading time 2 min
Views 12K
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, задача становится технической: глаза боятся, руки делают.
Tags:
Hubs:
+6
Comments 10
Comments Comments 10

Articles