Skip to content

Instantly share code, notes, and snippets.

@yytyd
Last active March 16, 2019 07:40
Show Gist options
  • Save yytyd/4ba31dfa17739d732f8dd7fd4938d42c to your computer and use it in GitHub Desktop.
Save yytyd/4ba31dfa17739d732f8dd7fd4938d42c to your computer and use it in GitHub Desktop.
Java で,幽霊型を使って builder を書く??
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());
}
}
package com.github.yytyd.status;
public interface BuildStatus {
}
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);
}
}
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();
}
}
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というメソッドが生えるイメージ
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;
}
}
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 ???; // 何を返すかも結構問題.あと値のコピーも.
}
}
package com.github.yytyd.status;
public class Progress implements BuildStatus {
public Progress() {}
}
package com.github.yytyd.status;
public class Ready implements BuildStatus {
public Ready() {}
}
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