DSL

Domain Specific Language

プログラミング言語に埋め込む内部DSL

独立した専用言語の外部DSL

Demo

★Fluent interface pattern (内部DSL)

サンプル1

interface Start {

End singleWord();

End parameterisedWord(String parameter);

Intermediate1 word1();

Intermediate2 word2();

Intermediate3 word3();

}

interface End {

void end();

}

interface Intermediate1 extends End {

End optionalWord();

}

interface Intermediate2 {

End wordChoiceA();

End wordChoiceB();

}

interface Intermediate3 extends End {

Intermediate3 word3();

}

Start start = ...

start.singleWord().end();

start.parameterisedWord("xxx").end();

start.word1().end();

start.word1().optionalWord().end();

start.word2().wordChoiceA().end();

start.word2().wordChoiceB().end();

start.word3().end();

start.word3().word3().end();

start.word3().word3().word3().end();

サンプル2

Car.Builder()

.withBody(Body.Builder()

.withColor()

.withDoorCount()

.build())

.withEngine(Engine.Builder()

.withPower()

.build())

.withWheel(Wheel.Builder()

.withSize()

.build())

.build();

★Fluent API Design

Interface based version

Mock mock = new Mock();

mock.whenConnecting()

.expectValue("a").and()

.expectHeader("header").andRespondWithValue("b");

public interface RequestExpectations {

ResponseActions expectValue(String value);

ResponseActions expectHeader(String name);

}

public interface ResponseActions {

RequestExpectations and();

void andRespondWithValue(String value);

void andRespondWithError();

}

public class Mock {

public RequestExpectations whenConnecting() {

return new MockInternal();

}

}

class MockInternal implements RequestExpectations, ResponseActions{

public RequestExpectations and() {

...

return this;

}

public void andRespondWithValue(String value) { ... }

public void andRespondWithError() { ... }

public ResponseActions expectValue(String value) {

...

return this;

}

public ResponseActions expectHeader(String name) {

...

return this;

}

}

Static factory Version

Mock mock = new Mock();

mock.whenConnecting()

.expect(value("a")).and()

.expect(header("header")).andRespond(withValue("b"));

public interface RequestExpectations {

ResponseActions expect(RequestMatcher matcher);

}

public interface ResponseActions {

RequestExpectations and();

void andRespond(ResponseCallback callback);

}

public class Mock {

public RequestExpectations whenConnecting() {

return new MockInternal();

}

}

class MockInternal implements RequestExpectations, ResponseActions{

public RequestExpectations and() {

...

return this;

}

public void andRespond(ResponseCallback callback) { ... }

public ResponseActions expect(RequestMatcher matcher) {

...

return this;

}

}

public class StaticFactory {

public static RequestMatcher value(String value) {

return new RequestMatcher() {

public void match(Object someParams) { ... }

};

}

public static RequestMatcher header(String value) {

return new RequestMatcher() {

public void match(Object someParams) { ... }

};

}

public static ResponseCallback withValue(String value) {

return new ResponseCallback() {

public void doWithResponse(Object someParams) { ... }

};

}

public static ResponseCallback withError(String value) {

return new ResponseCallback() {

public void doWithResponse(Object someParams) { ... }

};

}

}

Super static version

expect(value("a")).andExpect(header("header")).andRespond(withValue("b"));

public interface ResponseActions {

ResponseActions andExpect(RequestMatcher matcher);

void andRespond(ResponseCallback callback);

}

class MockInternal implements ResponseActions{

public ResponseActions andExpect(RequestMatcher matcher) {

...

return this;

}

public void andRespond(ResponseCallback callback) { ... }

}

public class StaticFactory {

public static ResponseActions expect(RequestMatcher matcher) {

MockInternal mock = new MockInternal();

mock.andExpect(matcher);

return mock;

}


public static RequestMatcher value(String value) {

return new RequestMatcher() {

public void match(Object someParams) { ... }

};

}

...

}

