Last active
March 16, 2019 07:40
-
-
Save yytyd/4ba31dfa17739d732f8dd7fd4938d42c to your computer and use it in GitHub Desktop.
Java で,幽霊型を使って builder を書く??
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.builder; | |
import com.github.yytyd.status.BuildStatus; | |
// 幽霊型を用いずに素直に実装していくパターン. | |
public abstract class Builder<T extends BuildStatus> { | |
protected final StringBuilder builder; | |
public Builder(StringBuilder builder) { | |
this.builder = builder; | |
} | |
public static InitializedBuilder start() { | |
return new InitializedBuilder(new StringBuilder()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.status; | |
public interface BuildStatus { | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.builder; | |
import com.github.yytyd.status.Progress; | |
import java.util.Arrays; | |
public class InitializedBuilder extends Builder<Progress> { | |
public InitializedBuilder(StringBuilder builder) { | |
super(builder); | |
} | |
public InitializedBuilder buildTitle(String title) { | |
super.builder.append("==============================\n"); | |
super.builder.append("『 ").append("title").append(" 』\n"); | |
super.builder.append("\n"); | |
return this; | |
} | |
public InitializedBuilder buildString(String str) { | |
super.builder.append("■ ").append(str).append("\n"); | |
super.builder.append("\n"); | |
return this; | |
} | |
public InitializedBuilder buildItems(String[] items) { | |
Arrays.stream(items).forEachOrdered(item -> super.builder.append(item).append("\n")); | |
super.builder.append("\n"); | |
return this; | |
} | |
public ReadyBuilder buildDone() { | |
builder.append("==============================\n"); | |
return new ReadyBuilder(builder); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd; | |
import com.github.yytyd.builder.Builder; | |
import com.github.yytyd.builder.InitializedBuilder; | |
import com.github.yytyd.builder.ReadyBuilder; | |
// 例.コンパイルエラーの出る箇所があります. | |
public class Main { | |
public static void main(String... args) { | |
// こういうふうにワンライナーで書けるとめっちゃきれい | |
Builder.start().buildTitle("title").buildString("moji").buildItems(new String[] {"a", "b", "c"}).buildDone().getResult(); | |
// #getResult は #buildDone が呼ばれないと呼び出せない | |
Builder.start().buildTitle("title").getResult(); | |
// #getResult は #start の直後には呼び出せない | |
Builder.start().getResult(); | |
// 型がこんなふうに煩雑になるのでこういう実装は悩ましくはある. | |
// 型推論の効く言語なら (Java のバージョンを上げればいける),var builder とかできるのでいいと思う. | |
InitializedBuilder builder = Builder.start(); | |
ReadyBuilder ready = builder.buildTitle("title").buildString("moji").buildDone(); | |
String result = ready.getResult(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.builder; | |
import com.github.yytyd.context.PhantomData; | |
import com.github.yytyd.status.BuildStatus; | |
import com.github.yytyd.status.Initial; | |
// 幽霊型を使うパターン | |
public abstract class PhantomBuilder<A extends BuildStatus, B extends BuildStatus, C extends BuildStatus> { | |
protected PhantomData<String, A> title; | |
protected PhantomData<String, B> str; | |
protected PhantomData<String[], C> items; | |
public static PhantomTypedTitleBuilder start() { | |
return new PhantomTypedTitleBuilder(); | |
} | |
} | |
// A, B, C の状態に応じて個別にクラスを用意すれば実装可能だと思う | |
// たとえば | |
// class TitleBuilder extends PhantomBuilder<Initial, Initial, Initial> | |
// タイトルを入れると PhantomBuilder<Ready, Initial, Initial> になり | |
// class StrBuilder extends PhantomBuilder<Ready, Initial, Initial> | |
// class ItemsBuilder extends PhantomBuilder<Ready, Ready, Initial> | |
// ↑この時点で buildDone が生える?? | |
// class Build extends PhantomBuilder<Ready, Ready, Ready> | |
// ↑になったら,getResultというメソッドが生えるイメージ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.context; | |
import com.github.yytyd.status.BuildStatus; | |
// X 側に実体となる値が入る. | |
// S 側に現在の状態が入る. | |
// S はコンパイルタイムで型消去される. | |
// S がいわゆる幽霊型 | |
public class PhantomData<X, S extends BuildStatus> { | |
private final X value; | |
private final S state; | |
// ちなみに完全な幽霊型はできなそう. | |
// public PhantomData<S>(X value) みたいな書き方ができればイケたが, | |
// 残念ながら仕様上多分無理.このように一度変数を確保しないといけないので厳しい. | |
// コンストラクタの中で state を使わないようにすれば(フィールドにいる state を消せば) | |
// 多分効率良くはなるけど,Java 使い的にはそれも NG だしな… | |
public PhantomData(X value, S state) { | |
this.value = value; | |
this.state = state; | |
} | |
public X getValue() { | |
return value; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.builder; | |
import com.github.yytyd.context.PhantomData; | |
import com.github.yytyd.status.Initial; | |
import com.github.yytyd.status.Ready; | |
public class PhantomTypedTitleBuilder extends PhantomBuilder<Initial, Initial, Initial> { | |
public PhantomBuilder<Ready, Initial, Initial> buildTitle(String title) { | |
super.title = new PhantomData(title, new Ready()); | |
return ???; // 何を返すかも結構問題.あと値のコピーも. | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.status; | |
public class Progress implements BuildStatus { | |
public Progress() {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.status; | |
public class Ready implements BuildStatus { | |
public Ready() {} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.github.yytyd.builder; | |
import com.github.yytyd.status.Ready; | |
public class ReadyBuilder extends Builder<Ready> { | |
public ReadyBuilder(StringBuilder builder) { | |
super(builder); | |
} | |
public String getResult() { | |
return super.builder.toString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment