web-dev-qa-db-ja.com

インターフェイスを使用して適切に分離する方法

動物が動いて衝突する生態系シミュレーションがあります。これが衝突の処理方法です。

public void HandleCollisionBetween(Animal a, Animal b)
    {
        if (a.GetType().Name == b.GetType().Name) { // same type collision
            bool fishCollision = a.GetType().Name == typeof(Fish).Name;
            if (fishCollision) {
                AddAnimal(new Fish());
            }
        }
        else { // bear-fish collision
            Bear bear = a.GetType() == typeof(Bear) ? a as Bear : b as Bear;
            Fish fish = a.GetType() == typeof(Fish) ? a as Fish : b as Fish;

            bear.Eat(fish);
            RemoveAnimal(fish);
        }
    }

このコードは私のエコシステムクラスの一部です。このコードを別のクラスに取り込み、インターフェイスを実装して代わりに使用することが推奨されました。このようにして、テスト可能になります。問題は、2匹の動物が衝突するたびに、生態系に動物を追加または削除する必要があることです。私が考えることができるすべてはこれです:

interface ICollisionHandler {
    void HandleCollisionBetween(Animal a, Animal b, Ecosystem ecosystem);
}

しかし、このエコシステムへの言及は正しくないと思います。私はこのようなことを改善するのに役立つ助けと情報があれば大いに感謝します。

2
Slava Logos

つまり、すぐに、多くのクラスをクロス比較する場合、1つのアプローチは、バックアップして、作成したクラスをクラスとしてではなく、DATAとして再構成する必要があるかどうかを自問することです。交差するアプリ内の接触の数。最初に、クマ/魚のクラスをデータとしてにパックされたクマかフォッシュの詳細で、1つの基本クラスである動物に削減する例を紹介します。ただし、この例をさらに使用して、Animalから継承するクマと魚のクラスを作成することもできますが、衝突の結果を判断するために、クラスとしてそれらを追加することには多くの価値があるとは思いません。これが私の構成要素です。

  • 環境に存在する動物を認識し、衝突が必要なときに呼び出されるpublic関数を持つEcoSystemクラス
  • インスタンスがその種(クマまたは魚)を理解できるようにする動物の基本クラスと、生態系での追跡に役立つ一意の識別子
  • 変更する唯一の理由であるCollisionResolverクラスは、新しい動物タイプが追加された場合に、新しい動物の競合のマッピングを処理するためです。

私はすべてのためのインターフェイスを大いに信じていませんが、この例を挙げます。これは問題を上記のチャンクに分割します。

public class Animal
{
    public string Identifier { get; } 
    public string SpeciesName { get; } 

    public Animal(string animalName)
    {
        SpeciesName = animalName;
        Identifier = Guid.NewGuid().ToString();
    }
}

public class AnimalCollisionResolver
{
    public ICollisionResult ResolveCollision(Animal a, Animal b)
    {
        // Handle the easy case -> reproduction of same species
        if (a.SpeciesName == b.SpeciesName)
            return new AddNewAnimalCollisionResult { NewAnimalToAdd = new Animal(a.SpeciesName) };

        // Handle handle harder case, how to determine which ones eat the others, this will be as complex as you need it

        var animals = new[] { a, b };
        var names = animals.Select(_ => _.SpeciesName);

        // Bear-eats-Fish mapping here
        if (names.Contains("Bear") && names.Contains("Fish"))
        {
            return new RemoveSpecificAnimalCollisionResult
            {
                IdentifierOfAnimalToRemove = animals.Single(_ => _.SpeciesName == "Fish").Identifier
            };
        }

        throw new NotImplementedException("I need to map out the rest of the collision resolutions here.");
    }
}

// This could be an abstract base class and the code would not change, FYI
public interface ICollisionResult { }

public class AddNewAnimalCollisionResult : ICollisionResult
{
    public Animal NewAnimalToAdd { get; set; }
}

public class RemoveSpecificAnimalCollisionResult : ICollisionResult
{
    public string IdentifierOfAnimalToRemove { get; set; }
}

public class Ecosystem
{
    public List<Animal> CurrentAnimals { get; set; }

    public AnimalCollisionResolver CollisionResolver { get; set; }

    public Ecosystem()
    {
        // seed the ecosystem here
        CurrentAnimals = new List<Animal> {
            new Animal("Bear"),
            new Animal("Bear"),
            new Animal("Fish"),
            new Animal("Fish"),
        };

        CollisionResolver = new AnimalCollisionResolver();
    }

    public void HandleCollision(Animal a, Animal b)
    {
        var result = CollisionResolver.ResolveCollision(a, b);

        if (result is AddNewAnimalCollisionResult)
        {
            var addResult = (AddNewAnimalCollisionResult)result;
            CurrentAnimals.Add(addResult.NewAnimalToAdd);
        }

        else if (result is RemoveSpecificAnimalCollisionResult)
        {
            var removeResult = (RemoveSpecificAnimalCollisionResult)result;
            CurrentAnimals = CurrentAnimals.Where(_ => _.Identifier != removeResult.IdentifierOfAnimalToRemove).ToList();
        }

        else
        {
            throw new Exception("Unknown ICollisionResult here");
        }

        PrintOutTheCurrentAnimals();
    }

    public void PrintOutTheCurrentAnimals()
    {
        foreach (var animal in CurrentAnimals)
            Console.WriteLine($"{animal.SpeciesName} {animal.Identifier}");
        Console.WriteLine("");
    }
}

どのように動物を宣言し、誰が誰と衝突するかを決定する方法はわかりませんが、基本を示す例を次に示します。

var ecoSystem = new Ecosystem();
ecoSystem.PrintOutTheCurrentAnimals();

var allBears = ecoSystem.CurrentAnimals.Where(_ => _.SpeciesName == "Bear");
var bear1 = allBears.Take(1).Single();
var bear2 = allBears.Skip(1).Take(1).Single();
var fish1 = ecoSystem.CurrentAnimals.First(_ => _.SpeciesName == "Fish");

ecoSystem.HandleCollision(bear1, fish1);

ecoSystem.HandleCollision(bear1, bear2);
0
Graham