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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

リファクタリング

リファクタリングとはコードの本体を再構築するための手法であり、外見を変更せずに内部構造を変更/改善させることを指します。

Q&A

解決済

5回答

4979閲覧

Java言語で学ぶリファクタリングの練習問題について

nakanohitobot

総合スコア48

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

リファクタリング

リファクタリングとはコードの本体を再構築するための手法であり、外見を変更せずに内部構造を変更/改善させることを指します。

3グッド

4クリップ

投稿2015/12/29 14:37

編集2015/12/30 15:29

現在「Java言語で学ぶリファクタリング(結城 浩著)」を進めています。
本著の第6章練習問題6-2について質問させていただきます。

この問題と模範解答は実際のコードが記載しておらずクラス図しか記載してありません。
そのため以下に示すコードは私がクラス図から起こしたものです。(コードに起こす段階から勘違いしている可能性もありえます。)

問題は以下のとおりです。

Playerクラスは、音楽やビデオを再生するクラスです。setMediaメソッドで音楽かビデオを選択し、play,loop,stopの各メソッドで、それぞれ再生、ループ、停止の処理をおこないます。音楽とビデオの再生方法は異なるので、音楽に大してplayメソッドを実行する場合には内部的にplayMusicメソッドを呼び出し、ビデオの場合には内部的にplayVideoメソッドを呼びだします(loop,stopについても同様)。

このクラスの問題点を指摘し、改善案を示してください。

Java

1//問題があるクラス 2 3public class Player { 4 5 public static final int MUSIC = 1; 6 public static final int VIDEO = 2; 7 8 private int currentMedia; 9 private String musicData; 10 private String videoData; 11 12 public Player(String musicData, String videoData){ 13 this.musicData= musicData; 14 this.videoData= videoData; 15 } 16 17 public void setMedia(int currentMedia){ 18 this.currentMedia = currentMedia; 19 } 20 21 public void play(){ 22 if(currentMedia == MUSIC){ 23 playMusic(); 24 }else if(currentMedia == VIDEO) { 25 playVideo(); 26 } 27 } 28 29 public void loop(){ 30 if(currentMedia == MUSIC){ 31 loopMusic(); 32 }else if(currentMedia == VIDEO) { 33 loopVideo(); 34 } 35 } 36 37 public void stop(){ 38 if(currentMedia == MUSIC){ 39 stopMusic(); 40 }else if(currentMedia == VIDEO) { 41 stopVideo(); 42 } 43 } 44 45 private void stopVideo() { 46 System.out.println("stopVideo:" + videoData); 47 } 48 49 private void stopMusic() { 50 System.out.println("stopMusic:" + musicData); 51 } 52 53 private void loopVideo() { 54 System.out.println("loopVideo:" + videoData); 55 } 56 57 private void loopMusic() { 58 System.out.println("loopMusic:" + musicData); 59 } 60 61 private void playVideo() { 62 System.out.println("playVideo:" + videoData); 63 } 64 65 private void playMusic() { 66 System.out.println("playMusic:" + musicData); 67 } 68} 69

この問題に対する私の解答は以下のとおりです。

Java

1//改善後のクラス(私の解答) 2public interface Player { 3 4 public void play(); 5 6 public void loop(); 7 8 public void stop(); 9} 10 11class Media { 12 String data; 13 14 public Media(String data){ 15 this.data = data; 16 } 17 18 public String toString(){ 19 return data; 20 } 21} 22 23class MusicPlayer implements Player { 24 Media data; 25 public MusicPlayer(Media data){ 26 this.data = data; 27 } 28 29 @Override 30 public void play(){ 31 System.out.println("MusicPlayer:play:" + this.data); 32 } 33 34 @Override 35 public void loop(){ 36 System.out.println("MusicPlayer:loop:" + this.data); 37 } 38 39 @Override 40 public void stop(){ 41 System.out.println("MusicPlayer:stop:" + this.data); 42 } 43} 44 45class VideoPlayer implements Player { 46 Media data; 47 public VideoPlayer(Media data){ 48 this.data = data; 49 } 50 51 @Override 52 public void play(){ 53 System.out.println("VideoPlayer:play:" + this.data); 54 } 55 56 @Override 57 public void loop(){ 58 System.out.println("VideoPlayer:loop:" + this.data); 59 } 60 61 @Override 62 public void stop(){ 63 System.out.println("VideoPlayer:stop:" + this.data); 64 } 65}

play,stop,loopといったPlayerクラスの動作を表すメソッドに着目し、VideoPlayerクラスとMusicPlayerクラスの2種類が必要と考えました。
Mediaクラスはあくまでデータの塊であり、動作を表すメソッドは存在しません。

