Как улучшить этот метод, используя полиморфизм + перегрузка, чтобы уменьшить IS (проверка типа)? - PullRequest
6 голосов
/ 27 марта 2012

Например,

BaseClass MyBase()
{
    public int Add(BaseClass next)
    {
        if (this is InheritedA && next is InheritedA)
            return 1;
        else if (this is InheritedA && next is InheritedB)
            return 2;
        else if (this is InheritedB && next is InheritedA)
            return 3;
        else if (this is InheritedB && next is InheritedB)
            return 4;      
     }
}

, где InheritedA и InheritedB являются его унаследованными классами.На самом деле унаследованных классов больше, и Add возвращает разные результаты в зависимости от порядка и типов его операнда.

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

Например,

InheritedA myA()
{
    public override int Add(BaseClass next)
    {
        return next.AddTo(this);
    }
}

Теперь я должен поместить AddTo в BaseClass и переопределить его в унаследованномкласс также.

InheritedA myA()
{
    public override int AddTo(InheritedA next) { return 1; }
    public override int AddTo(InheritedB next) { return 3; }
}

BaseClass myBase()
{
    public abstract int Add(BaseClass next);
    public abstract int AddTo(InheritedA next);
    public abstract int AddTo(InheritedB next);
}

Есть ли лучший способ сделать это?

Ответы [ 2 ]

9 голосов
/ 27 марта 2012

Шаблон, который вы реализуете, называется двойная виртуальная отправка .

A одиночная виртуальная отправка выбирает, какой метод вызывать на основе типа времени выполнения получателя и типа времени компиляции аргументов , Это традиционная виртуальная рассылка:

abstract class Animal {}
class Tiger : Animal {}
class Giraffe : Animal {} 
class B
{
    public virtual void M(Tiger x) {}
    public virtual void M(Animal x) {}
}
class D : B
{
    public override void M(Tiger x) {}
    public override void M(Animal x) {}
}
...
B b = whatever;
Animal a = new Tiger();
b.M(a);

Какой метод вызывается? B.M(Tiger) и D.M(Tiger) не выбраны; мы отклоняем их, основываясь на типе аргумента время компиляции , то есть Animal. Но мы выбираем, вызывать ли B.M(Animal) или D.M(Animal) во время выполнения, в зависимости от того, whatever равен new B() или new D().

Двойная виртуальная диспетчеризация выбирает метод для вызова на основе типов времени выполнения двух вещей . Если C # поддерживает двойную виртуальную диспетчеризацию, чего не происходит, тогда диспетчеризация во время выполнения переходит к B.M(Tiger) или D.M(Tiger), даже если тип аргумента во время компиляции - Animal .

Однако

C # 4 поддерживает динамическую диспетчеризацию. Если вы скажете

dynamic b = whatever;
dynamic a = new Tiger();
b.M(a);

Тогда анализ M будет выполнен полностью во время выполнения с использованием типов времени выполнения b и a. Это значительно медленнее, но работает.

В качестве альтернативы, если вы хотите сделать двойную виртуальную диспетчеризацию и получить как можно больше анализа во время компиляции, тогда стандартным способом для этого является реализация Visitor Pattern , который вы можете легко найти в интернете.

1 голос
/ 27 марта 2012

Как было предложено в комментариях, если вы можете назначить постоянное значение каждому производному, тогда вы можете построить гораздо более чистую реализацию, чем я здесь описываю, просто имея виртуальное свойство с именем Value или аналогичное, чтоиспользуется для добавления.

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

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

public class BaseClass
{
  private static readonly Dictionary<int, int> addResults = new Dictionary<int, int>();

  static BaseClass()
  {
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildA)), 1);
    addResults.Add(CreateKey(typeof(ChildA), typeof(ChildB)), 2);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildA)), 3);
    addResults.Add(CreateKey(typeof(ChildB), typeof(ChildB)), 4);
  }

  public static int CreateKey(Type a, Type b)
  {
    return (String.Concat(a.Name, b.Name).GetHashCode());
  }

  public int Add(BaseClass next)
  {
    var result = default(int);

    if (!addResults.TryGetValue(CreateKey(this.GetType(), next.GetType()), out result))
    {
      throw new ArgumentOutOfRangeException("Unknown operand combination");
    }

    return result;
  }
}

public class ChildA : BaseClass {}
public class ChildB : BaseClass {}
...