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

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

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

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

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

リファクタリング

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

Q&A

解決済

3回答

3824閲覧

State/Strategeによるタイプコードの置き換えについて(Java言語で学ぶリファクタリング入門)

oshigotoDacho

総合スコア53

Java

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

デザインパターン

デザインパターンは、ソフトウェアのデザインでよく起きる問題に対して、解決策をノウハウとして蓄積し再利用出来るようにした設計パターンを指します。

リファクタリング

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

0グッド

2クリップ

投稿2016/01/27 01:50

Java言語で学ぶリファクタリング入門 (結城 浩 著)の第9章<State/Strategeによるタイプコードの置き換え>について質問させていただきます。

以下にリファクタリング前後のコードと動作用のMainクラスを記載します。

Java

1 2//リファクタリング前 3public class Logger { 4 public static final int STATE_STOPPED = 0; 5 public static final int STATE_LOGGING = 1; 6 private int _state; 7 public Logger() { 8 _state = STATE_STOPPED; 9 } 10 public void start() { 11 switch (_state) { 12 case STATE_STOPPED: 13 System.out.println("** START LOGGING **"); 14 _state = STATE_LOGGING; 15 break; 16 case STATE_LOGGING: 17 /* Do nothing */ 18 break; 19 default: 20 System.out.println("Invalid state: " + _state); 21 } 22 } 23 public void stop() { 24 switch (_state) { 25 case STATE_STOPPED: 26 /* Do nothing */ 27 break; 28 case STATE_LOGGING: 29 System.out.println("** STOP LOGGING **"); 30 _state = STATE_STOPPED; 31 break; 32 default: 33 System.out.println("Invalid state: " + _state); 34 } 35 } 36 public void log(String info) { 37 switch (_state) { 38 case STATE_STOPPED: 39 System.out.println("Ignoring: " + info); 40 break; 41 case STATE_LOGGING: 42 System.out.println("Logging: " + info); 43 break; 44 default: 45 System.out.println("Invalid state: " + _state); 46 } 47 } 48} 49

Java

1//リファクタリング後 2public class Logger { 3 private enum State { 4 STOPPED { 5 @Override public void start() { 6 System.out.println("** START LOGGING **"); 7 } 8 @Override public void stop() { 9 /* Do nothing */ 10 } 11 @Override public void log(String info) { 12 System.out.println("Ignoring: " + info); 13 } 14 }, 15 16 LOGGING { 17 @Override public void start() { 18 /* Do nothing */ 19 } 20 @Override public void stop() { 21 System.out.println("** STOP LOGGING **"); 22 } 23 @Override public void log(String info) { 24 System.out.println("Logging: " + info); 25 } 26 }; 27 28 public abstract void start(); 29 public abstract void stop(); 30 public abstract void log(String info); 31 } 32 33 private State _state; 34 public Logger() { 35 setState(State.STOPPED); 36 } 37 public void setState(State state) { 38 _state = state; 39 } 40 public void start() { 41 _state.start(); 42 setState(State.LOGGING); 43 } 44 public void stop() { 45 _state.stop(); 46 setState(State.STOPPED); 47 } 48 public void log(String info) { 49 _state.log(info); 50 } 51} 52

Java

1//動作用Mainクラスはリファクタリング前後で変化せず 2 3public class Main { 4 public static void main(String[] args) { 5 Logger logger = new Logger(); 6 logger.log("information #1"); 7 8 logger.start(); 9 logger.log("information #2"); 10 11 logger.start(); 12 logger.log("information #3"); 13 14 logger.stop(); 15 logger.log("information #4"); 16 17 logger.stop(); 18 logger.log("information #5"); 19 } 20}

疑問点は以下になります。

余分なインスタンスが作成されていい理由は?
リファクタリング前のコードは、メソッドが呼び出された時に、Stateが該当する状態だったら何もしないとなっています。
(STATE_LOGGINGならばstart()を呼び出した時、STATE_STOPPEDならばstop()を呼び出した時)
しかし、リファクタリング後のコードでは、setState(State.~)を実行して新たにインスタンスを作成してしまいます。
実行結果はリファクタリング前と変化はありませんが、余分なインスタンスが作成されてしまっていいのでしょうか?

自己流でダメな点は?
以下は自分でリファクタリングを行ったコードです。
こちらもメソッドが呼び出される度に新たなインスタンスを作成していますが、①が問題ないのでしたら、このコードも問題ないのでしょうか?

Java