一方で、本著の模範解答は以下の通りでした。

Java

1//改善後のクラス(模範解答) 2public class Player { 3 Media media; 4 5 public void play(){ 6 System.out.println("Player:play"); 7 media.play(); 8 } 9 10 public void loop(){ 11 System.out.println("Player:loop"); 12 media.loop(); 13 } 14 15 public void stop(){ 16 System.out.println("Player:stop"); 17 media.stop(); 18 } 19 20 public void setMedia(Media media){ 21 this.media = media; 22 } 23} 24 25class Media { 26 String data; 27 28 public Media(String data){ 29 this.data = data; 30 } 31 32 public void play(){ 33 System.out.println("Media:play"); 34 } 35 36 public void loop(){ 37 System.out.println("Media:loop"); 38 } 39 40 public void stop(){ 41 System.out.println("Media:stop"); 42 } 43} 44 45class Music extends Media{ 46 47 public Music(String data){ 48 super(data); 49 } 50 51 @Override 52 public void play(){ 53 System.out.println("Music:play:" + this.data); 54 } 55 56 @Override 57 public void loop(){ 58 System.out.println("Music:loop:" + this.data); 59 } 60 61 @Override 62 public void stop(){ 63 System.out.println("Music:stop:" + this.data); 64 } 65} 66 67class Video extends Media{ 68 69 public Video(String data){ 70 super(data); 71 } 72 73 @Override 74 public void play(){ 75 System.out.println("Video:play:" + this.data); 76 } 77 78 @Override 79 public void loop(){ 80 System.out.println("Video:loop:" + this.data); 81 } 82 83 @Override 84 public void stop(){ 85 System.out.println("Video:stop:" + this.data); 86 } 87} 88

以上です。

どうも模範解答に納得できません。
MusicやVideoというデータを表すクラスがplay,stop,loopといった動作を表すメソッドをもっていることに違和感を感じてしまいます。

しかし、私の解答ですと、Mediaクラスのインスタンスと、どちらかのPlayerクラスのインスタンスの2つをつくらなければなりませんし、setMediaメソッドであとからPlayerの動作を変化させることはできないなど使い勝手が悪い気もします。

長くなりましたが、お聞きしたいことは以下です。
・私の解答に問題はないか?
・模範解答のように、MusicクラスとVideoクラスに動作を表すメソッドが存在してもよい理由はなにか?

よろしくお願いします。

ikuwow, argius👍を押しています

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakanohitobot

2015/12/30 15:11

ご指摘ありがとうございます。 ご指摘のコードは、次の章の練習問題の解答で、本問題の解答ではないようです。 ただそれとは別に、「問題があるクラス」で一部間違いがあることに気付きました。自分で色々といじっている内に修正後のコードと混じってしまったのだと思います。 該当部分を修正させていただきます。
guest

回答5

0

ベストアンサー

ここでのリファクタリングの目的は、次のような変化に対処しやすくすることだと思います。

  1. video と music とは別の種類の mdeia を追加しやすくする。
  2. 再生、ループ、停止とは別の種類の操作を追加しやすくする。

リファクタリング前のコードでは、media の種類を追加する場合は、次の事が問題です。
play(), loop(), stop() 中の if 分岐に それぞれ 条件分岐を追加していくことが必要です。
これは間違いや追加漏れが発生しやすいです。
操作を追加する場合も、その中に media 種類ごとの条件文を書くことが必要で、↑の場合と同様に間違いが発生しやすいです。

・質問文の解答に問題はないか?
==> Media の種類を追加して、アプリケーション側で play() を行う場合、
Media の種類ごとの条件分岐を増やす必要がある。

リファクタリングの目的は達成できているかもしれません。 でも使う側にとっては、リファクタリング前のコードの方が使いやすいです。

・模範解答のように、MusicクラスとVideoクラスに動作を表すメソッドが存在してもよい理由はなにか?
==> オブジェクト指向の基本は、オブジェクトにメッセージを投げることです。
Music というオブジェクトに play() のようなメッセージを投げることは問題ではありません。

シューティングゲームを作る場合、自機を表すオブジェクトを作ると思います。 そのオブジェクトに 位置を移送する move() や 弾を撃つ hit() といった動作を表すメソッドを追加することは、 極めて自然な事と思います。

わたしなら、次のように書きます。
模範解答との差は extends でなく interface をつかっている点です。
(extends にすると、java では多重継承ができないので将来 拡張がしにくくなる可能性がある)

コメントとして、pause() 操作を追加する場合のことと、video クラスを追加する場合のことを書いてあります。

