3

I have following C# code. It works fine; but the GetDestination() method is cluttered with multiple if conditions by using is operator.

In .Net 4.0 (or greater) what is the best way to avoid these “if” conditions?

EDIT: Role is part of the business model, and the destination is purely an artifact of one particular application using that business model.

CODE

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();
    }
}

REFERENCES

  1. Dictionary<T,Delegate> with Delegates of different types: Cleaner, non string method names?
  2. Jon Skeet: Making reflection fly and exploring delegates
  3. if-else vs. switch vs. Dictionary of delegates
  4. Dictionary with delegate or switch?
  5. Expression and delegate in c#
4

7 回答 7

5

拥有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();
于 2014-01-21T15:00:00.993 回答
5

这是一个选项:

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";
}

笔记:

  • 这不适用于空参数。目前尚不清楚您是否真的需要它。不过,您可以轻松添加 null 处理。
  • 这不会与继承一起复制(例如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,因为这会破坏关注点分离。

基本上,在不了解更多上下文的情况下,我们无法判断哪种解决方案最合适——这在设计问题上经常是这样。

于 2014-01-21T15:02:09.513 回答
4

方法一(已选): 使用dynamic关键字实现multimethods/double dispatch

方法 2:使用 adictionary来避免if下面 Jon Skeet 的回答中提到的阻塞。

方法 3:如果存在除相等以外的条件(例如,如果 input < 25),则使用HashListwith 。delegates请参阅如何将一组 <= , >= if...else 语句重构为字典或类似的东西

Apporach 4:下面 MarcinJuraszek 的回答中提到的虚拟功能。

使用动态关键字的 MultiMethods / Double Dispatch 方法

基本原理:这里的算法根据类型而变化。也就是说,如果输入是 Accountant,则要执行的功能与 Manager 不同。

    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();
        }

    }
于 2014-01-22T13:54:01.280 回答
2

这是一种强类型的命令式语言,因此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];
于 2014-01-21T14:57:57.093 回答
1

角色应该有一个可以返回目的地的虚函数:

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

并且所有类都应该覆盖这个函数并返回正确的字符串。然后在代码中您将拥有:

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

我希望这会有所帮助。可能有错别字,我是从头开始写的。

于 2014-01-21T14:59:13.983 回答
1

一种方法是使用地图而不是 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

于 2014-01-21T15:01:27.577 回答
0

您可以使用接口定义或抽象方法/属性

带接口:

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";
}
于 2014-01-21T15:01:48.847 回答