7

私はOOPを初めて使用し、ポリモーフィズムに苦労しました:

class Animal
{
    public virtual void eat()
    {
        Console.Write("Animal eating");
    }
}
class Dog : Animal
{
    public override void eat()
    {
        Console.Write("Dog eating");
    }
}
class Program
{
    public void Main()
    {
        Animal dog = new Dog();
        Animal generic = new Animal();
        dog.eat();
        generic.eat();
    }
}

そのため、印刷されます

Dog eating
Animal eating

しかし、Dog dog = new Dog() のように、animal の代わりに Dog 型を使用しないのはなぜでしょうか? オブジェクトが動物であることがわかっているが、それがどのような動物であるかがわからない場合、これは便利だと思います。これを私に説明してください。

ありがとう

4

7 に答える 7

9

スーパークラスによってサブクラスを参照できます。

Animal dog = new Dog();
Animal cat = new Cat();
Animal frog = new Frog();

List<Animal> animals = new List<Animal>();

animals.add(dog);
animals.add(cat);
animals.add(frog);

foreach(Animal animal in animals)
{   
    Console.WriteLine(animal.eat());
}
于 2012-10-06T02:05:12.340 に答える
8

ポリモーフィズムは、特定の実装を気にせず、包括的なタイプのみを気にする一般的なメソッドのようなものが必要な場合に非常に役立ちます。あなたの動物の例を使用して:

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使用できるはずです。DogAnimalDog

また、依存性逆転の原則を検討することでメリットが得られる場合があります。これは、上記のリポジトリの実装が例として役立ちます。実行中のアプリケーションは、どのタイプ(AnimalRepositoryImplementation)がインターフェースを実装しているかには関係ありません。それが気にする唯一のタイプはインターフェース自体です。実装タイプには、特定の依存関係の実装方法に関して実装アセンブリが使用する追加のパブリックメソッドまたは少なくとも内部メソッドが含まれる場合がありますが、コードの消費には影響しません。各実装は自由に交換でき、呼び出しコードは、より汎用的なインターフェースのインスタンスでのみ提供する必要があります。


補足:私は個人的に、継承が頻繁に使用されていることに気付きました。特に、インスタンス化可能なクラスであってはならないAnimal例のような単純な継承です。Animalアプリケーションのロジックに汎用形式が必要な場合は、インターフェイスまたは抽象クラスにすることができます。しかし、それをするためだけにそれをしないでください。

一般に、 Gang Of Fourの本で推奨されているように、継承よりも構成を優先します。(コピーがない場合は入手してください。)継承を使いすぎないでください。ただし、必要に応じて使用してください。共通の機能がコンポーネントにグループ化され、それぞれがそれらのコンポーネントで構築されている場合、アプリケーションはおそらくより意味がありますか?より一般的に使用される例は、確かにそれから教訓を得ることができます。AnimalAnimalCar

タイプを論理的に定義してください。あなたは今までに書くことができるべきですnew Animal()か?Animalそれの一般的なインスタンスをこれ以上具体的にしないことは理にかなっていますか?確かに違います。Animalしかし、あらゆるもの(フィード、複製、ダイなど)で動作できる一般的な機能を持つことは理にかなっているかもしれません。

于 2012-10-06T02:26:36.847 に答える
2

ポリモーフィズムの「動物」の例は、すべての「種類」の関係をポリモーフィックにモデル化する必要があることを暗示しているように見えるため、階層しか表示されない場合はかなり欠陥があります。これにより、多くの非常に複雑なコードが生成されます。やむを得ない理由がない場合でも、基本クラスはたくさんあります。

この例は、階層を使用するオブジェクトを導入すると、より意味のあるものになります。例えば:

public abstract class Pet
{
    public abstract void Walk();
}

public sealed class Dog : Pet
{
    public override void Walk()
    {
        //Do dog things on the walk
    }
}

public sealed class Iguana : Pet
{
    public override void Walk()
    {
        //Do iguana things on the walk
    }
}

public sealed class PetWalker
{
    public void Walk(Pet pet)
    {
        //Do things you'd use to get ready for walking any pet...
        pet.Walk(); //Walk the pet
        //Recover from the ordeal...
    }
}

