根据类型和参数列表删除ifs

时间:2015-05-29 09:58:08

标签: c# if-statement refactoring

我想重构以下递归方法:

public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
{
    if (control == null)
    {
        return;
    }

    var controlWithTextBase = control as ICustomControlWithText;
    if (controlWithTextBase != null)
    {
       controlWithTextBase.DocumentLoaded = true;
       controlWithTextBase.Initialize(container, provider);
    }

    var custom = control as CustomCheckbox;
    if (custom != null)
    {
        custom.DocumentLoaded = true;
        custom.Initialize(container);
    }

    foreach (Control subControl in control.Controls)
    {
        Initialize(subControl, container, provider);
    }
}


public interface ICustomControlWithText : ICustomControl
{
    void Initialize(DocumentContainer container, ErrorProvider provider);
    void InitializeValidations();

    string Text { get; set; }
    ErrorProvider ErrorProvider { get; set; }
    List<IValidation> Validations { get; set; }
}


public interface ICustomControl
{
    void Clear();

    FieldType FieldType { get; set; }
    bool DocumentLoaded { get; set; }
}

class CustomCheckbox : CheckBox, ICustomControl
{
     public void Initialize(DocumentContainer container)
    {
    //...
    }
}

如您所见,取决于winforms的类型控制此代码初始化控件。它从主窗体开始,包含自定义控件( IControlWithText CustomCheckbox )和默认的winforms窗体。 我会创建3个初始化器和每个方法CanInitialize,具体取决于控件的类型,但即便如此,我也不知道如何跳过那些&#34; ifs&#34;,如果需要发送,我需要知道< strong> ErrorProvider 到方法Initialize。

我非常感谢你的帮助!

7 个答案:

答案 0 :(得分:4)

您可以使用&#34;动态重载解析&#34;。 (需要.Net 4+)

如果将输入控件转换为dynamic,。Net将在运行时查找合适的过载 小心提供一个&#34; catch&#34;在意外类型的控制情况下过载。这就是object重载在这里的好处。否则您可能会遇到运行时异常。

public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
{
    if (control == null) return;

    dynamic c = control;       
    InitializeControl(c, container, provider);

    foreach (Control subControl in control.Controls)
        Initialize(subControl, container, provider);
}


public static void InitializeControl(ICustomControlWithText controlWithTextBase, DocumentContainer container, ErrorProvider provider)
{
    controlWithTextBase.DocumentLoaded = true;
    controlWithTextBase.Initialize(container, provider);
}

public static void InitializeControl(CustomCheckbox custom, DocumentContainer container, ErrorProvider provider)
{
    custom.DocumentLoaded = true;
    custom.Initialize(container);
}

public static void InitializeControl(object _, DocumentContainer container, ErrorProvider provider)
{
    // do nothing if the control is neither a ICustomControlWithText nor a CustomCheckbox
}

答案 1 :(得分:3)

您正在寻找的是Visitor(Gang of Four)模式。

确保您的基准界面ICustomControl通过向其添加额外的Accept方法来接受访问者。让该访问者被命名为ControlVisitor,但任何其他名称都可以。

    public interface ICustomControl
    {
        void Accept(ControlVisitor visitor);
        void Clear();

        FieldType FieldType { get; set; }
        bool DocumentLoaded { get; set; }
    }

简化Initialize方法

    public static void Initialize(Control control, ControlVisitor visitor)
    {
        if (control == null) //can this ever be null?
        {
            return;
        }

        var customControl = control as ICustomControl;
        if (customControl != null)
        {
           customControl.Accept(visitor);
        }


        foreach (Control subControl in control.Controls)
        {
            Initialize(subControl, visitor);
        }
    }

通过添加访问者填写空白(我的ControlVisitor示例是一个具体的例子,但你也可以为它设置一个界面)。在这里,您将提供一个重载的Visit方法

    public class ControlVisitor
    {
        private readonly DocumentContainer container;
        private readonly ErrorProvider provider;
        public ControlVisitor(DocumentContainer container, ErrorProvider provider)
        {
            this.container = container;
            this.provider = provider;
        }

        public void Visit(ICustomControlWithText control)
        {
            control.DocumentLoaded = true;
            control.Initialize(container, provider);
        }

        public void Visit(CustomCheckbox control)
        {
            control.DocumentLoaded = true;
            control.Initialize(container);
        }            
    }

Accept方法的实现非常简单,无论你在哪里都一样。下面是CustomCheckBox的示例:

    public class CustomCheckbox : CheckBox, ICustomControl
    {
        //..

        public void Accept(ControlVisitor visitor)
        {
            visitor.Visit(this);
        }
        //..
    }

