【问题标题】:How can I make a switch statement more object oriented? [duplicate]如何使 switch 语句更加面向对象? [复制]
【发布时间】:2015-09-27 23:24:30
【问题描述】:

我正在尝试重构这样的 switch 语句:

private void Validate(DataTypeEnum dataType, string value, ...)
{
    switch(dataType)
    {
        case DataTypeEnum.Number:
            var typedValue = int.Parse(value);
            //Validation of typedValue
            break;
        case DataTypeEnum.Decimal:
            var typedValue = Decimal.Parse(value);
            //Validation of typedValue
            break;
        case DataTypeEnum.DateTime:
            var typedValue = DateTime.Parse(value);
            //Validation of typedValue
            break;
    }
}

我想摆脱 switch 语句并以某种方式将其替换为更面向对象的构造。有什么建议吗?

我的理想是这样的:

private void Validate(DataTypeEnum dataType, string value, ...)
{
    Validate(value);
}
private void (Decimal value)
{
    //Validate
}
private void (DateTime value)
{
    //Validate
}

有什么优雅的方法可以解决这个问题吗?

【问题讨论】:

  • 这个关于the visitor pattern的问题对你有帮助
  • durron597:此时,我没有类型化对象,我只有一个需要解析的字符串和一些类型的枚举。
  • 那么,除了switch 声明之外,您为什么还要打扰其他任何事情呢?如果你想要面向对象,你需要真正的对象。

标签: c# .net


【解决方案1】:

使用多态性。

例子:

public class DataType
{
    public virtual void Validate()
    {
        Console.WriteLine("Performing base class validation tasks");
    }
}

class Foo : DataType
{
    public override void Validate()
    {
        // Code to validate a foo...
        Console.WriteLine("Validating a foo");
    }
}
class Bar : DataType
{
    public override void Validate()
    {
        // Code to validate a bar...
        Console.WriteLine("Validating a bar");
    }
}


List<DataType> datatypes = new List<DataType>();
datatypes.Add(new Foo());
datatypes.Add(new Barr());

foreach (DataType s in datatypes)
{
    s.Validate();
}

【讨论】:

  • 这与数据验证有什么关系?问题不在于行为,而在于数据。
  • @Fabjan,来吧。最初的问题不是关于 验证。它只是以此为例。这个答案使用了另一个例子。
  • 我建议进行编辑以对齐示例。
  • 也许这是好的设计,但我不认为这是答案。 OP 将字符串作为对象发送到验证。但我在这里看不到对象值的任何用途。我知道你可以像这样添加到列表中......但是当你不知道在 Validate 方法之前发生了什么时,你不能给出这样的建议。
  • OP 使用枚举 DataTypeEnum 而不是类,它还将如何验证 int、string、bool 等类型?仅当我们使用用户类时,这是一个很好的答案
【解决方案2】:

此外,虽然有些人可能认为这根本不是 OOP,但您可以使用 dynamic 重载来处理此问题:

public bool ValidateAny(dynamic val)
{
  return Validate(val);
}

private bool Validate(string val) { ... }
private bool Validate(int val) { ... }
private bool Validate(decimal val) { ... }

private bool Validate(object val) { ... } // This is the fallback

基本上,这与通常的重载解析相同,但它在运行时执行,具体取决于ValidateAnyval 的运行时类型。

例如:

ValidateAny(3); // Uses the int overload
ValidateAny((object)3); // Uses the int overload as well - dynamic handles the unboxing
ValidateAny(3M); // Uses the decimal overload
ValidateAny(3.ToString()); // Uses the string overload
ValidateAny(3f); // Uses the object overload, since there's no better match

这非常强大,只要您只需要对不同的类型进行不同的验证。如果您还有其他考虑,这必须在某种程度上回到if/switch。即便如此,它也可以为您节省大量时间。

【讨论】:

  • 这很酷。我可以为此想到许多其他用途。
  • 而且它也很慢,更不用说它可能会导致运行时和编译时问题,因为 IS 不工作
  • @Fabjan 我想知道你从哪里得到这样的印象——即使使用完全幼稚的基准测试,它甚至比简单的方法调用慢十倍。这仍然意味着您每秒可以调用它大约五千万次!无论如何,它会被其他地方隐含的字符串操作相形见绌。 is 工作得很好——我不确定你想警告什么。是的,它可能会导致运行时问题——就像根据字典或使用类多态来决定一样。
  • 动态值类型与 DLR 一起使用,它需要大量时间进行初始化。对于诸如响应来自服务器的用户请求之类的操作来说是可以的,因为客户端正忙于处理自己的任务以处理一些额外的 100 毫秒,但如果你可以使用结构类型并且没问题,那么使用动态是不好的做法
  • @Fabjan 只要使用最有意义的东西。如果它成为性能问题,您总是可以使用非动态方法重写它。在大多数情况下,它甚至不会接近——事实上,到目前为止,我在实践中从未见过这样的案例。您大大高估了性能影响,而大大低估了在清晰度和编程时间方面的节省。
