web-dev-qa-db-ja.com

ポリモーフィズムのコンテキストでサブタイプに追加されたメソッドを処理する方法は?

ポリモーフィズムの概念を使用する場合、クラス階層を作成し、親参照を使用して、オブジェクトがどの特定の型にあるかを知らずにインターフェイス関数を呼び出します。これは素晴らしい。例:

動物のコレクションがあり、すべての動物に関数eatを呼び出します。犬を食べるのか猫をするのかは関係ありません。ただし、同じクラス階層で、クラスAnimalから継承および実装されたもの以外に、追加の動物があります。 makeEggsgetBackFromTheFreezedStateなど。したがって、場合によっては、関数内で追加の動作を呼び出すために特定のタイプを知りたい場合があります。

たとえば、朝の時間で動物だけの場合はeatを呼び出し、それ以外の場合は最初にwashHandsgetDressedを呼び出します。その後、eatを呼び出します。このケースをどのように処理しますか?ポリモーフィズムは死ぬ。コードのにおいのように聞こえるオブジェクトのタイプを見つける必要があります。このケースを処理するための一般的なアプローチはありますか?

14
Narek

依存します。残念ながら、一般的な解決策はありません。要件について考え、これらのことで何をすべきかを考えてみてください。

たとえば、あなたは朝、さまざまな動物がさまざまなことをしていると言いました。メソッドgetUp()またはprepareForDay()などを導入してみませんか。次に、多型を続行し、各動物に朝のルーチンを実行させます。

動物を区別したい場合は、無差別にリストに保存しないでください。

他に何も機能しない場合は、Visitor Pattern を試すことができます ハックの種類 動物からタイプ正確なコールバックを受け取るビジターを送信できる、一種の動的なディスパッチを可能にします。ただし、他のすべてが失敗した場合は、これが最後の手段であることを強調しておきます。

18

これは良い質問であり、OOの使い方を理解しようとするときに多くの人が悩むようなものです。ほとんどの開発者はこれに苦労していると思います。私はほとんどがそれを乗り越えたと言えるかもしれませんが、それが事実であるかどうかはわかりません。私の経験では、ほとんどの開発者は pseudo-OO property bag を使用します。

まず、はっきりさせておきます。これはあなたのせいではありません。 OOが通常教えられている方法には、かなり欠陥があります。Animalの例は、最高の犯罪者IMOです。 Animalできますeat()speak()できます。すばらしいです。動物をいくつか作成して、動物が食べたり話したりする方法をコーディングします。

問題は、これがOOで間違った方向から来ていることです。なぜこのプログラムに動物がいるのか、なぜ彼らは話したり食べたりする必要があるのですか?

Animal型の実際の使用を考えるのに苦労しています。確かにあると思いますが、トラフィックシミュレーションについて、簡単に説明できると思います。さまざまなシナリオでトラフィックをモデル化するとします。これを実現するために必要な基本的な事項を以下に示します。

Vehicle
Road
Signal

歩行者や電車など、あらゆるものをより深く理解することができますが、簡単に説明します。

Vehicleについて考えてみましょう。車両にはどのような機能が必要ですか?道路を走行する必要があります。信号で停止できる必要があります。交差点をナビゲートできる必要があります。

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

これはおそらく単純過ぎるでしょうが、それは始まりです。今。車両が行う可能性のある他のすべてのことはどうですか?彼らは道路をオフにして溝に入ることができます。それはシミュレーションの一部ですか?いいえ、必要ありません。一部の車とバスには、それぞれ跳ね返ったりひざまずいたりできる油圧装置があります。それはシミュレーションの一部ですか?いいえ、必要ありません。ほとんどの車はガソリンを燃やします。一部ではありません。発電所はシミュレーションの一部ですか?いいえ、必要ありません。車輪の大きさ?必要ありません。 GPSナビゲーション?インフォテインメントシステム? emは必要ありません。

使用する動作を定義するだけです。そのためには、多くの場合、相互作用するコードからOOインターフェイスを構築する方が良いと思います。空のインターフェイスから始めて、存在しないメソッドを呼び出すコードの記述を開始します。これが、インターフェイスに必要なメソッドを知る方法です。それを行ったら、これらの動作を実装するクラスの定義を開始します。使用されない動作は無関係であり、定義する必要はありません。

