質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

引数によって異なるサブクラスのインスタンスを返したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 109

fsk5303

score 19

前提

  • 引数によって異なるサブクラスのインスタンスを返したい
  • もしくはその他の方法で下記要件を満たしたい
  • より良いクラス設計を学びたい

該当のソースコード

  • 下記のようなXMLを入力とします。
  • XMLのスキーマはname, detailまでは共通ですが、detailより下の階層はnameの中身によって変わります。
  • 実際に実現したいプログラムで使用するXMLのスキーマはもっと複雑ですが例として簡単にしています。

  • XML例1
<sports>
    <name>
        baseball
    </name>
    <detail>
        <items>
            <item>
                ball
            </item>
            <item>
                bat
            </item>
            <item>
                glove
            </item>
        </items>
        <swing>
            double play....
        </swing>
    </detail>
</sports>
  • XML例2
<sports>
    <name>
        soccer
    </name>
    <detail>
        <positions>
            <position>
                offence
            </position>
            <position>
                diffence
            </position>
            <position>
                midfielder
            </position>
        </positions>
        <shoot>
            goal!!!
        </shoot>
    </detail>
</sports>

  • 各XMLの共通部分をスーパークラスとして定義しています。
  • 各XML独自の部分をサブクラスとして定義しています。
  • XML文字列を解析するXMLUtilクラスは省略しています。

  • Sportクラス
public class Sport {
    protected String name;
    public Sport(String xml) {
        // baseball / soccer
        name = XmlUtil.get(xml, "name");
    }
    public String getName() {
        return name;
    }
}
  • Baseballクラス
public class Baseball extends Sport {
    private List<String> itemList;
    private String swingResult;
    public Baseball(String xml) {
        super(xml);

        // [ball, bat, glove]
        itemList = XmlUtil.getList(xml, "items");
        // "double play..."
        swingResult = XMlUtil.get(xml, "swing");
    }
    public List<String >getItemList() {
        return itemList;
    }
    public void swing() {
        System.out.println(swingResult);
    }
}
  • Soccerクラス
public class Soccer extends Sport {
    private List<String> positionList;
    private String shootResult;
    public Soccer(String xml) {
        super(xml);

        // [offence, diffence, midfielder]
        positionList = XmlUtil.getList(xml, "positions");
        // "goal!!!"
        shootResult = XMlUtil.get(xml, "shoot");
    }
    public List<String >getPositionList() {
        return positionList;
    }
    public void shoot() {
        System.out.println(shootResult);
    }
}

  • 呼び出し元クラスではXMLの中身に応じたサブクラスのインスタンスを生成し、それぞれ独自のメソッドを呼び出したいです。
  • このクラスではXmlUtilクラスのメソッドは呼び出したくないです。
  • 下記方法だと、new Sport(xml);new Baseball(xml);で二回スーパークラスのコンストラクタが呼び出されていると思います。そのため、とても冗長に感じています。

  • 呼び出し元クラス
public void playSport(String xml) {

    Sport sport = new Sport(xml);

    if("baseball".equals(sport.getName())) {
        Baseball baseball = new Baseball (xml);
        baseball.swing();
    } else if ("soccer".equals(sport.getName())) {
        Soccer soccer = new Soccer(xml);
        soccer.shoot();
    }

}

実現したいこと

  • 以下のようなイメージで一度でサブクラスを作成し処理を行うことはできないでしょうか?
  • このクラスではXML文字列の中身を解析したくはないです。
public void newPlaySport(String xml) {

    Sport sport = new Sport(xml);

    if(sport instanceof Baseball) {
        Baseball baseball = (Baseball)sport;
        baseball.swing();
    } else if (sport instanceof Soccer) {
        Soccer soccer = (Soccer)sport;
        soccer.shoot();
    }

}

ジェネリクスや抽象クラス等を組み合わせてやるとよいのか、あるいはファクトリクラスのようなものを定義するのがよいのか、いろいろ考えてみたのですが難しいです。
なにか、もっとこうした方がよい、私ならこうする、というようなアドバイスいただけると幸いです。
XMLのスキーマの設計等は変更はできません。

よろしくお願いいたします。

補足情報(FW/ツールのバージョンなど)

Java 1.8

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

以下のようなイメージで一度でサブクラスを作成し処理を行うことはできないでしょうか?

Javaの場合、new ClassNameと書いたものは本当にそのクラスしか作成できません。なので、Factory Methodを使う必要があります。

Sport Sport.fromXML(String xml)のようなメソッドを立てて、その内部でxmlを見て実際に作るクラスを作り分ければ、外部からはまとめて使えます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/08/29 17:37

    早速のご回答ありがとうございます。やはりそのようなメソッドを作成する必要があるのですね。
    少し追加で聞かせていただきたいのですが、

    ・ファクトリメソッドを使う場合、Sportクラスに定義するのが好ましいですか?それともSportFactoryのような別のクラスを作成するのが好ましいですか?

    ・`Sport Sport.fromXML(String xml)`として実際にはサブクラスである`Baseball`や`Soccer`クラスのインスタンスを返しても問題ないのでしょうか?その場合、呼び出し元では返り値を`instanceof`で判定して分岐させるのが好ましいという認識であっているでしょうか?

    キャンセル

  • 2019/08/29 17:41

    > ・ファクトリメソッドを使う場合、Sportクラスに定義するのが好ましいですか?それともSportFactoryのような別のクラスを作成するのが好ましいですか?

    このあたりは他の場所との兼ね合いにもよりますので、一概には言えないです。

    > ・`Sport Sport.fromXML(String xml)`として実際にはサブクラスである`Baseball`や`Soccer`クラスのインスタンスを返しても問題ないのでしょうか?

    それを行えるようにするための仕組みがFactory Methodです。

    > その場合、呼び出し元では返り値を`instanceof`で判定して分岐させるのが好ましいという認識であっているでしょうか?

    実際のクラスを見て処理を分けるというのもあまり行儀が良くない感じはあります。play()のような抽象メソッドをSportにもたせて、どちらか気にせずに呼び出す、というような手法のほうがいいかもしれません。

    キャンセル

  • 2019/08/29 17:59 編集

    ありがとうございます。play()のような共通のメソッドで処理を分ける方法は行いたいのですが、XMLのスキーマの特性上難しいです。

    ファクトリクラスを以下のように定義しようと思います。
    soccerやbaseballをEnum化したり、soccer,baseball以外だった場合の例外処理は検討の余地があるとおもいますが、その他で何かアドバイスありますでしょうか。
    public class SportFactory {

    public static Sport createSport(String xml) {

    String sportName = XmlUtil.get(xml, "name");

    if("baseball".equals(sportName)) {
    return new Baseball(xml);
    } else if ("soccer".equals(sportName)) {
    return new Soccer(xml);
    }

    return null;
    }
    }

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る