Как сделать оператор switch более объектно-ориентированным?

Я пытаюсь реорганизовать оператор 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
}

Есть ли какой-нибудь элегантный способ исправить это?

7 ответов

Решение

Прежде всего, я бы начал с реализации простого интерфейса для проверки:

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

Используйте полиморфизм.

Пример:

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

Чтобы добавить к этому, хотя некоторые могут считать это не ООП вообще, вы можете использовать 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

По сути, это работает так же, как и обычное разрешение перегрузки, но выполняется во время выполнения, в зависимости от типа времени выполнения. val в ValidateAny,

Например:

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, Даже тогда это может сэкономить вам много времени.

Создайте словарь с DataTypeEnum в качестве ключа и действием в качестве значения. Затем получите значение из словаря по ключу и вызовите действие.

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
}
//...

Здесь действие - делегат Void, который не принимает параметров. Он преобразует значение в подходящий формат и вызывает метод проверки.

    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);
    }
private void Validate<T>(T value) where T : IComparable
{
    if(value is Number)
    {

    }
    if(value is Decimal)
    {

    }
    if(value is DateTime)
    {

    }
}

Как насчет чего-то вроде этого:

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, он запустит соответствующий метод, в противном случае он просто вернется.

Другие вопросы по тегам