OOの要点は、これらのインターフェースの新しい実装を後で呼び出しコードを変更せずに追加できることです。機能する唯一の方法は、呼び出しコードの必要性がインターフェースに入る内容を決定する場合です。後で考えられる可能性のあるすべての可能性のあるすべての動作を定義する方法はありません。

33
JimmyJames

TL; DR:

すべてのサブクラスに適用され、必要なすべてをカバーする抽象化とメソッドについて考えてください。

まず、eat()の例を見てみましょう。

人間が食べることの前提条件として、人間は手を洗って服を着て食べたがるのは人間の特性です。誰かがあなたと一緒に朝食を食べに来て欲しい場合、あなたはtell彼らが手を洗って着替えるようにしないでください、あなたが彼らを招待するときに彼らは自分でそれをするか、彼らは答えますいいえ、来られません。手を洗っていませんし、まだ服を着ていません。」.

ソフトウェアに戻る:

Humanインスタンスは前提条件なしでは食べられないため、Humaneat()メソッドを実行するwashHands()およびgetDressed()それが行われていない場合。その特殊性を知るのはeat()の呼び出し元としてのあなたの仕事ではありません。頑固な人間の代替策は、前提条件が満たされていない場合に例外(「私は食べる準備ができていない!」)をスローすることで、イライラさせられますが、少なくとも食事がうまくいかなかったことが通知されます。

makeEggs()はどうですか?

あなたの考え方を変えることをお勧めします。おそらく、すべての存在の予定された朝の任務を実行したいと思うでしょう。繰り返しになりますが、発信者として、彼らの職務が何かを知ることはあなたの仕事ではありません。したがって、すべてのクラスが実装するdoMorningDuties()メソッドをお勧めします。

9
Ralf Kleberhoff

答えは非常に簡単です。

期待以上のことができるオブジェクトをどのように処理しますか?

それは目的を果たさないのでそれを扱う必要はありません。インターフェイスは通常、それがどのように使用されるかに応じて設計されます。インターフェイスが手を洗うことを定義していない場合、インターフェイスの呼び出し元としてそれを気にする必要はありません。もしそうなら、あなたはそれを別様に設計したでしょう。

たとえば、朝の時間で、それが動物だけの場合は、eatを呼び出し、それ以外の場合は、最初に、washHands、getDressedを呼び出してから、eatを呼び出します。このケースをどのように処理しますか?

たとえば、疑似コードでは:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

次に、IMorningPerformerを実装してAnimalを食べるだけで実行し、Humanを実装して手を洗って着替えます。 MorningTimeメソッドの呼び出し元は、それが人間または動物の場合、あまり気にすることができません。望んでいるのは、実行された朝のルーチンです。OOのおかげで、各オブジェクトは見事にこれを実行します。

ポリモーフィズムは死ぬ。

それともそうですか?

オブジェクトのタイプを見つける必要があります

なぜそれを想定しているのですか?これは間違った仮定かもしれません。

このケースを処理するための一般的なアプローチはありますか?

はい、通常、注意深く設計されたクラスまたはインターフェース階層で解決されます。上記の例では、与えられた例と矛盾するものは何もないことに注意してください。ただし、執筆の時点で質問に書いていないことをさらに想定しているため、おそらく不満を感じるでしょう。 、そしてこれらの仮定はおそらく違反されています。

あなたがあなたの仮定を引き締めて私がそれらを満足させるために答えを修正することによってウサギの穴に行くことは可能ですが、私はそれが役に立つとは思いません。

優れたクラス階層を設計することは困難であり、ビジネスドメインに対する多くの洞察が必要です。複雑なドメインの場合、適切なモデルに到達するまで、ビジネスドメイン内のさまざまなエンティティがどのように相互作用するかについての理解を深めるため、2、3、またはそれ以上の反復が行われます。

それは単純な動物の例が欠けているところです。私たちは簡単に教えたいと思いますが、私たちが解決しようとしている問題は、より深く理解するまでは明らかではありません。

2
Andrew Savinykh