拡張可能なクラス-拡張可能なビルダー!

最近、私は一見したところよりもはるかにささいなことが判明したタスクに出会いました。 ビルダーが存在するクラス(この例では不変)があります。 祖先のビルダーから継承されたビルダーを提供することにより、このクラスから継承できる必要があります。 カットの下で、私の考えの経過、失敗したオプション、問題の最終的な解決策を示します。



明らかなことから始めましょう

コード内の問題の条件を形式化します。

public class ImmutableBase { private ImmutableBase(Builder builder) { // ... } public static class Builder { public Builder setSomeString(String value) { // ... return this; } public Builder setSomeInt(int value) { // ... return this; } public ImmutableBase build() { return new ImmutableBase(this); } } }
      
      





ここで、ImmutableBaseを継承するクラスを作成する必要があります。 ImmutableBaseコンストラクターのアクセス修飾子をprotectedに変更することを忘れずに、「正面」アプローチを試してみましょう。



 public class MyImmutable extends ImmutableBase { protected MyImmutable(Builder builder) { super(builder); // do more things } public static class Builder extends ImmutableBase.Builder { public Builder setSomeDouble(double value) { // ... return this; } @Override public MyImmutable build() { return new MyImmutable(this); } } public static void main(String[] args) { Builder builder = new Builder(); MyImmutable myImmutable = builder.setSomeDouble(0.0). setSomeInt(0).setSomeString("0").build(); } }
      
      





一見したところ、すべてが順調です。 ただし、ビルダーのメソッドの呼び出しを交換するだけで、悲しみがどのように私たちに降りかかるか:



 MyImmutable.java:20: cannot find symbol symbol : method setSomeDouble(double) location: class ImmutableBase.Builder setSomeInt(0).setSomeDouble(0.0).build(); ^
      
      







実際、ImmutableBase.Builderで定義されたメソッドは、その子孫のインスタンスではなく、そのインスタンスを返します。



二回目



MyImmutable.Builderで彼の祖先のすべてのメソッドをオーバーライドするという考えをすぐに却下し、考え始めます。 魅力的なオプションはセキュリティに唾を吐き、次のようなことをするようです



 public <B extends Builder> B setSomeString(String value) { // ... return (B) this; }
      
      





しかし、幸いなことに(安全に唾を吐くものはありません!)、この解決策は機能せず、すべて同じエラーが発生します。 事実、この場合、javaはそれ自体が使用方法に基づいて型を推測できないため、制限に基づいて可能な限り狭い型を選択します。 Builderを拡張したため、この型はImmutableBase.Builderです。 これを書かないと、Objectになります。 次のように記述することで、コンパイラが待機しているタイプを回避策として伝えることができます。



 MyImmutable myImmutable = (MyImmutable) builder.setSomeString("0"). <MyImmutable.Builder>setSomeInt(0).setSomeDouble(0.0).build();
      
      





しかし、あなたは認めなければなりません、それは図書館利用者にとって便利ではないでしょう。



すべてのジェネリックは私たちのものです!



ジェネリックのアイデアはおそらく正しかったので、この方向をもう少し考えれば、この例で次の解決策を得ることができます:



 public static class Builder<B extends Builder<?>> { public B setSomeString(String value) { // ... return (B) this; } public B setSomeInt(int value) { // ... return (B) this; } public ImmutableBase build() { return new ImmutableBase(this); } }
      
      







ただし、ここでも最終的な幸福には達していません。タイプセーフではないものがあり、エラーを再度取得するのは非常に簡単です。

 public static void main(String[] args) { ImmutableBase.Builder<Builder> builder = new ImmutableBase.Builder<Builder>(); MyImmutable myImmutable = builder.setSomeString("0"). setSomeInt(0).setSomeDouble(0.0).build(); }
      
      





起動時に



 Exception in thread "main" java.lang.ClassCastException: ImmutableBase$Builder cannot be cast to MyImmutable$Builder at MyImmutable.main(MyImmutable.java:24)
      
      





ユーザーが何もキャストしなかったという事実を考えると、コンパイラーがジェネリックを処理するときに、必要なキャスト自体を挿入することを忘れた人たちの間で混乱を引き起こす可能性があります。 ファイルは再び、致命的に見えます。問題は、MyImmutable.BuilderがImmutableBase.Builderのサブクラスであり、条件によってこれを取り除くことができないことです。



帝国の逆襲

しかし、心を失うことはありません! 少し洞察を示して、ユーザーに突き出ているクラスがMyImmutableから継承するクラスと一致する必要はないと推測するだけで十分です。 したがって、これを行います。



 public class ImmutableBase { protected ImmutableBase(InnerBuilder<?> builder) { // ... } protected static class InnerBuilder<B extends InnerBuilder<B>> { public B setSomeString(String value) { // ... return(B) this; } public B setSomeInt(int value) { // ... return (B)this; } public ImmutableBase build() { return new ImmutableBase(this); } } public static class Builder extends InnerBuilder<Builder> {} }
      
      







したがって、InnerBuilderクラスがありますが、これはユーザーには表示されず、ユーザーは彼と一緒に汚い詐欺を行うことはできません。 そして、最初から単純に継承するBuilderクラスがあり、実装の詳細(つまりジェネリック)をユーザーから隠します。 その後、次のようにMyImmutableでBuilderを宣言するだけで十分です。

 public static class Builder extends ImmutableBase.InnerBuilder<Builder> { public Builder setSomeDouble(double value) { // ... return this; } @Override public MyImmutable build() { return new MyImmutable(this); } }
      
      





タスクが最終的に解決されるため、人生を楽しみます。 念のため、次の2つの主なアイデアを思い出します。



このメソッドを使用すると、より深い継承を簡単に実行できることを理解するのは簡単ですが、これを行うことを計画している場合は、おそらくリファクタリングを検討する必要があります。



これが時間の節約になることを願っています。 頑張って!



All Articles