java

1public class SamplePlayer { 2 public static void main(String[] args) throws Exception { 3 Player player = new Player(); 4 Media music = new Music("ABBA"); 5 player.setMedia(music); 6 player.play(); 7 player.stop(); 8 } 9} 10 11class Player { 12 Media media = null; 13 14 public void setMedia(Media media) { 15 this.media = media; 16 } 17 18 public void play() throws Exception { 19 System.out.println("Player:play"); 20 this.media.play(); 21 } 22 23 public void loop() throws Exception { 24 System.out.println("Player:loop"); 25 this.media.loop(); 26 } 27 28 public void stop() throws Exception { 29 System.out.println("Player:stop"); 30 this.media.stop(); 31 } 32 33 // public void pause(Media media) { 34 // System.out.println("Player:pause"); 35 // this.media.pause(); 36 // } 37} 38 39interface Media { 40 public void play() throws Exception; 41 42 public void loop() throws Exception; 43 44 public void stop() throws Exception; 45 // public void pause() throws Exception; 46} 47 48class Music implements Media { 49 private final String data; 50 51 public Music(String data) { 52 this.data = data; 53 } 54 55 @Override 56 public void play() { 57 System.out.println("Music:play:" + this.data); 58 } 59 60 @Override 61 public void loop() { 62 System.out.println("Music:loop:" + this.data); 63 } 64 65 @Override 66 public void stop() { 67 System.out.println("Music:stop:" + this.data); 68 } 69 70 // @Override 71 // public void pause() { 72 // System.out.println("Music:pause:" + this.data); 73 // } 74} 75 76// class Video implements Media { 77// private final String data; 78// ... 79// }

実行したときの出力

Player:play Music:play:ABBA Player:stop Music:stop:ABBA

投稿2015/12/29 20:02

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakanohitobot

2015/12/31 07:34

ご回答ありがとうございます。 わざわざコードまで示していただいてとても助かります。 シューティングゲームの例えは納得できました。確かに動作を表すメソッドを追加するのは自然ですね。 オブジェクトにメッセージを投げて、処理を指示するのがオブジェクト指向なのですね。 まだピンと来ないのが正直なところですが、今後慣れていこうと思います。 重ねて申し訳ありませんが、示していただいたコードについて2点ほど質問させてください。 ・全てのメソッドに「throws Exception」がついているのはなぜでしょうか? ・Musicクラスのフィールド変数にfinalを付けているのはなぜでしょうか?(private final String data) 以上です。よろしくお願いします。
katoy

2015/12/31 08:43

> 全てのメソッドに「throws Exception」がついているのはなぜでしょうか? > Musicクラスのフィールド変数にfinalを付けているのはなぜでしょうか? どちらも、質問に対する回答としては本質的なことではありません。 throws Exception をつけたのは、エラー処理を意識してほしかったからです。 (アプリケーションとしてこれを発展させて行く場合、エラーをどのように扱うかを初期段階から意識しておくべきです。 Excepiton を発生させるのは誰か、どんな Exception の種類にするか、Exception を catch してエラー処理をするのは誰か) final をつけているのは、コンストラクタで設定した後は、そのフィールドに値は変化させたくないと思ったからでした。
nakanohitobot

2015/12/31 10:07

ご回答有り難うございます。 解説していただければ納得できるのですが、なかなかそこまで気が回りませんね。 今後意識するよう心がけていきたいと思います。
guest

0

こんにちは。

う~ん、そもそも元のプログラムと回答プログラムの外部からみた動きが異なるので、リファクタリングになっていないように思います。模範解答とnakanohitobotさんのプログラムはかなり近いですが、元のプログラムだけ大きく異なってます。書き起こし失敗されてますよね?

setMediaメソッドであとからPlayerの動作を変化させることはできないなど使い勝手が悪い気もします。

機能を変えてしまうと、そもそもリファクタリングではないと思います。


プログラムの構造としては、私はnakanohitobotさんの構造の方が好ましく思います。プログラムの構造は現実に近いモデルにしておいた方が、可読性/メンテナンス性にすぐれていると考えているからです。プレイヤーとメディアを分離するnakanohitobotさんの案の方がより現実世界に近いモデルになっています。

ただ、元のプログラムの外部I/Fが良く分からないので、制約条件が不明ですから、リファクタリングの演習問題の回答としての意見を述べることはできません。

投稿2015/12/29 18:24

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakanohitobot

2015/12/31 07:41

ご回答有り難うございます。 確かにリファクタリングの大原則を考えれば不味いですよね私のコードは。。 クラスを修正することに夢中でそこまで意識していませんでした。 以後、肝に銘じようと思います。 プログラムの構造に関しては、人によって色々な考え方があるのですね。 私は最初、自分の考え方しかないと思っていましたが、他のかたの意見を聞くうちにそちらのほうが理にかなっているのではないかと思い始めました。 皆様の意見を聞かせていただいて少しでも精進して行ければと思います。
guest

0

・質問1について
「interface Player」はplay,loop,stopできる物という意味を持ちます。一方で、「extend Media」はメディアの一つであるとなります、この違いはかなり大きいと思います。
質問者のコードでは、「人の動き」や「機械の動作」などもPlayerを実装する可能性があり、本来のコードの意味から離れてしまう可能性があります。
そのため、私はMediaを継承する方がいいと思います。

・質問2について
Javaでは、データだけを表すクラスは本来ありえないと思います。

クラスは「データ」と「データに対する操作」を定義した物です。

ここから、質問者のコードではMediaをデータだけを定義したクラスとなっていて、その使い方は他のクラスに任してしまう、何もしない仲介者となっています。

投稿2015/12/29 17:39

yona

総合スコア18155

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Stripe

2015/12/29 17:49

誤)クラスは「データ」と「データに対する操作」を定義した物です。 正)オブジェクトは「データ」と「データに対する操作」を定義した物です。 データだけを表すクラスは普通にありえます。
yona

