4647


Java で以下のようなことを簡潔に記述可能でしょうか。  
稚拙な質問ですが、慣習等あればご回答いただけたらと思います。  
よろしくお願いいたします。

[code]

    class A {
      B b;
      A() {
        this.b = new B();
      }
      // 以下、b を使ったメソッドなど
    }
    
    class B {}
    
    
    class A' extends A {
      // b を使ったメソッドの動作はそのままに b を B' のインスタンスとしたい
    }
    
    class B' extends B {}
    
[/code]

自分で考えたのは、以下のようなコードです。  
そもそもクラス A のメンバにクラス B のインスタンスを生成せず、クラス A を継承した A' でメンバ変数を付加する形です。  
もちろん、B' で拡張された実装を利用する場合は b を B' でキャストする必要があります。

[code]

    class A {
      B b;
      A() {
      }
      // 以下、b を使ったメソッドなど
    }
    
    class B {}
    
    
    class A' extends A {
      A'() {
        this.b = new B'();
      }
      // b を使ったメソッドの動作はそのまま
      // B' で拡張された実装を利用する場合は b を B' でキャストする
    }
    
    class B' extends B {}
    
[/code]


CC BY-SA 3.0
2015-01-15T17:31:42.300
4642
2015-01-16T05:55:16.070
3132
question
java
継承したクラスのメンバ変数を差し替えたい


abstract class を使ってコンパウンド実装してみてはどうでしょうか？

[code]

    abstract class Animal {
        public void Sing() {
            System.out.println("うー");
        }
    
        public String getKind() {
            return "動物";
        }
    
        public String toString() {
            return "動物";
        }
    }
    
    class Cat extends Animal {
        public void Sing() {
            System.out.println("にゃー");
        }
    
        // getKind() は実装しない
    
        public String toString() {
            return "ぬこ";
        }
    }
    
    class Controller {
        private Animal animal;
        public Controller(Animal a) {
            animal = a;
        }
    
        public void Sing() {
            animal.Sing();
        }
    
        public String getKind() {
            return animal.getKind();
        }
    
        public String toString() {
            return animal.toString();
        }
    }
    
    public class foo {
        public static void main(String[] args) {
            Controller c = new Controller(new Cat());
            c.Sing(); // にゃー
            System.out.println(c.getKind()); // 動物
            System.out.println(c.toString()); // ぬこ
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-15T17:52:35.177
4643
2015-01-15T17:52:35.177
440
4642
answer


別解として、ジェネリクスを使った場合は下記の様な感じになるかと思います。

**クラス継承**

[code]

    B1
     └─ B2
    
    AbstractA <T extends B1>
     ├─ A1 <B1>
     └─ A2 <B2>
    
[/code]

**_コード_**

[code]

    class B1 {
        public B1() {}
    
        public void method1() {}
    }
    
    class B2 extends B1 {
        public B2() {}
    
        @Override
        public void method1() {}
    
        public void method2() {}
    }
    
    abstract class AbstractA<T extends B1> {
    
        protected final T b;
    
        public AbstractA(final T b) {
            this.b = b;
        }
    }
    
    class A1 extends AbstractA<B1> {
    
        public A1() {
            super(new B1());
    
            b.method1();
        }
    }
    
    class A2 extends AbstractA<B2> {
    
        public A2() {
            super(new B2());
    
            b.method1();
            b.method2();
        }
    }
    
[/code]

この場合、気を付けないと下記の様な意図していないコードが書けてしまいます。

[code]

    class A extends AbstractA<B1> { // ← B1 を指定
    
        public A() {
            // B2 のインスタンスを渡してしまっている！
            super(new B2());
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-15T18:32:45.470
4644
2015-01-15T19:03:11.943
2015-01-15T19:03:11.943
3068
3068
4642
answer


まずは、質問のコードを少し変えて、変数の中身を指定させる `protected` なコンストラクタを設け、以下のようにした方がいいですね。

Q2322_1.java

[code]

    class A {
      private final B b;
    
      protected A( B b) {
        this.b = b;
      }
    
      public A() {
        this( new B());
      }
    
      public B getB() { return b; }
    }
    
    class B {}
    
    class A_Dash extends A {
      public A_Dash() {
        super( new B_Dash());
      }
    
      @Override
      public B_Dash getB() { return (B_Dash)super.getB(); }
    }
    
    class B_Dash extends B {
      public String getDashness() { return "daaaaaah.";}
    }
    
    public class Q2322_1 {
      public static void main(String argv[]) {
        System.out.println( new A_Dash().getB().getDashness());
      }
    }
    
[/code]

[code]

    $javac Q2322_1.java
    $java -cp . Q2322_1
    daaaaaah.
    
[/code]

上記の書き方はわかりやすいですが、無駄があります。この形から、ジェネリクスを使うようにしてください。そうするとコードが少なくなる上、適切な制約を拡張クラスの実装者に押し付けられ、より型安全になります。

[code]

    class A_Proto<T extends B> {
      private final T b;
    
      protected A_Proto( T b) {
        this.b = b;
      }
    
      public T getB() { return b; }
    }
    
    class A extends A_Proto<B> {
      public A() {
        super( new B());
      }
    }
    
    class B {}
    
    class A_Dash extends A_Proto<B_Dash> {
      public A_Dash() {
        super( new B_Dash());
      }
    }
    
    class B_Dash extends B {
        public String getDashness() { return "daaaaaah."; }
    }
    
    public class Q2322_2 {
      public static void main(String argv[]) {
        System.out.println( new A_Dash().getB().getDashness());
      }
    }
    
[/code]

[code]

    $javac Q2322_2.java
    $java -cp . Q2322_2
    daaaaaah.
    
[/code]

## 追記:

ところで、コンストラクタの引数を増やさない設計もできます。 `A_Proto` を以下のようにしてください。

[code]

    abstract class A_Proto<T extends B> {
      private final T b;
      protected A_Proto() {
        this.b = newB();
      }
      protected abstract T newB();
      public final T getB() { return b; };
    }
    
    class A extends A_Proto<B> {
      @Override protected B newB() { return new B(); }
    }
    
[/code]

`A` の実装者は `newB()` をオーバーライドします。コンストラクタが抜けるのでさらにシンプルになりました。

## 追記2:

そしてもう一つ、脱線になると思いますが、抽象化されたパラメータを持つクラスをさらに継承する場合は以下のようにします。

[code]

    class B {}
    class C {}
    
    abstract class A_Proto<T extends B> {
      private final T b;
      public A_Proto() {
        this.b = newB();
      }
      protected abstract T newB();
      public final T getB() { return b; };
    }
    
    abstract class Z_Proto<T extends B, T2 extends C> extends A_Proto<T> {
      private final T2 c;
      public Z_Proto() {
        this.c = newC();
      }
      protected abstract T2 newC();
      public final T2 getC() { return c; };
    }
    
[/code]

うわあ。こんなんいやですね。はい。だから Java は嫌いなんだよ？はい。これを継承するクラスは以下のようになります。

[code]

    class B2 extends B {}
    class C2 extends C {}
    
    class A extends Z_Proto<B2, C2> {
      @Override protected B2 newB() { return new B2(); }
      @Override protected C2 newC() { return new C2(); }
    }
    
[/code]

`A` の実装が随分簡単になのに、 `getB()`, `getC()` の返す型は `B2`, `C2` になって大きなことをした気分になります。確かに
`A` の上の実装はすこし複雑でしたが、 `A` の利用者も実装者も、楽ができています。

ここまでの説明で覚えてほしいことがあります。それは、これらの例は「機能の引き継ぎを目的とした継承ではない」ことです。 `A` クラスは定義されましたが、
「`A_Proto` クラスで **パラメータ化された型情報を固める** 」 ためだけに継承が使用されたのです。

言い直すと、`getB()` が `B2` を返すという設計が `A`
クラスの定義で固められました。固まった設計は元に戻す方法がなく、継承を使って型定義の変更はできません。

この `A` クラスは `A_Proto` に対する **コンクリートクラス**
と呼んでください。そして、普通の継承とは別に扱うようにしてください。これが使いこなせると、ジェネリクスで応用的なことをするとき致命的な損失を回避できます。


CC BY-SA 3.0
2015-01-15T22:59:04.450
4647
2015-01-16T05:55:16.070
2015-01-16T05:55:16.070
4978
4978
4642
answer