- PR -

クラス設計(継承に関して)

投稿者投稿内容
Cluster
ぬし
会議室デビュー日: 2003/03/06
投稿数: 289
お住まい・勤務地: 大阪
投稿日時: 2004-03-05 22:01
Javaのクラス設計で悩んでいます。
GoFのデザインパターンとかも少し見てみたのですが、
ピッタリ来るのが無いようです。(理解できてないだけ?
なにかいいアイデアは無いでしょうか?

やりたいことは、こんな感じです。(変な例えなのはご勘弁 )

  1. AnimalクラスとBirdクラスがあり、BirdクラスはAnimalクラスのサブクラスである。
  2. ArmクラスとWingクラスがあり、WingクラスはArmクラスのサブクラスである。
  3. Animalクラスは、インスタンス変数としてArmクラスのインスタンスを持つ。
  4. Birdクラスは、インスタンス変数としてWingクラスのインスタンスを持つ。


これを単純にコードにすると、こうなると思うのですが、
コード:
public class Animal {
    private Arm myArm;

    public Arm getArm() {
        return myArm;
    }

    public void setArm(arm) {
        myArm = arm;
    }
}

public class Bird extends Animal{
    private Wing myWing;

    public Wing getArm() {
        return myWing;
    }

    public void setArm(wing) {
        myWing = wing;
    }
}

public class Arm {
(略)
}

public class Wing extends Arm {
(略)
}




これだと、BirdクラスのgetArmがコンパイルエラーになります。
(オーバーライドしてるメソッドのreturnするクラスが違うので)

BirdクラスのgetArmmメソッドを、
コード:
public Arm getArm() 


とすればいいんですが、BirdクラスはWingクラスしか返さないので
明示的にWingクラスを返すようにしたいと考えています。
(getArmを呼び出した側でキャストするのが面倒だから、というのが理由 )

こういうものを実現したい場合、なにかいいデザインの仕方が無いものでしょうか?
山本 裕介
ぬし
会議室デビュー日: 2003/05/22
投稿数: 2415
お住まい・勤務地: 恵比寿
投稿日時: 2004-03-05 22:09
このクラスを扱うクライアントはどんなイメージですか?
Animal クラスを扱いたいのに、インターフェースにWing 型が含まれるのは妙な気がしますが。
ぽん
大ベテラン
会議室デビュー日: 2003/05/13
投稿数: 157
投稿日時: 2004-03-05 22:33
引用:

とすればいいんですが、BirdクラスはWingクラスしか返さないので
明示的にWingクラスを返すようにしたいと考えています。
(getArmを呼び出した側でキャストするのが面倒だから、というのが理由 )

こういうものを実現したい場合、なにかいいデザインの仕方が無いものでしょうか?


getWing()関数をBirdクラスに追加すれば良いのでは?
コード:
public class Animal {
    private final Arm myArm;

    public Animal() {
        this(new Arm());
    }

    protected Animal(final Arm myArm) {
        this.myArm = myArm;
    }

    public Arm getArm() {
        return myArm;
    }

}

public class Bird extends Animal{
    public Bird() {
        super(new Wing());
    }

    public Wing getWing() {
        return (Wing)getArm();
    }
}

unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2004-03-05 23:03
unibon です。こんにちわ。

引用:

Clusterさんの書き込み (2004-03-05 22:01) より:
やりたいことは、こんな感じです。(変な例えなのはご勘弁 )


突っ込むなと書かれているところに突っ込んで申し訳ありません。
たしかに Animal クラスは例としてきわめてメジャーなものですが、抽象的すぎるので例としてはふさわしくない、というか Wing の必然性が思い浮かばないので、もっと実際に悩まれた場面で登場したクラス構成で説明されたほうが良いかもしれません。
#でも、人に説明するときは Animal や Car にしたくなりますよね。私も他の例が思い付きません。

引用:

Clusterさんの書き込み (2004-03-05 22:01) より:
コード:

public class Bird extends Animal{
private Wing myWing;

public Wing getArm() {
return myWing;
}

public void setArm(wing) {
myWing = wing;
}
}




これだと、Bird クラスは、
private Arm myArm;
private Wing myWing;
の2つのフィールドを持つことになり、冗長さがあり、好ましくないです。

また、思うに、おっしゃることが解決できるようであれば、Java のダウンキャストは一掃できてしまうような気がします。だから、おっしゃることはできないのではないでしょうか。
あるいは Generics が使えるかも?

#書いた後すぐ、ちょびっとだけ修正。

[ メッセージ編集済み 編集者: unibon 編集日時 2004-03-05 23:05 ]
かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2004-03-06 16:56
継承を考える場合、クラスの振る舞いを基点に共通性を見出します。

今回のケースでは、おそらくJava-Beansタイプのクラスを設計しようとしていると思われますが、例示された2つのクラス間に共通性を見出すことができないように思われるため、継承を使用せず、それぞれ単独クラスとして設計した方がよいと思われます。

もし共通の振る舞いがあるのなら(例えばmove()かな...)、その振る舞いを定義したインターフェースを派生させるように設計します。

具体的には
コード:
initerface Movable {
  public void move();
}

class Animal implements Movable {
  public void move() {
    ....
  }
}

class Bird implements Movable {
  public void move() {
    ....
  }
}


とします。

振る舞いを無視して、羽は腕の一種だからと、腕を継承させた羽を設計すれば今回のように破綻を招きます。
かずくん
ぬし
会議室デビュー日: 2003/01/08
投稿数: 759
お住まい・勤務地: 太陽系第三惑星
投稿日時: 2004-03-06 17:02
C++では、返値が同一の継承階層であれば、別のクラス返すようにオーバーライドすることができます。
即ち、今回例示したままのスタイルで、設計することができます。

この仕様、C++以外にも導入されるという話を以前何かで見たような気がしたけど、Javaだったっけ?
それともC#?
御存知の方、フォローをお願い致します。
Cluster
ぬし
会議室デビュー日: 2003/03/06
投稿数: 289
お住まい・勤務地: 大阪
投稿日時: 2004-03-08 01:54
こんにちは、Clusterです。

みなさん、ご意見ありがとうございます。
やっぱり例えがあんまり良くなかったみたいなので、ちょっと変えてみます。


  1. AnimalクラスとHumanクラスがあり、HumanクラスはAnimalクラスのサブクラスである。
  2. HandクラスとHumanHandクラスがあり、HumanHandクラスはHandクラスのサブクラスである。
  3. Animalクラスは、インスタンス変数としてHandクラスのインスタンスを持つ。
  4. Humanクラスは、インスタンス変数としてHumanHandクラスのインスタンスを持つ。
  5. HumanHandクラスは、Thumクラスのインスタンスを持つ。 ←ここが前回との主な相違点


で、コードですが、
コード:
 

public class Animal {
private Hand myHand;

public Hand getHand() {
return myHand;
}

public void setHand(hand) {
myHand = hand;
}
}

public class Human extends Animal{
private HumanHand myHumanHand;

public HumanHand getHand() {
return myHumanHand;
}

public void setHand(HumanHand humanHand) {
myHumanHand = humanHand;
}
}

public class Hand {

}

public class HumanHand extends Hand {

private Thum myThum;

public Thum getThum() {
return myThum;
}

public void setThum(Thum thum) {
myThum = thum;
}

}


みたいに書ければ(実際にはコンパイルエラーになりますが)

コード:

Human myHuman = new Human();
Thum myThum = null;

myThum = myHuman.getHand().getThum();


みたいに書けていいなぁって・・・。

実際には、HumanクラスのgetHand()メソッドのリターンはHandクラスになるので、
コード:

Human myHuman = new Human();
Thum myThum = null;

myThum = ( (HumanHand) myHuman.getHand() ).getThum();


と書かなくちゃいけなくて、冗長でいやだなぁっていうのが動機です。
# 人間の手に親指があるのは分かってるんだから、いちいちキャストしなくても・・・
# 横着すぎるかな?(笑)

Javaの文法に文句つけても仕方ないので、
「こういうときにはこうデザインすればいいよ〜」
みたいな意見を聞けたらなぁって考えたんですが、難しいみたいですね。


[ メッセージ編集済み 編集者: Cluster 編集日時 2004-03-08 01:56 ]

[ メッセージ編集済み 編集者: Cluster 編集日時 2004-03-08 02:13 ]
ねこばば
会議室デビュー日: 2004/01/19
投稿数: 8
投稿日時: 2004-03-08 02:55
こんにちは、ねこばばといいます。

このパターンは、僕も仕事で遭遇しました。
クラスの機能を継承できるという利便性を元に考えているのであれば、
コードを減らす為に、Armクラスを型にして、戻り値の値を拾い、
メソッドは用意してあるgetArmメソッドを使用して、似たようなメソッドを追加しない。
返されるオブジェクトの中身は、AnimalかHuman、もうひとつは
HandかHumanHandになりますが、instanceofなどで判別できるので、問題ないかと。

ソースが見づらくてもJavaVMにとって何の懸念もないと思います。
ソースを見やすくして、中のオブジェクトはinstanceofで判別して、
AnimalクラスとArmクラスでコードを記述するか、どちらかにポリシーを
持つ事をお勧めします。

スキルアップ/キャリアアップ(JOB@IT)