PetWalker は、あらゆる種類のペットの歩行に関連するいくつかの共有機能をカプセル化することに注意してください。私たちが行ったことは、仮想メソッドの背後にある Pet 固有の動作のみを分離することです。犬は消火栓におしっこをするかもしれませんし、イグアナは通行人にシューッという音を立てるかもしれませんが、イグアナを歩く行為は、歩いているときの行為から切り離されています。

于 2012-10-06T02:21:07.253 に答える
1

基本クラスで共通の動作を維持する必要があり、特定のサブクラスで異なる動作をオーバーライドするためです。したがって、コードの重複を減らします。

例では、 Dog dog = new Dog() のようにオブジェクトをインスタンス化できます。それはポリモーフィズムとはほとんど関係がありません。ただし、人間、犬など、動物を期待するメソッドに渡そうとする場合は、基本クラスを使用することをお勧めします。

于 2012-10-06T02:06:51.367 に答える
1

また、スーパークラスをサブクラスではなくメソッドに渡すこともできます。次に例を示します。

class GroomService
{
    public void Groom(Animal animal)
    {
        animal.Groom();
    }
}

public void Main()
    {
        GroomService groomService = new GroomService();
        groomService.Groom(dog);
        groomService.Groom(generic);
    }

その結果、コードが少なくなり、保守が容易になります。

于 2012-10-06T02:20:52.737 に答える
0

私が実際にポリモーフィズムを使用した実際の実際的なケースを提供します。これは、 MikeBが説明している特定のケースです。

ポリモーフィズムの実例を検索すると、実生活と多くの同型がレンダリングされ(犬->動物または車->車両など)、「実際にこのコードを作成した」のように十分なREALが得られないため、この話をしたいと思います。

さて、話の前に、ほとんどの場合、私はポリモーフィズムを使用して、サードパーティの開発者によるコードの拡張を可能にしていることを述べておきたいと思います。他の人が実装するためのインターフェースを作成するとします。


物語

約6年前、地図上でルートを検索するアプリケーションを作成しました。これはデスクトップアプリケーションであり、すべての道路を接続して維持し、通りと番号で道順を特定し、ある場所から別の場所へのルートを見つけることができなければなりませんでした。私が実際の地図を追加したことは一度もないことに注意したいのですが、私が使用したのは架空のものであり、デモ用に設計されたものだけでした。

だから私は、ノードがマップの場所にあるグラフを持っています。すべてのノードには、それを地図上に配置するための座標がありましたが、通りの交差点、建物、およびその他のいくつかのノードに特別な場所もありました。

そのために、グラフのノードにインターフェイスを使用しました。このインターフェイスを使用すると、ノードの座標と接続を保存および取得できます。そして、私はこのインターフェースのさまざまな実装を持っています。

名前だけでそれらを見つけることができるはずの建物と特別な場所のための1つの実装。道路の交差点を指定するために使用される場所もあります(したがって、特定の方向を検索するときに家番号をカウントするために開始位置をマークします*)。また、プレゼンテーションのためだけに、道路の形状を説明できるものもあります(道路は必ずしも直線ではないため)。

*:はい、私は実際に各家の数を保存する代わりに、それらを数えるためのコードを書きました。各家の番号を保存するのはナイーブだと思うのはナイーブでした。(または今日は私がそれを考えるのはナイーブかもしれません...気にしないでください)。

描画に関して重要なのは、ノードがどこにあるか、そしてそれを強調表示する必要がある場合(ああ、そうです、ユーザーがそれらの上にポインターを置いたときに強調表示しました)ですが、関連する他の種類のデータを検索するためです。


最後の注意:私の経験では、複雑な継承ツリーを作成しても、メンテナンスにはまったく役立ちません。特に、派生クラスが基本クラスに関して何も追加しない場合、または派生クラスが基本クラスのコードを再利用しない場合。そして、これは一般的な例の大きな問題です。これは、Animal&Dogの例では、DogをAnimalから継承させても何も得られないことが明らかだからです。

于 2012-10-06T02:29:31.310 に答える
0

これは、複数のクラスが同じ親クラスを継承しているか、同じインターフェースを実装している場合に便利です。例えば:

class Cat : Animal {
    public override void eat()
    {
        Console.Write("Cat eating");
    }
}

class Program {

    public void Main() {
        Animal cat = new Cat();
        Animal dog = new Dog();

        cat.eat();
        dog.eat();
    }
}

これは出力されます:

"Cat eating"
"Dog eating"
于 2012-10-06T02:07:06.603 に答える