Я реализую набор классов, которые имеют следующий шаблон:
public class Animal {}
public abstract class AnimalToy
{
public AnimalToy(Animal owner)
{
Owner = owner;
}
public Animal Owner { get; private set; }
/* Various methods related to all toys that use the Owner property */
}
public class Dog: Animal
{
public void Bark() {}
}
public class PlasticBone: AnimalToy
{
public PlasticBone(Dog owner) : base(owner) {}
public void Throw()
{
((Dog)Owner).Bark();
}
}
AnimalToy
со свойством, которое является ссылкой на другой базовый класс Animal
.PlasticBone
Dog
и PlasticBone
для этого класса Dog
. PlasticBone
- это игрушка, которая действительна только для собак, и на самом деле конструктор ограничивает владельца PlasticBone
типом Dog
.PlasticBone
имеет метод Throw()
, уникальный для этого класса, который использует метод Dog
(Bark()
), который является уникальным для класса Dog
. Поэтому перед тем, как я получу доступ к нему, мне нужно передать родовую собственность Owner
на Dog
.Это работает отлично, но в проекте, над которым я работаю, эта ситуация возникает снова и снова и приводит к довольно уродливому коду, где методы производных классов полны нисходящих ссылок на базовые классы. Это нормально? Или есть лучший общий дизайн, который был бы чище?
Я попытаюсь немного подробнее остановиться на обсуждениях в комментариях и, надеюсь, предоставит вам обобщенное, полиморфное решение. Общая идея функционально эквивалентна тому, что предлагала Карра, но, надеюсь, это поможет вам применить эти концепции в области вашей реальной проблемы.
Чтобы работать с вашей абстракцией вашей проблемы; предположим, что ваш абстрактный базовый класс определен следующим образом:
public abstract class Animal
{
public abstract void Catch();
}
Это сдвиг в семантике абстрактного метода; у вас больше нет абстрактного метода Bark
, у вас просто есть метод Catch
, семантика которого определяет его как, по существу "реагирует на что-то, что бросается". Как Animal
реагирует полностью на животное.
Итак, вы тогда определяете Dog
так:
public class Dog : Animal
{
public override void Catch()
{
Bark();
}
public void Bark()
{
// Bark!
}
}
И измените метод Throw
на:
public void Throw()
{
Owner.Catch();
}
Теперь, Bark
специфичен для Dog
, тогда как общий метод Catch
универсален для всех Animal
. Класс Animal
теперь указывает, что его подклассы должны реализовывать метод, который реагирует на предметы, которые его бросают. Тот факт, что Dog
лает, полностью зависит от Dog
.
В этом суть полиморфизма - это не до игрушки, чтобы определить, что делает собака; это до собаки. Вся игрушка должна знать, что собака может ее поймать.
Однако в целом мне не нравится, что два класса, которые расширяют независимые базовые классы, логически связаны (параллельные иерархии, как указывает Эрик), если только один из них явно не создает другое, хотя невозможно предложить какие-либо реальные советы по этому не видя свою фактическую архитектуру решения.
Здесь один из способов исправить это:
public abstract class Animal
{
public abstract void MakeNoise();
}
Пусть собака реализует MakeNoise, и вы можете просто назвать это в своем классе Toy:
public void Throw()
{
Owner.MakeNoise();
}
MakeNoise
не является семантически правильным, тогда выполните Owner.Catch
и вызовите Bark
внутри Dog.Catch
. Это случай правильного полиморфизма, а не использования дженериков, как в ответе Шримара.
Вы можете сделать класс AnimalToy
общим, благодаря чему вы можете избежать кастинга.
public abstract class AnimalToy<TAnimal> where TAnimal : Animal
{
public AnimalToy(TAnimal owner)
{
Owner = owner;
}
public TAnimal Owner { get; private set; }
}
public class PlasticBone: AnimalToy<Dog>
{
public PlasticBone(Dog owner) : base(owner) {}
public void Throw()
{
Owner.Bark();
}
}
Стоит отметить, что ((Dog)Owner)
не взвинчен, он называется downcast. upcast по пути.
Animal
было свойство Parent
типа Animal
, а затем было действие, которое собаки делали только со своими родителями (например, Bark()
называемое Parent.ResponseBark()
). Чтобы смоделировать это с помощью дженериков, класс Animal
должен быть универсальным (т. public class Animal<TAnimal> where TAnimal: Animal
), и тогда вы будете объявлять новых собак как Dog Fido = new Dog<Dog>()
. Почему-то это не кажется мне логичным. Я пропускаю лучший способ сделать это?