.Net 4.0 Оптимизированный код для рефакторинга существующих условий "если" и оператора "есть"

У меня есть следующий код C#. Работает отлично; но GetDestination() метод загроможден несколькими if Условия использования оператора.

В.Net 4.0 (или выше), каков наилучший способ избежать этих "если" условий?

РЕДАКТИРОВАТЬ: Роль является частью бизнес-модели, а назначение является просто артефактом одного конкретного приложения, использующего эту бизнес-модель.

КОД

public class Role { }
public class Manager : Role { }
public class Accountant : Role { }
public class Attender : Role { }
public class Cleaner : Role { }
public class Security : Role { }

class Program
{
    static string GetDestination(Role x)
    {
        string destination = @"\Home";

        if (x is Manager)
        {
            destination = @"\ManagerHomeA";
        }

        if (x is Accountant)
        {
            destination = @"\AccountantHomeC";
        }

        if (x is Cleaner)
        {
            destination = @"\Cleaner";
        }

        return destination;

    }

    static void Main(string[] args)
    {
        string destination = GetDestination(new Accountant());
        Console.WriteLine(destination);
        Console.ReadLine();
    }
}

РЕКОМЕНДАЦИИ

  1. Словарь с делегатами разных типов: более чистые, не строковые имена методов?
  2. Джон Скит: заставляет задуматься и исследует делегатов
  3. if-else против switch против словаря делегатов
  4. Словарь с делегатом или переключателем?
  5. Выражение и делегат в C#

7 ответов

Решение

Подход 1 (выбранный): использование dynamic ключевое слово для реализации multimethods / double dispatch

Подход 2: Используйте dictionary избежать if блоки, как указано в ответе Джона Скита ниже.

Подход 3: Используйте HashList с delegates если есть условие, отличное от равенства (например, если input < 25). Посмотрите, как реорганизовать набор операторов <=, >= if...else в словарь или что-то в этом роде

Apporach 4: Виртуальные функции, как указано в ответе MarcinJuraszek ниже.

Подход MultiMethods / Double Dispatch с использованием динамического ключевого слова

Обоснование: здесь алгоритм изменяется в зависимости от типа. То есть, если вводом является Бухгалтер, выполняемая функция отличается от функции Менеджера.

    public static class DestinationHelper
    {
        public static string GetDestinationSepcificImplm(Manager x)
        {
            return @"\ManagerHome";
        }

        public static string GetDestinationSepcificImplm(Accountant x)
        {
            return @"\AccountantHome";
        }

        public static string GetDestinationSepcificImplm(Cleaner x)
        {
            return @"\CleanerHome";
        }
    }

   class Program
    {
        static string GetDestination(Role x)
        {

            #region Other Common Works
            //Do logging
            //Other Business Activities
            #endregion

            string destination = String.Empty;
            dynamic inputRole = x;
            destination = DestinationHelper.GetDestinationSepcificImplm(inputRole);
            return destination;
        }

        static void Main(string[] args)
        {
            string destination = GetDestination(new Security());
            Console.WriteLine(destination);
            Console.WriteLine("....");
            Console.ReadLine();
        }

    }

Вот один из вариантов:

private static readonly Dictionary<Type, string> DestinationsByType =
    new Dictionary<Type, string> 
{
    { typeof(Manager), @"\ManagerHome" },
    { typeof(Accountant), @"\AccountantHome" },
    // etc
};

private static string GetDestination(Role x)
{
    string destination;
    return DestinationsByType.TryGetValue(x.GetType(), out destination)
        ? destination : @"\Home";
}

Замечания:

  • Это не справляется с нулевыми параметрами. Не ясно, нужно ли вам это на самом деле. Вы можете легко добавить нулевую обработку, хотя.
  • Это не копируется с наследованием (например, class Foo : Manager); Вы можете сделать это, подняв иерархию наследования, если это необходимо

Вот версия, которая имеет дело с обоими этими пунктами ценой сложности:

private static string GetDestination(Role x)
{
    Type type = x == null ? null : x.GetType();
    while (type != null)
    {
        string destination;
        if (DestinationsByType.TryGetValue(x.GetType(), out destination))
        {
            return destination;
        }
        type = type.BaseType;
    }
    return @"\Home";
}

РЕДАКТИРОВАТЬ: было бы чище, если Role сам был Destination имущество. Это может быть либо виртуальным, либо предоставленным RoleБазовый класс.

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

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

Имеющий virtual свойство, которое будет переопределено в производных классах, должно помочь:

class Role
{
    public virtual string Destination { get { return "Home"; } }
}
class Manager : Role
{
    public override string Destination { get { return "ManagerHome;"; } }
}
class Accountant : Role
{
    public override string Destination { get { return "AccountantHome;"; } }
}
class Attender : Role
{
    public override string Destination { get { return "AttenderHome;"; } }
}
class Cleaner : Role
{
    public override string Destination { get { return "CleanerHome;"; } }
}
class Security : Role { }

Я не сделал свойство абстрактным, чтобы обеспечить дефолт Home значение, когда оно не переопределено в производном классе.

Использование:

string destination = (new Accountant()).Destination;
Console.WriteLine(destination);
Console.ReadLine();

Это строго типизированный, обязательный язык, так if утверждения и проверка типов будут происходить.

Сказав это, вы рассмотрели virtual метод на Role которые можно переопределить, чтобы указать пункт назначения string?

Еще одна альтернатива, таблица поиска!

Dictionary<Type, string> paths = new Dictionary<TYpe, string>()
{
    { typeof(Manager),  @"\ManagerHomeA" }
    { typeof(Accountant),  @"\AccountantHomeC" }
    { typeof(Cleaner),  "Cleaner" }
}

string path = @"\Home";
if(paths.ContainsKey(x.GetType())
    path = paths[x];

Один из способов сделать это - использовать карту вместо if:

//(psuedocode)
private Dictionary<Type, string> RoleMap;

void SomeInitializationCodeThatRunsOnce()
{
  RoleMap.Add(typeof(Manager), @"\ManagerHome");
  RollMap.Add(typeof(Accountant), @"\AccountantHome");
  // ect...
}

string GetDestination(Role x)
{
  string destination;
  if(!RoleMap.TryGet(x.GetType(), out destination))
    destination = @"\Home";
  return destination;
}

Дополнительное чтение: http://www.hanselman.com/blog/BackToBasicsMovingBeyondForIfAndSwitch.aspx

Роль должна иметь виртуальную функцию, которая будет возвращать пункт назначения:

public virtual string GetDestination()
{
     return "Home";
}

И все классы должны переопределить эту функцию и вернуть правильную строку. Тогда в коде вы должны иметь:

var role = new Accountant();
string destination = role.GetDestination();

Надеюсь, это поможет. Там могут быть опечатки, я пишу с головы.

Вы можете использовать определение интерфейса или абстрактный метод / свойство

с интерфейсом:

public interface IDestinationProvider
{
    sting Destination { get; }
}

string GetDestination(Role role)
{
    var provider = role as IDestinationProvider;
    if (provider != null)
        return provider.Destination;
    return "Default";
}

с абстрактным базовым классом

abstract class Role 
{ 
    public abstract string GetDestination();
}

class Manager : Role
{
    public virtual string GetDestination() { return "ManagerHomeA"; }
}

string GetDestination(Role role)
{
    return @"\" + role.GetDestination();
}

или с атрибутами:

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DestinationAttribute : Attribute
{
    public DestinationAttribute() { this.Path = @"\Home"; }
    public string Path { get; set; }
}

[Destination(Path = @"\ManagerHome")]
public class Manager : Role { }

string GetDestination(Role role)
{
    var destination = role.GetType().GetCustomAttributes(typeof(DestinationAttribute), true).FirstOrDefault();
    if (destination != null)
        return destination.Path;

    return @"\Home";
}
Другие вопросы по тегам