1 2//自己流リファクタリング後 3public class Logger { 4 private State _state; 5 public Logger() { 6 State state = new StateStopped(); 7 setState(state); 8 } 9 public void setState(State state) { 10 this._state = state; 11 } 12 public void start() { 13 _state.start(); 14 setState(new StateLogging()); 15 } 16 public void stop() { 17 _state.stop(); 18 setState(new StateStopped()); 19 } 20 public void log(String info) { 21 _state.log(info); 22 } 23} 24 25public abstract class State { 26 public abstract void start(); 27 public abstract void stop(); 28 public abstract void log(String info); 29} 30 31public class StateLogging extends State { 32 @Override public void start() { 33 System.out.println(); 34 } 35 @Override public void stop() { 36 System.out.println("** STOP LOGGING **"); 37 } 38 @Override public void log(String info) { 39 System.out.println("Logging: " + info); 40 } 41} 42 43public class StateStopped extends State { 44 @Override public void start() { 45 System.out.println("** START LOGGING **"); 46 } 47 @Override public void stop() { 48 System.out.println(); 49 } 50 @Override public void log(String info) { 51 System.out.println("Ignoring: " + info); 52 } 53}

以上になります。どなたかご教示お願い致します。

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

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

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

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

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

guest

回答3

0

①余分なインスタンスが作成されていい理由は?
そもそもenumはシングルトンになるため、インスタンスの増減はしません。setStateは定数の代入に近いものですね。

②自己流でダメな点は?
enumが導入される前はこのようなコードで対応していたと推測されます。
ただし、現状のコードでは状態のシングルトンな性質を捉えていない点や拡張が自由な点が問題かなと思います。

投稿2016/01/27 02:29

編集2016/01/27 02:31
yona

総合スコア18155

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

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

0

ベストアンサー

  1. 余分なインスタンスは作成されません。

列挙型enumと言うのは静的不変インスタンスの集合です。列挙された要素はstatic finalなインスタンスのような特徴を持ち、enumのクラスがロードされた段階で列挙された要素(この場合LOGGING,STOPPED)のインスタンスが作成されて変数として保持されます。setStateで渡しているインスタンスはこのStateクラスですでに作成されているインスタンスへの参照を代入していることにほかなりません。setStateの段階でインスタンスを作成しているわけではありません。
それよりもこのリファクタリングで目的としていることは、想定外の状態を代入しないことです。元のコードでは、状態としてintの値を利用しています。しかし、状態を表すのは0と1のみで、他は「異常な状態」です。しかし、int値としては当然それ以外の値も入り得るため、安全性に欠けます。そこで状態に列挙型を利用すると、列挙型で挙げられたインスタンス以外を指定することが不可能になるため(指定するとコンパイルエラーになる)、安全性が高まります。

  1. StateLogging,StateStopped以外のStateを作成され、想定外の動きをさせられる恐れがあります。

前述のとおりenumを使った場合新たなインスタンスを生成しないのに対し、こちらは新たなインスタンスをメソッドの起動のたびに生成するというデメリットが有ります。仮にこの2つのクラスのインスタンスを1つ生成し、どこかのクラスのstatic finalフィールドにおいておき、それを使えばそのような問題はなくなります。
しかし一方で、引数として受け取っているのが抽象クラスStateであることから、**ユーザーが自身でこれを継承し、独自の実装をしてこのメソッドに渡すことが可能になってしまいます。**セキュリティの観念からこれは望ましくないことは明白です。Stateクラスをパッケージプライベートにして、2つの状態クラスをfinalに指定するという方法が考えられますが、それらをすべて実現できるenumの利便性には劣ります。

投稿2016/01/27 02:24

編集2016/01/27 02:32
swordone

総合スコア20649

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

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

0

こんにちは。

先ずご質問に回答させていただく前にですが、
start() と stop() ですが、継承先でどちらか一方が要らなくなっています(機能退化してます)。

ご提示いただいた例だけを考えますと、startもstopも、Loggerクラスの機能としたほうがふさわしく、
一方でStateがやるべきことはstartかstopされたときに、何らかのアクションを起こすものとお見受けします。

ですから、Stateから start と stop メソッドを取り去り、代わりに initメソッドにしてしまったほうが良いかと思われます。

Java

