ポリモーフィズムは、特定の実装を気にせず、包括的なタイプのみを気にする一般的なメソッドのようなものが必要な場合に非常に役立ちます。あなたの動物の例を使用して:
public static void Main()
{
var animals = new List<Animal>();
animals.Add(new Dog());
animals.Add(new Cat());
foreach (var animal in animals)
Feed(animal);
}
public static void Feed(Animal animal)
{
animal.Eat();
}
この方法では、どのような動物を飼うかは気にせず、餌を与えようとするだけであることに注意してください。たぶん、それが目に見えるすべてのものをむさぼり食うようにDog
実装します。Eat()
たぶんCat()
、それが一口食べて立ち去るようにそれを実装します。多分Fish()
それを食べ過ぎて死ぬようにそれを実行します。メソッド自体はどちらを取得するかを気にせず、それらを受け入れるメソッドを変更することなく、Animal
簡単にタイプを追加できます。Animal
(これに関連するのは戦略パターンです。)
逆に、実装されているものに関係なく、メソッドが一般的な型を返すようにしたい場合があります。私が使用する一般的な例は次のとおりです。
public interface AnimalRepository
{
IEnumerable<Animal> GetAnimals();
}
これは、実際には2つの方法でポリモーフィズムを使用します。Animal
まず、返されるsの列挙は、どのタイプでもかまいません。この場合、どの呼び出しコードもどちらがどちらであるかを気にせず、より一般的な方法でそれらを使用します(前の例など)。さらに、実装IEnumerable
するものはすべて返品できます。
したがって、たとえば、LINQtoSQLを使用するこのインターフェイスの実装があります。
public class AnimalRepositoryImplementation : AnimalRepository
{
public IEnumerable<Animal> GetAnimals()
{
return new DBContext().Animals;
}
}
これはを返しますIQueryable
。ただし、メソッドを呼び出すものは何でも、それがであるかどうかは気にしませんIQueryable
。の機能のみを使用しますIEnumerable
。
または、模擬テスト用の別の実装があります。
public class AnimalRepositoryImplementation : AnimalRepository
{
private IList<Animal> animals = new List<Animal>();
public IEnumerable<Animal> GetAnimals()
{
return animals;
}
}
これは、を返します。これも、呼び出し元のコードが使用するすべてであるためIList
、より一般的なものに変形されます。IEnumerable
これらは、共変性および反変性とも呼ばれます。上記を返す場合IEnumerable
、タイプはより具体的な(IQueryable
およびIList
)からより一般的な(IEnumerable
)に移動しました。より具体的な型は型階層内のより一般的な型のインスタンスでもあるため、変換なしでこれを行うことができました。
また、これに関連するのは、プログラムに変更を加えることなく、タイプの任意のサブタイプをその親タイプとして使用できることを示すリスコフの置換原則です。つまり、aDog
がのサブタイプである場合、それがaであることを知らなくても、または特別な考慮を払うことなく、常にaをとしてAnimal
使用できるはずです。Dog
Animal
Dog
また、依存性逆転の原則を検討することでメリットが得られる場合があります。これは、上記のリポジトリの実装が例として役立ちます。実行中のアプリケーションは、どのタイプ(AnimalRepositoryImplementation
)がインターフェースを実装しているかには関係ありません。それが気にする唯一のタイプはインターフェース自体です。実装タイプには、特定の依存関係の実装方法に関して実装アセンブリが使用する追加のパブリックメソッドまたは少なくとも内部メソッドが含まれる場合がありますが、コードの消費には影響しません。各実装は自由に交換でき、呼び出しコードは、より汎用的なインターフェースのインスタンスでのみ提供する必要があります。
補足:私は個人的に、継承が頻繁に使用されていることに気付きました。特に、インスタンス化可能なクラスであってはならないAnimal
例のような単純な継承です。Animal
アプリケーションのロジックに汎用形式が必要な場合は、インターフェイスまたは抽象クラスにすることができます。しかし、それをするためだけにそれをしないでください。
一般に、 Gang Of Fourの本で推奨されているように、継承よりも構成を優先します。(コピーがない場合は入手してください。)継承を使いすぎないでください。ただし、必要に応じて使用してください。共通の機能がコンポーネントにグループ化され、それぞれがそれらのコンポーネントで構築されている場合、アプリケーションはおそらくより意味がありますか?より一般的に使用される例は、確かにそれから教訓を得ることができます。Animal
Animal
Car
タイプを論理的に定義してください。あなたは今までに書くことができるべきですnew Animal()
か?Animal
それの一般的なインスタンスをこれ以上具体的にしないことは理にかなっていますか?確かに違います。Animal
しかし、あらゆるもの(フィード、複製、ダイなど)で動作できる一般的な機能を持つことは理にかなっているかもしれません。