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

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

新規登録して質問してみよう
ただいま回答率
85.37%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

デザインパターン

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

Q&A

解決済

1回答

311閲覧

InterfaceとAdapterパターン採用時における仕様変更への対処

footarooo

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

インターフェース

インターフェイスという用語はハードウェア・ソフトウェアの両方に使うことができます。 一般的に、インターフェイスは内部処理の詳細を見せないように設定されます。オブジェクト指向プログラミングにおいて、インターフェイスはabstractクラスとして定義されます。

デザインパターン

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

0グッド

2クリップ

投稿2024/10/20 03:44

編集2024/10/21 01:17

実現したいこと

添付コードの状況において「Carインスタンス作成後に、プレイヤーの数値入力によって動的にSuperEngineのgearを上げ下げできるようにしたい」といった仕様変更が入った際、どのように修正するのが良いか意見を伺いたいです。

発生している問題・分からないこと

そもそもこのような仕様変更が入る可能性を考えずにIEngineインターフェースで抽象化してしまったのが間違いなのか、それとも他に抽象化のメリットを維持しながら仕様変更に対処する方法があるなら知りたい。

該当のソースコード

C#

1public class Hello{ 2 public static void Main(){ 3 4 Car car = new Car(new MiniEngine()); 5 Car car2 = new Car(new SuperEngineAdapter(new SuperEngine(1))); 6 7 car.Run(); // 5 8 car2.Run(); // 30 9 10 var line = System.Console.ReadLine(); 11 int newGear = 0; 12 bool result = int.TryParse(line, out newGear); 13 if(result){ 14 //car2.SetGear(newGear); //コンパイルエラー 15 System.Console.WriteLine($"newGear:{newGear}"); 16 } 17 18 19 car2.Run(); // newGearに応じて変化させたい 20 21 } 22} 23 24 25 26public class Car{ 27 public IEngine engine; 28 public Car(IEngine engine){ 29 this.engine = engine; 30 } 31 32 public void Run(){ 33 float power = engine.Boost(); 34 System.Console.WriteLine("power=" + power.ToString()); 35 } 36} 37 38public interface IEngine{ 39 float Boost(); 40} 41public class SuperEngine{ 42 int gear; 43 public SuperEngine(int initalGear){this.gear = initalGear;} 44 45 public float GetPower(){ 46 if(gear == 0) {return 10;} 47 else if(gear == 1){return 30;} 48 else return 50; 49 } 50 51 //public void SetGear(int gear) {this.gear = gear;} 52 53} 54public class SuperEngineAdapter : IEngine 55{ 56 SuperEngine se; 57 public SuperEngineAdapter(SuperEngine se){ 58 this.se = se; 59 } 60 public float Boost(){ 61 return se.GetPower(); 62 } 63} 64 65public class MiniEngine : IEngine{ 66 public float Boost(){ 67 return 5; 68 } 69}

修正済みコード(追加)

C#

1public class Hello{ 2 public static void Main(){ 3 4 Car car = new Car(new MiniEngine()); 5 Car car2 = new Car(new SuperEngineAdapter(new SuperEngine(1))); 6 7 car.Run(); // 5 8 car2.Run(); // 30 9 10 var line = System.Console.ReadLine(); 11 int newGear = 0; 12 bool result = int.TryParse(line, out newGear); 13 if(result){ 14 //New Code 15 car.SetEngineGear(newGear); 16 car2.SetEngineGear(newGear); 17 // 18 System.Console.WriteLine($"newGear:{newGear}"); 19 } 20 21 car.Run(); //Warning 22 car2.Run(); // newGearに応じて変化 23 24 } 25} 26 27 28 29public class Car{ 30 protected IEngine engine; 31 public Car(IEngine engine){ 32 this.engine = engine; 33 } 34 35 //New Code 36 public void SetEngineGear(int newGear){ 37 engine.SetGear(newGear); 38 } 39 40 public void Run(){ 41 float power = engine.Boost(); 42 System.Console.WriteLine("power=" + power.ToString()); 43 } 44} 45 46public interface IEngine{ 47 float Boost(); 48 void SetGear(int newGear); //New Code 49} 50public class SuperEngine{ 51 int gear; 52 public SuperEngine(int initalGear){this.gear = initalGear;} 53 54 public float GetPower(){ 55 if(gear == 0) {return 10;} 56 else if(gear == 1){return 30;} 57 else return 50; 58 } 59 60 public void SetGear(int gear) {this.gear = gear;} //New Code 61 62} 63public class SuperEngineAdapter : IEngine 64{ 65 SuperEngine se; 66 public SuperEngineAdapter(SuperEngine se){ 67 this.se = se; 68 } 69 public float Boost(){ 70 return se.GetPower(); 71 } 72 73 public void SetGear(int gear){ se.SetGear(gear);} //New Code 74} 75 76public class MiniEngine : IEngine{ 77 public float Boost(){ 78 return 5; 79 } 80 81 //New Code 82 public void SetGear(int newGear){ 83#warning MiniEngine has no gear choice. It's always 5. 84 } 85}

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

以下参考サイト
https://refactoring.guru/ja/design-patterns/adapter
https://www.youtube.com/watch?v=OCh1Bnj9gws
https://www.youtube.com/watch?v=CrqdpVPgE9k

補足

特になし

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

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

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

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

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

fana

2024/10/21 01:47

(既に回答が付いていますが) このような話をするには「どこが変更可能で,どこが変更不可能なのか」という話が必要かと見えます. 「修正済みコード」を見た感じだと,登場人物の全てに変更が入っているように見えます. そういうことが可能なのであれば, > そもそもこのような仕様変更が入る可能性を考えずにIEngineインターフェースで抽象化してしまったのが間違いなのか… とか嘆く(?)必要もなく,明確になった新仕様に合わせてしまえばよいです. (それが「修正済みコード」なのであれば,既に問題は解決しているのではないのでしょうか?)
guest

回答1

0

ベストアンサー

こんにちは。

結論から、質問の仕様変更の場合であれば、IEngine インターフェースにメソッドを追加し、実装を修正するか新たに Adapter を噛ませて互換にするのが正解です。

割と勘違いされやすいのですが、このコードにおいて IEngine インターフェースの持ち主 (== 責任者) は、インターフェースを実装している Engine や EngineAdapter ではなく、それを利用している Car クラスの方です。
インターフェースというのは「どのようにアクセスしたいか」の抽象なので、Car が IEngine に対して「gearを上げ下げ」する要件を追加するのであれば、それはそのままインターフェースへのメソッドの追加で表現されるわけです。

インターフェースを用いた抽象化のメリットは「変更しなくても良いこと」ではなく、「どのように変更するのかが明確であること」なのです。

元々は「Engine の内部がどうであろうと、Boost が押せれば良い」という要件がインターフェースとして表現されていたので、それが「Engine の内部がどうであろうと、Boost が押せて gear の上げ下げができれば良い」という要件に変わったことで、それをそのままインターフェースの定義に表現すれば良いわけです。
Car の求める要件が変わるのなら、Car の持つ IEngine インターフェースが変わるのもごく自然なことなのです。

Adapter パターンは、このような「インターフェースの変更」を実装側に適用できない (実装を変更出来ない) 状況において、利用側 (Car) と実装側 (XXEngine) をインターフェースで繋ぐために存在します。
そのため、実装をインターフェースの変更に追従させられる状況であるなら、Adapter はそもそも不要です。

投稿2024/10/20 09:31

tamoto

総合スコア4228

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

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

footarooo

2024/10/21 01:29 編集

丁寧な回答ありがとうございます! IEngineインターフェースにSetGearメソッドを追加する形で実装したコードを質問内容に追加したのですが、このような実装で問題無いということでしょうか? 個人的に、MiniEngineに対してSetGearを行っても何も起こらないということがCarクラスからはわからないという点について気味悪く感じたのでWarningを出力するようにしたのですが、何か他に適切な方法はありますでしょうか?
tamoto

2024/10/21 02:35

現在の設計において、Car は IEngine にのみ依存していて、その実装が何であるかを知るべきではないです。 Car の視点では、常に IEngine として表面化されたインターフェースにアクセスすることしかできないということです。 よって、実装が MiniEngine だった場合に SetGear を行っても何も起こらないというのは想定されたことで、それは現状の IEngine を介した抽象化において何の問題もないと考えられます。 仮に現在 `void SetGear(XX newParameter);` のようなインターフェース定義にしているとしたら、「SetGear が呼び出されたあと結果はどのようになったか」を Car がハンドルできるような戻り値を用意してあげると良いかもしれません。 「結果が返ること」が IEngine のインターフェースとして表現されていれば、Car はそれらの情報を合法的に利用可能になるわけです。
footarooo

2024/10/21 11:13

抽象化することの意味をきちんと理解できていなかったので、助かります。 確かに戻り値として正しく処理が反映されたかといった情報をresultとして返してあげることでより安全に利用できると感じました。 また、Adapaterパターンを採用しているのにも関わらず実装側のSuperEngineクラスを修正してしまっているため、その部分もよく考えたいと思います。 ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.37%

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

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

質問する

関連した質問