答案 2 :(得分:1)

我同意@ 3dGrabber,但这里还有一个解决方案,不需要动态,但与3dGrabber的答案非常相似。但严格来说,旧版的.net

public class ClassInitiator{

public static void Initialize(Control control, 
     DocumentContainer container, ErrorProvider provider)
{
    if (control == null) return;

    typeof(ClassInitiator).InvokeMember(
        "InitializeControl",
        BindingFlags.InvokeMethod | BindingFlags.Public,
        null,
        null,
        new object[]{
             control,
             container,
             provider
        });

    foreach (Control subControl in control.Controls)
        Initialize(subControl, container, provider);
}

public static void InitializeControl(
    ICustomControlWithText controlWithTextBase, 
    DocumentContainer container, 
    ErrorProvider provider)
{
    controlWithTextBase.DocumentLoaded = true;
    controlWithTextBase.Initialize(container, provider);
}

public static void InitializeControl(
    CustomCheckbox custom, 
    DocumentContainer container, 
    ErrorProvider provider)
{
    custom.DocumentLoaded = true;
    custom.Initialize(container);
}

public static void InitializeControl(
    object _, 
    DocumentContainer container, 
    ErrorProvider provider)
{
    // do nothing if the control is neither a 
    // ICustomControlWithText nor a CustomCheckbox
}
}

请注意,这只是为了支持以前版本的.NET 2.0,它没有动态支持,因为性能,动态更快,因为它缓存了调用方法所需的运行时解析,但在这种情况下,InvokeMember的解析需要更长时间。

替代方法是解决方法并将其缓存,如下所示。

private Dictionary<Type,MethodInfo> cache = 
   new Dictionary<Type,MethodInfo>();


public static void Initialize(Control control, 
     DocumentContainer container, ErrorProvider provider)
{
    if (control == null) return;

    MethodInfo initializer = null;
    Type controlType = control.GetType();
    if(!cache.TryGetValue(controlType, out initializer)){
        initializer = typeof(ClassInitialor).GetMethod("InitializeControl",
            new Type[] {
                controlType,
                typeof(DocumentContainer),
                typeof(ErrorProvider),
            });
        cache[controlType] = initializer;
    }

    initializer.Invoke(null,
        new object[] {
             control,
             container,
             provider
        });

    foreach (Control subControl in control.Controls)
        Initialize(subControl, container, provider);
}

答案 3 :(得分:0)

从第二个接口中删除初始化逻辑,只留下验证逻辑,所以你可以写这样的东西:

public interface ICustomControl
{
        void Clear();
        FieldType FieldType { get; set; }
        bool DocumentLoaded { get; set; }
        void Initialize(DocumentContainer container);
}

public interface ICustomControlWithText 
{
        string Text { get; set; }
        ErrorProvider ErrorProvider { get; set; }
        List<IValidation> Validations { get; set; }
        void Validate(ErrorProvider provider);
}



class CustomCheckbox : CheckBox, ICustomControl    {  ...  }

class CustomTextbox : TextBox, ICustomControl, ICustomControlWithText      {...}

 public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
    {
        var custom = control as ICustomControl;

        if (control == null)
            return;

        custom.DocumentLoaded = true;
        custom.Initialize(container);

        var controlWithTextBase = control as ICustomControlWithText;
        if (controlWithTextBase != null)
            controlWithTextBase.Validate(provider);

        foreach (Control subControl in control.Controls)
            Initialize(subControl, container, provider);
    }

当然你可以制作ICustomControlWithText&#34;继承&#34;如果你想要ICustomControl

public interface ICustomControlWithText : ICustomControl

答案 4 :(得分:0)

也许基于多态的东西。我认为这很简单,很简单:

class TempTest
    {
        public static void Run()
        {
            IData data = new InitData() { IntegerData = 1, StringData = "some" };

            IBaseControl c1 = new ControlA();
            IBaseControl c2 = new ControlB();

            c1.Init( data );
            c2.Init( data );
        }
    }


    // Interfaces

    public interface IData
    {
        int IntegerData { get; set; }
        string StringData { get; set; }
    }

    public interface IBaseControl
    {
        void Init( IData data );
    }

    public interface IControlA
    {
        void Init( int IntegerData );
    }

    public interface IControlB
    {
        void Init( int IntegerData, string StringData );
    }


    // Base classes

    public abstract class Base : IBaseControl
    {
        #region IBaseControl Members

        public abstract void Init( IData data );

        #endregion
    }


    // Concrete classes

    public class InitData : IData
    {
        public int IntegerData { get; set; }
        public string StringData { get; set; }
    }

    public class ControlA : Base, IControlA
    {
        public override void Init( IData data )
        {
            Init( data.IntegerData );
        }

        #region IControlA Members

        public void Init( int IntegerData )
        {
            Console.WriteLine( "ControlA initialized with IntegerData={0}", IntegerData );
        }

        #endregion
    }

    public class ControlB : Base, IControlB
    {
        public override void Init( IData data )
        {
            Init( data.IntegerData, data.StringData );
        }

        #region IControlB Members

        public void Init( int IntegerData, string StringData )
        {
            Console.WriteLine( "ControlB initialized with IntegerData={0} and StringData={1}", IntegerData, StringData );
        }

        #endregion
    }