Inheritance version

public class MockTest extends MockFactory{

@Test

public void testMock() {

expect(value("a"))

.andExpect(header("header")).andRespond(withValue("b"));

}

}

あるいは

public class MockTest{

@Test

public void testMock() {

MockFactory mockFactory = new MockFactory() {

protected void configure() {

expect(value("a"))

.andExpect(header("header")).andRespond(withValue("b"));

};

};

}

}

public interface ResponseActions {

ResponseActions andExpect(RequestMatcher matcher);

void andRespond(ResponseCallback callback);

}

class MockInternal implements ResponseActions{

public ResponseActions andExpect(RequestMatcher matcher) {

...

return this;

}

public void andRespond(ResponseCallback callback) { ... }

}

public class MockFactory {

public MockFactory() {

configure();

}


protected void configure() {

}


public ResponseActions expect(RequestMatcher matcher) {

MockInternal mock = new MockInternal();

mock.andExpect(matcher);

return mock;

}


public RequestMatcher value(String value) {

return new RequestMatcher() {

public void match(Object someParams) { ... }

};

}

...

}

★実際の応用

jOOQ

http://www.jooq.org/

create().select(

r1.ROUTINE_NAME,

r1.SPECIFIC_NAME,

decode()

.when(exists(create()

.selectOne()

.from(PARAMETERS)

.where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))

.and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))

.and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),

val("void"))

.otherwise(r1.DATA_TYPE).as("data_type"),

r1.NUMERIC_PRECISION,

r1.NUMERIC_SCALE,

r1.TYPE_UDT_NAME,

decode().when(

exists(

create().selectOne()

.from(r2)

.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))

.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))

.and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),

create().select(count())

.from(r2)

.where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))

.and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))

.and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())

.as("overload"))

.from(r1)

.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))

.orderBy(r1.ROUTINE_NAME.asc())

.fetch();

SQL vs jOOQ

SELECT FIRST_NAME, LAST_NAME, COUNT(*)

FROM AUTHOR

JOIN BOOK ON AUTHOR.ID = BOOK.AUTHOR_ID

WHERE LANGUAGE = 'DE'

AND PUBLISHED > '2014-04-20'

GROUP BY FIRST_NAME, LAST_NAME

HAVING COUNT(*) > 5

ORDER BY LAST_NAME ASC NULLS FIRST

LIMIT 2

OFFSET 1

FOR UPDATE

OF FIRST_NAME, LAST_NAME

create.select(FIRST_NAME, LAST_NAME, create.count())

.from(AUTHOR)

.join(BOOK).on(Author.ID.equal(Book.AUTHOR_ID))

.where(LANGUAGE.equal("DE"))

.and(PUBLISHED.greaterThan(parseDate('2014-04-20')))

.groupBy(FIRST_NAME, LAST_NAME)

.having(create.count().greaterThan(5))

.orderBy(LAST_NAME.asc().nullsFirst())

.limit(2)

.offset(1)

.forUpdate()

.of(FIRST_NAME, LAST_NAME);

jRTF

https://code.google.com/p/jrtf/

rtf()

.header(

color( 0xff, 0, 0 ).at( 0 ),

color( 0, 0xff, 0 ).at( 1 ),

color( 0, 0, 0xff ).at( 2 ),

font( "Calibri" ).at( 0 ) )

.section(

p( font( 1, "Second paragraph" ) ),

p( color( 1, "green" ) )

)

).out( out );

Apache Camel

https://camel.apache.org/

http://sourceforge.jp/projects/cameluserjp/wiki/FrontPage

RouteBuilder builder = new RouteBuilder() {

public void configure() {

errorHandler(deadLetterChannel("mock:error"));

from("seda:a")

.choice()

.when(header("foo").isEqualTo("xxx"))

.to("seda:b")

.when(header("foo").isEqualTo("yyy"))

.to("seda:c")

.otherwise()

.to("seda:d");

}

};