【问题标题】:.Net 4.0 Optimized code for refactoring existing "if" conditions and "is" operator.Net 4.0 重构现有“if”条件和“is”运算符的优化代码
【发布时间】:2014-01-21 14:54:55
【问题描述】:

我有以下 C# 代码。它工作正常;但是GetDestination() 方法使用is operator 会被多个if 条件所干扰。

在 .Net 4.0(或更高版本)中,避免这些“if”条件的最佳方法是什么?

编辑:角色是业务模型的一部分,而目标纯粹是使用该业务模型的特定应用程序的工件。

代码

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. 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#

【问题讨论】:

  • 您可以将其替换为 string.Format(@"\{0}Home", x.GetType().Name)。如果这是一个好主意是另一个取决于您的设计的问题。
  • 为什么不让 GetDestination() 成为 Role 的方法并覆盖它?
  • 您展示的任何类是否有可能被进一步子类化?
  • @Rik 不同意。这显然只是一个代码示例,目的是询问 OOP 技术。
  • @Lijo:好吧,如果你有一个 Typestring 的字典,你需要知道对象的类型会有一个完全匹配的......见在可能有子类化的情况下,我对替代方案的回答。

标签: c# oop design-patterns double-dispatch multimethod


【解决方案1】:

拥有将在派生类中被覆盖的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();

【讨论】:

  • 谢谢.. 但是这在以下情况下不起作用 - Role is part of the business model, and the destination is purely an artifact of one particular application using that business model.
  • @Lijo 你应该接受乔恩的回答!
【解决方案2】:

这是一种选择:

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 属性会更干净。这可以是虚拟的,也可以由 Rolebase 类提供。

然而,目的地可能真的不是Role 应该关注的事情 - 可能Role 是业务模型的一部分,目的地纯粹是一个特定应用程序使用的工件这种商业模式。在这种情况下,您不应该将其放入Role,因为这会破坏关注点分离。

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

【讨论】:

  • 这不是OO。如果已经提出更好的建议,请问您为什么要提供这样的解决方案?
  • @MichalB.:你认为哪个更好?如果Role 可以自己提供它会更好,但它可能不合适。我将编辑我的答案以解释我的意思。
  • 我认为 MarcinJuraszek 的回答是最干净的。当 Role 类不应该提供目标时,它可能是不合适的。但在这种情况下,应该给出一些解释并提出解决方案(例如 DestinationProvider)
  • @MichalB.:我现在已经用解释更新了我的答案——但没有迹象表明完全需要一个单独的“目的地提供者”接口。当我们没有任何要求建议时,这听起来像是过度设计。
  • @Lijo:说实话,如果不了解更多实际需求,很难说。您可能想将代表放入字典中...或者您可能不想:(
【解决方案3】:

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

方法 2:使用 dictionary 来避免 if 块,如下 Jon Skeet 的回答中所述。

方法 3: 如果存在不相等的条件(例如,如果输入 HashList 和 delegates。参考how to refactor a set of <= , >= if...else statements into a dictionary or something like that

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

    }
【解决方案4】:

这是一种强类型的命令式语言,因此if 语句和类型检查将会发生。

话虽如此,您是否考虑过Role 上的virtual 方法可以被覆盖以提供目标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];

【讨论】:

  • 正在输入同样的内容,这似乎更 OO + 1
  • 您的编辑/添加使您的答案变得更糟 imo - 为什么您不按照您的建议去做?
  • @BrokenGlass 原来 Skeet 先生给出了相同的答案。为什么我添加它?目的地可能是一个比Role 需要了解的更专业的概念。因此,将其分开。
  • @BrokenGlass 你不能说这更糟,你不知道程序设计和这段代码的目的。如果目的地与 UI 框架中的路由有关,您可能不想在 Role 或派生类本身中解决此问题,因为它们看起来像应该与 UI 无关的业务对象。如果您想要“更多 OO”,请将此代码粘贴到 RouteEvaluator 类或类似的类中。
  • @Gusdor:我只是在我的答案中添加了一些 Role 可能不想知道的东西:)
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

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

    所有的类都应该重写这个函数并返回正确的字符串。然后在你的代码中:

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

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

    【讨论】:

    • 哦对了,我没看出来,角色不明他也想回东西。抽象不是一个好的选择。我会选择虚拟的,而且确实 - 属性会更好。
    【解决方案7】:

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

    带接口:

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

    【讨论】:

    • 好像有默认目的地,所以方法应该是virtual而不是abstract
    • 你是对的,没有注意到这一点。另一种方法是坚持使用抽象,并为每个实现使用return Role.DefaultDestination 之类的东西。
    猜你喜欢
    • 1970-01-01
    • 2019-07-04
    • 1970-01-01
    • 2013-01-22
    • 2013-03-16
    • 2022-01-24
    • 2022-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多