1 private enum State { 2 STOPPED { 3 @Override public void init() { 4 System.out.println("** START LOGGING **"); 5 } 6 @Override public void log(String info) { 7 System.out.println("Ignoring: " + info); 8 } 9 }, 10 11 LOGGING { 12 @Override public void init() { 13 System.out.println("** STOP LOGGING **"); 14 } 15 @Override public void log(String info) { 16 System.out.println("Logging: " + info); 17 } 18 }; 19 20 public abstract void init(); 21 public abstract void log(String info);

そのうえで、Loggerクラスのstartおよびstopメソッドは次のように変更します。

Java

1 public void start() { 2 setState(State.LOGGING); 3 _state.init(); 4 } 5 public void stop() { 6 setState(State.STOPPED); 7 _state.init(); 8 }

stateのイニシャライズが必須であれば、できればsetStateメソッドは外部公開しないように設計するほうが望ましいでしょう。

これが適うならば、内部の _state を入れ替えるだけですので、setStateメソッド自体がいらなくなります。

さて、ご質問いただいた ① に関しましてですが、都度インスタンスが作られることを気にされるのであれば、Singletonで設計すると良いでしょう。

ただし、Singletonにしてもインスタンスが増えることには変わりありません。
メモリに常駐しますので、その分メモリは消費します。

ですが、メモリを消費してStateパターンを採用したところ、
ご提示の例で言えば、ログが「停止状態」と「実行状態」の実装を別々に分けて記述できるようになります。

例えば「受注が未だ」「受注確度高い」「受注済」「在庫あり」「発注前」「納品前」・・・
これらの状態のもと、一つの機能(取引状況お知らせメールとか)が使われなければならない、と想定します。

きっとStateパターンを利用しない場合はif文やswitch文を使って、あらゆるところで条件分岐が使われることになるのは想像できるでしょうか。
ソースの見通しが悪くなり、保守効率が下がります。改修の際にバグが発生する確率も上がります。

Stateパターンによりメモリが消費されることと、保守性能を下げることとを比較し、どちらに重点を置くか。
感情的にならず、冷静に、公平に判断され、最適な方法である根拠が示せるのであれば、どちらの方法をとっても良いと考えます。

startおよびstopメソッドの「機能の退化」を改善する前提であれば、②の方法も十分立派だと思います。
都度インスタンスすることが気になるようであれば、StateはSingletonにしてしまいましょう。
私もこの場合においてStateパターンを採用するのであればSingletonにします。

Java

1public class StateLogging extends State { 2 3 private SateLogging(){} 4 private static StateLogging _me; 5 public static StateLogging instance(){ 6 if(_me == null) _me = new StateLogging(); 7 return _me; 8 } 9 10 @Override public void init() { 11 System.out.println("** STOP LOGGING **"); 12 } 13 @Override public void log(String info) { 14 System.out.println("Logging: " + info); 15 } 16}

投稿2016/01/27 02:56

Toyoshima

総合スコア422

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

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

swordone

2016/01/27 03:07

うーん…言わんとしていることはわかるのですが… これだと「開始状態で開始しようとする」とか、「停止状態で停止しようとする」とかの、現実的に起こりそうな操作が全く考慮されていないのが気になります。 さらにMainの書き換えも必要になり、当初の目的からずれているように思います。
Toyoshima

2016/01/27 05:31

swordone さん こんにちは。ご指摘ありがとうございます。 機能退化を改善することに関してのご指摘でしょうか? Loggerクラスには sart と stop メソッドは実装されています。 MainロジックはLoggerクラスだけが見える状態ですので、書き換えは必要ないです。 「開始状態で開始しようとする」と「停止状態で停止しようとする」の点につきましては、その処理に目的があり、startメソッドもstopメソッドも、継承先で有意に実装されるのであれば、残すべきです。 ご質問内容を拝見したところ、 ①start、stopメソッドが何も実装されない場合がある ②インスタンスを都度作成する という2か所あると考えました。 ①を前提とされる場合、やはりこれは実装されないメソッドですので、「そのうち使うかもしれない」という無駄な機能を残すべきではなく、機能の退化を避けるべきと考えます。 繰り返しますが、有意な実装が成されるならば、startもstopも残すべきです。 ご質問内容に外れるところは断りつつ、できるだけくみ取って沿って回答差し上げたつつもりですが・・・「当初の目的とずれている」と感じられたのはどのあたりでしょうか?
Toyoshima

2016/01/27 06:08

「当初の目的とずれている」というご意見の論拠に、気が付きました! swordoneさんは、リファクタリングの目的を 「想定外の状態を代入しないこと」に置いてますね。 私のほうは、リファクタリングの目的は、Sate/Strategyのパターンを採用されているため、 「区分による条件分岐があちこちに散見されるような効率の悪いコードにしない」 という前提のもと、 ①インスタンスを都度つくるのは大丈夫か ②EnumではなくAbstractのクラスでも良いか を質問されているものと考えてました。 加えて、①の質問の中に、「start と stop の実装が要らない場合もあるのは良いのか」という質問もあるのかと考えてもいます。 どちらがご質問者の意図なのでしょう・・・
oshigotoDacho

2016/01/29 08:21

ご回答ありがとうございます。 >①の質問の中に、「start と stop の実装が要らない場合もあるのは良いのか」 これは意図していませんでした。しかし、仰るとおり無駄な機能は残すべきではないですね。自分でコードを組むときは心がけるようにします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問