Реализация коллекции базовых классов - нужно ли мне столько удручать в производных классах?

1

Я реализую набор классов, которые имеют следующий шаблон:

 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.

Это работает отлично, но в проекте, над которым я работаю, эта ситуация возникает снова и снова и приводит к довольно уродливому коду, где методы производных классов полны нисходящих ссылок на базовые классы. Это нормально? Или есть лучший общий дизайн, который был бы чище?

Теги:
casting
design

3 ответа

0
Лучший ответ

Я попытаюсь немного подробнее остановиться на обсуждениях в комментариях и, надеюсь, предоставит вам обобщенное, полиморфное решение. Общая идея функционально эквивалентна тому, что предлагала Карра, но, надеюсь, это поможет вам применить эти концепции в области вашей реальной проблемы.

Чтобы работать с вашей абстракцией вашей проблемы; предположим, что ваш абстрактный базовый класс определен следующим образом:

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.

В этом суть полиморфизма - это не до игрушки, чтобы определить, что делает собака; это до собаки. Вся игрушка должна знать, что собака может ее поймать.

Однако в целом мне не нравится, что два класса, которые расширяют независимые базовые классы, логически связаны (параллельные иерархии, как указывает Эрик), если только один из них явно не создает другое, хотя невозможно предложить какие-либо реальные советы по этому не видя свою фактическую архитектуру решения.

  • 0
    Спасибо @Ant P. В большинстве ситуаций это было бы лучшим ответом на мой вопрос, хотя в моем конкретном случае я, вероятно, не смогу избежать параллельных иерархий. Спасибо за ваше время.
3

Здесь один из способов исправить это:

public abstract class Animal 
{
    public abstract void MakeNoise();
}

Пусть собака реализует MakeNoise, и вы можете просто назвать это в своем классе Toy:

public void Throw()
{
    Owner.MakeNoise();
}
  • 1
    +1 - это в основном то, что я собирался опубликовать. Если MakeNoise не является семантически правильным, тогда выполните Owner.Catch и вызовите Bark внутри Dog.Catch . Это случай правильного полиморфизма, а не использования дженериков, как в ответе Шримара.
  • 0
    Мой выбор имен методов, возможно, немного вводил в заблуждение. Я согласен, что в этом конкретном примере поиск более общей формы Bark () будет правильным подходом (спасибо Карре). Однако в моем реальном проекте у меня есть много методов, которые не являются общими. Так есть ли способ улучшить это, если Bark () не является видом деятельности, который делают все животные?
Показать ещё 2 комментария
3

Вы можете сделать класс 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 по пути.

  • 0
    Спасибо за ответ, Шрирам. Иногда классы имеют ссылки на классы одного и того же типа ... для расширения приведенного выше примера, если у Animal было свойство Parent типа Animal , а затем было действие, которое собаки делали только со своими родителями (например, Bark() называемое Parent.ResponseBark() ). Чтобы смоделировать это с помощью дженериков, класс Animal должен быть универсальным (т. public class Animal<TAnimal> where TAnimal: Animal ), и тогда вы будете объявлять новых собак как Dog Fido = new Dog<Dog>() . Почему-то это не кажется мне логичным. Я пропускаю лучший способ сделать это?
  • 0
    @ user3676281 Я никогда не просил изменить животных на общие, и я не буду. Это на самом деле другой вопрос. Я предлагаю вам задать еще один вопрос для этого с более подробной информацией. Полиморфизм может помочь вам, если у вас нет ничего специфического для конкретного животного.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню