4

明らかに、ここで問題を単純化しようとしています。基本クラスといくつかの派生クラスがあります。

public class Mammal { }

public class Cat : Mammal { } 

public class Dog : Mammal { }

そしてユーティリティクラス:

public static class AnotherClass
{
    public static void GiveFood(Cat cat) {}
    public static void GiveFood(Dog dog) {}
}

他の場所には、Mammal を受け取るメソッド Feed があり、そこから、AnotherClass で適切なオーバーロードを呼び出したいと考えています。

public void Feed(Mammal mammal) {
    // if mammal is a cat, call the AnotherClass.GiveFood overload for cat,
    // if it's a dog, call the AnotherClass.GiveFood for dog, etc.
}

それを行う1つの方法は、次のようなことです。

public void Feed(Mammal mammal) {
    if (mammal is dog) 
        AnotherClass.GiveFood((Dog)mammal);
    if (mammal is Cat) 
        AnotherClass.GiveFood((Cat)mammal);
}

……でも、実は哺乳類由来の動物が結構いるんです。Feed() でやりたいことを行うためのより良い方法はありますか? Feed() がこれらの「if x is y then call z」ステートメントで満たされた巨大な醜いメソッドになることを避ける方法はありますか?

4

5 に答える 5

8

通常は を使用するのは好きではありませんdynamicが、これは適切だと思うケースの 1 つです。

public void Feed(Mammal mammal) {
  Anotherclass.GiveFood((dynamic)mammal);
}

これにより、事前にタイプを知らなくても、実行時に正しいオーバーロードが解決されます。

厳密に言えば、これはおそらく最速の方法ではないでしょうが、あなたが指摘するように、代替手段は維持するのが本当に面倒であったり、読みにくい場合があります。この場合、動的ディスパッチは洗練されており、将来追加するオーバーロードを自動的に組み込みます。

Chris Sinclair が指摘しているように、catchall メソッドを追加して、無効な呼び出しを検出し、一致するGiveFood()オーバーロードが見つからない場合に受け取るランタイム エラーよりもわかりやすい例外を提供することもできます。

public static class AnotherClass
{
  public static void GiveFood(Cat cat) {}
  public static void GiveFood(Dog dog) {}

  public static void GiveFood(Mammal mammal)
  {
    throw new AnimalNotRecognizedException("I don't know how to feed a " + mammal.GetType().Name + ".");
  }
}
于 2013-05-23T17:10:57.500 に答える
5

食べ物を処理するのは動物の責任であり、餌を与える人ではないと思います。そうしないと、次の問題が発生します。

public void Feed(Mammal mammal) {
    if (mammal is Duck) 
    {
        ((Duck)mammal).PryOpenBeak();
        ((Duck)mammal).InsertFeedingTube();
        ((Duck)mammal).PourDownFood();
    }
}

アヒルは哺乳類ではありませんが。

とにかく、Mammal クラスには抽象メソッドが必要Feed(Food food)であり、動物自体が食物を処理する方法を理解する必要があります。これにより、後で新しい哺乳動物を追加するときに、この新しい哺乳動物の給餌ロジックでフィーダーを更新する必要がなくなります。

@Chris のコメント: その後、動物はメソッドIFoodXEaterを含む適切なインターフェイスを実装できFeed(IFoodX)、フィーダーはそれを検索できますが、その後は振り出しに戻ります:

if (mammal is IFishEater)
{
    ((IFishEater)mammal).Feed(new Fish());
}
于 2013-05-23T17:14:12.637 に答える
2

型マップを作成する労力を気にしない場合は、次のように二重ディスパッチを偽造できます。

[編集] この新しく改良されたバージョンは、サブクラスをより適切に処理します。別の哺乳類クラスから派生したクラスがある場合 (以下の例でPug派生したものなどDog)、クラスのフィーダーを明示的に追加する必要はありません。Pug 基本クラスのフィーダーが自動的に呼び出されますDog

ただし、以下のクラスで示すように、必要に応じて派生クラスに特定のフィーダーを設定できManxます。