【解决方案3】:

首先我要实现简单的验证接口:

public interface IValidator
{
    bool Validate(object value);
}

那么数字验证器可能如下所示:

public class NumberValidator : IValidator
{
    public bool Validate(object value)
    {
        return (int) value > 0;
    }
}

最后一步用字典替换你的开关:

 var _validators = new Dictionary<DataTypeEnum, IValidator> // injected via DI
 {
     { DataTypeEnum.Number, new NumberValidator() },
     { DataTypeEnum.DateTime, new DateTimeValidator() },
     { DataTypeEnum.String, new StringValidator() }
 };

 ......

 private bool Validate(DataTypeEnum dataType, object value, ...)
 {
     if (_validators.ContainsKey(dataType))
     {
        return _validators[dataType].Validate(value);
     }

     return false;
 }

【讨论】:

  • 您遗漏了一个关键点 - 您的示例中没有 IValidator,因此代码无法编译。 C# 没有像 Java 的 IGeneric&lt;?&gt; 这样的东西。
  • 好点,将我的答案更新为具有非通用IValidator 接口
  • 谢谢,这是一个很好的解决方案。
  • @RobinNadeau 当然,你甚至不必使用一些IValidator - 一个简单的Func&lt;object, bool&gt; 就可以了。然后字典将包含{ DataTypeEnum.Number, v =&gt; ((int)v) &gt; 0 } 之类的元素。最后,这并没有太大的区别,只要你的界面无论如何只有一个方法。
【解决方案4】:
    public interface IValidator
    {
        void Validate(object value);
    }

    public class NumberValidator : IValidator
    {
        public void Validate(object value)
        {
            //implementation
        }
    }

    public class DecimalValidator : IValidator
    {
        public void Validate(object value)
        {
            //implementation
        }
    }

    public class DatetimeValidator : IValidator
    {
        public void Validate(object value)
        {
            //implementation
        }
    }

    private void Validate(IValidator validator, object value)
    {
        validator.Validate(value);
    }

【讨论】:

  • 那么value 是什么意思?
  • 嗯,现在更有意义了,但它仍然脆弱得要命。是什么阻止人们传入不兼容的 validatorvalue 实例?
  • 比你需要采取下一步来重新编写你的代码。值应该成为 IValidator 的一个属性。 IValidator 应该成为泛型类型。
【解决方案5】:

创建一个字典,其中 DataTypeEnum 作为 Key,Action 作为 Value。然后通过键从字典中获取值并调用操作。

private readonly Dictionary<DataTypeEnum , Action > Validator = new Dictionary<DataTypeEnum , Action >
        {
            { DataTypeEnum.Number, () => Validate(Convert.ToDouble((string)value)) },
            { DataTypeEnum.Decimal, () => Validate(Convert.ToDecimal((string)value)) },
            { DataTypeEnum.DateTime, () => Validate(Convert.ToDateTime((string)value)) },
            // ...
        }

private void Validate(DataTypeEnum dataType, object value, ...)
{
    Validator[dataType](); // Gets the Action and Invokes it
}

private void Validate (Decimal value)
{
    //Validate
}
private void Validate (DateTime value)
{
    //Validate
}
//...

这里的 Action 是不带参数的 Void 委托。它将值转换为合适的格式并调用验证方法。

【讨论】:

    【解决方案6】:
    private void Validate<T>(T value) where T : IComparable
    {
        if(value is Number)
        {
    
        }
        if(value is Decimal)
        {
    
        }
        if(value is DateTime)
        {
    
        }
    }
    

    【讨论】:

    • 为什么不private void Validate(IComparable value)
    • 因为你不会访问值运行时类型,你将把它作为IComparable 转换为方法,所以value is Number 将返回false
    • 没有。对象仍然是一个数字,即使引用是 IComparable 类型。
    【解决方案7】:

    这样的事情怎么样:

    private void Validate(object value, ...)
    {
        if(Reference.Equals(null, value)
            return;
    
        if(value is Number)
        {
    
        }
        else if(value is Decimal)
        {
    
        }
        else if (value is DateTime)
        {
    
        }
        else return;
    }
    

    如果传入的值是 Number、Decimal 或 DateTime,它将运行适当的方法,否则它将简单地返回。

    【讨论】:

    • 方法末尾的返回是多余的
    • 是的,但它让我更容易阅读并且不会受到伤害。当传递的值不是预期的数据类型之一时,您还可以将其替换为实际方法来处理时间。
    • 包含不必要的代码让我(在你找到另一份工作后会阅读你的代码的人)想知道你为什么把它放在那里。这花费了我本可以做其他事情的时间。
    • 我。我写代码是为了好玩,你在我的工作中取代我的机会是一个大胖子 0。没有必要因为 1 条不需要的行在它的上下文中被视为占位符而受到侮辱。
    猜你喜欢
    • 1970-01-01
    • 2012-11-03
    • 1970-01-01
    • 2023-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    • 1970-01-01
    相关资源
    最近更新 更多