2015/12/30 03:45

クラスで属性と振る舞いを定義し、オブジェクトで定義された内容を使うので、私の書き方は間違っていないと思います。 「データだけのクラスはあり得ない」は個人的な意見となります。 「クラスは自分が保持するデータとその使い方に責任を持つべき」と言う考えから来ています。
nakanohitobot

2015/12/31 07:57

ご回答有り難うございます。 確かに、Mediaクラスをデータだけとすると、他の機能は全てPlayerクラスが担わなければならなくなりますね。機能追加のことまで今は考えられませんが、徐々にこういう視点も身に着けていきたいと思います。 「クラスは「データ」と「データに対する操作」を定義した物」これはクラスを習い始めたころに聞いた気がしますが、今になって腑に落ちました。 この原則に従えばMediaクラスがデータだけを持つのは明らかにおかしいですね。 「クラスの責任」というのは自分で所持しているデータは自分自身で使い方を決めるということなのですね。
guest

0

それは、「Stateパターンを利用したタイプコードの置き換え」のリファクタリングの演習問題です。

PlayerはMediaの内容に応じて、振る舞いを変えることが要件となっています。
しかし、あなたの回答にはそれがありません。Mediaの内容がMusicなのかVideoなのかが、あらかじめ分かっていることが前提となってしまっています。

とは言え、Stateパターンは状態変化に伴う振る舞いの変化を実装するものなので、あまり出題の内容が良くない気もしますが。

投稿2015/12/29 17:24

Stripe

総合スコア2183

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakanohitobot

2015/12/31 08:00

ご回答有り難うございます。 デザインパターンはまだ勉強していないのでわからないですね。 逆を言えばデザインパターンを学んで身に付ければこういう考え方が自然にできるということなのでしょうか。
Stripe

2015/12/31 08:31

あなたが読んだリファクタリングの本には、「タイプコードの置き換え」について、何も解説が書かれていないんですか?
nakanohitobot

2015/12/31 10:09

目次をみたところ先の章で解説しているようです。 先に一通り目を通したほうが理解が深まりそうですね。
guest

0

Mediaのplayという名前に違和感があるのならば、playではなくstartと考えれば自然でしょうか。
実際、Androidのメディア系のメソッドはstartなどがあります。
実際の機械を考えると、「再生」や「停止」の命令を入力するのは外部の機械のインタフェースで、
実際に動くのはメディアである点を考えれば、メディアが動作のメソッドを持っていても何ら不思議ではありません。

あなたの解答において、音楽と動画において「メディアを再生・停止」などの目的がほぼ同じなのにそれをインタフェース経由であえて実装を分ける必要性が余り感じられないように思います。
また、例えば再生時や停止時に画面に何か表示するという機能に変更が生じた際、2つのクラスを修正する必要が発生し、整合性の問題が生じます。

投稿2015/12/29 16:42

swordone

総合スコア20651

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakanohitobot

2015/12/31 08:05

ご回答有り難うございます。 「実際に動くデータに対して使いかたを提供するのがメソッド」と考えればいいのですね。皆さんの回答で考え方がわかった気がします。 目の前の実装に必死で、修正のことまで考えるのはなかなか厳しいですが今後慣れていこうと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問