使い方dynamicはずっと簡単です。を使用していない場合にどのように見えるかを示したかっただけですdynamic

using System;
using System.Collections.Generic;

namespace Demo
{
    public class Mammal {}

    public class Cat: Mammal {}
    public class Pig: Mammal {}
    public class Dog: Mammal {}

    public class Pug:  Dog {}
    public class Manx: Cat {}

    public static class Feeder
    {
        static readonly Dictionary<Type, Action<Mammal>> map = createMap();

        static Dictionary<Type, Action<Mammal>> createMap()
        {
            return new Dictionary<Type, Action<Mammal>>
            {
                {typeof(Cat),  mammal => GiveFood((Cat)  mammal)},
                {typeof(Dog),  mammal => GiveFood((Dog)  mammal)},
                {typeof(Manx), mammal => GiveFood((Manx) mammal)}
            };
        }

        public static void GiveFood(Mammal mammal)
        {
            for (
                var currentType = mammal.GetType(); 
                typeof(Mammal).IsAssignableFrom(currentType);
                currentType = currentType.BaseType)
            {
                if (map.ContainsKey(currentType))
                {
                    map[currentType](mammal);
                    return;
                }
            }

            DefaultGiveFood(mammal);
        }

        public static void DefaultGiveFood(Mammal mammal)
        {
            Console.WriteLine("Feeding an unknown mammal.");
        }

        public static void GiveFood(Cat cat)
        {
            Console.WriteLine("Feeding the cat.");
        }

        public static void GiveFood(Manx cat)
        {
            Console.WriteLine("Feeding the Manx cat.");
        }

        public static void GiveFood(Dog dog)
        {
            Console.WriteLine("Feeding the dog.");
        }
    }

    class Program
    {
        void test()
        {
            feed(new Cat());
            feed(new Manx());
            feed(new Dog());
            feed(new Pug());
            feed(new Pig());
            feed(new Mammal());
        }

        void feed(Mammal mammal)
        {
            Feeder.GiveFood(mammal);
        }

        static void Main()
        {
            new Program().test();
        }
    }
}
于 2013-05-23T17:35:04.913 に答える
2

私のおすすめ:

ステップ 1 : インターフェイス IMammal を作成する

<!-- language: c# -->
public interface IMammal
{
    void Feed();
}

ステップ 2 : (オプション) 基本クラス BaseMammal を実装する

public class BaseMammal : IMammal
{
    public void Feed()
    {
        Trace.Write("basic mammal feeding");
        //a basic implementation of feeding, common to all or most mammals
    }
}

ステップ 3 : 継承したクラスを実装する

public class Cat : BaseMammal
{
    public void Feed()
    {
        Trace.Write("cat feeding");
        BePicky();//some custom cat like functionality
        base.Feed(); //and afterwards its still just a mammal after all
    }
}

public class Gruffalo : BaseMammal
{
    public void Feed()
    {
        Trace.Write("Gruffalo feeding");
        WeirdWayOfEating();//the base implementation is not appropriate
    }
}

ステップ 4 : 使う!(ランダムな例が含まれています)

List<IMammal> pets = new List<IMammal>()
    {
        new Cat(catValues),
        new Gruffalo(gruffaloValues)
    };

foreach(var pet in pets)
{
    pet.Feed();
}

各動物は、独自の実装によって供給されます。見よ、複雑なコードがシンプルになりました。また、これと他の多くの概念について説明している「Head First Design Patterns」を読むことをお勧めします。http://www.amazon.co.uk/Head-First-Design-Patterns-Freeman/dp/0596007124

于 2013-05-23T17:59:02.020 に答える
1

複数の動物が摂食行動を共有している場合は、戦略パターンを使用して摂食行動をインターフェースにカプセル化し、動物のグループごとに各行動を具体的に実装することをお勧めします

継承の代わりに構成を使用します

これについては、ヘッドファーストのデザインパターンを確認してください。あなたの場合、それは良い実装になると思います

于 2013-05-23T17:47:09.190 に答える