我想你明白了。

答案 5 :(得分:0)

我会做以下事情:

  1. 将方法void Initialize(DocumentContainer container, ErrorProvider provider);ICustomControlWithText移至ICustomControl

  2. 更改CustomCheckbox中的Initialize方法签名
      

    public void Initialize(DocumentContainer容器)

      

    public void Initialize(DocumentContainer容器,ErrorProvider提供程序);

    是的,CustomCheckBox永远不会使用provider变量,但那是什么?在执行时间和代码大小方面,为方法调用添加一个额外的参数比使用访问者或动态方法(恕我直言)

    便宜得多
    1. 以下列方式重写递归方法:

      public static void Initialize(Control control, DocumentContainer container, ErrorProvider provider)
      {
          if (control == null)
          {
              return;
          }
      
          var custom = control as ICustomControl;
          if (custom != null)
          {
              custom.DocumentLoaded = true;
              custom.Initialize(container, provider);
          }
      
          foreach (Control subControl in control.Controls)
          {
              Initialize(subControl, container, provider);
          }
      }
      

答案 6 :(得分:0)

我一直使用这种模式:

首先,定义初始化器接口。该接口将用于定义控件类型的初始化方式。

    public interface IInitializer
    {
        void Intialize(Control c, DocumentContainer container, ErrorProvider provider);
        bool Accept(Control c);
    }

其次,创建一些初始化器,每个初始化器代表一个if语句。

    public class InitializerForControlWithTextBase : IInitializer
    {
        public void Intialize(Control control, DocumentContainer container, ErrorProvider provider)
        {
            var c = control as ICustomControlWithText;
            c.DocumentLoaded = true;
            c.Initialize(container, provider);
        }

        public bool Accept(Control c)
        {
            return GetBaseType().IsInstanceOfType(c);
        }

        public Type GetBaseType()
        {
            return typeof(ICustomControlWithText);
        }
    }

    public class InitializerForCustomCheckbox : IInitializer
    {
        public void Intialize(Control control, DocumentContainer container, ErrorProvider provider)
        {
            var c = control as CustomCheckbox;
            c.DocumentLoaded = true;
            c.Initialize(container);
        }

        public bool Accept(Control c)
        {
            return GetBaseType().IsInstanceOfType(c);
        }

        public Type GetBaseType()
        {
            return typeof(CustomCheckbox);
        }
    }

第三,重写Initialize。

    public static void Initialize(IEnumerable<IInitializer> initializers, Control control, DocumentContainer container, ErrorProvider provider)
    {
        if (control == null) return;

        var inSituInitializer = control as IInitializer;
        if (inSituInitializer != null)
        {
            inSituInitializer.Intialize(control, container, provider);
        }
        else
        {
            foreach (var initializer in initializers)
            {
                if (initializer.Accept(control))
                {
                    initializer.Intialize(control, container, provider);
                    break;
                }
            }
        }

        foreach (Control subControl in control.Controls)
        {
            Initialize(initializers, subControl, container, provider);
        }
    }

你会注意到我首先检查控件是否已经实现了IIntializer。如果是,那么您可以直接进行初始化而无需进一步搜索(很容易将其放在您自己的代码上)。如果使用第三方控件,则可以找到并使用上面步骤2中的一个初始值设定项。

最后,在某个地方调用初始化程序。

            var inits = new IInitializer[] 
            { 
                new InitializerForControlWithTextBase(), 
                new InitializerForCustomCheckbox(), 
            };

            Initialize(inits, control, container, provider);

这种方法的好处是:

  1. 你有两种方法可以找到一个初始化器 - 把它放在你的控制之下,或者循环找到一个用于第三方控制的方法。 (如果你有数百个初始化器,你当然可以通过字典进一步提高循环性能。)

  2. 代码已解耦 - 您可以将各个初始值设定项放在单独的DLL中。