2

假设我有许多具有不同签名的方法。基于一些外部代码,所有这些方法都可能被调用。您可能会将它们视为一些事件处理程序。

现在我应该有两个实现:

  • 每个实现实际上处理所有可能事件的一部分。
  • 每个实现对于它不想/不能处理的所有事件根本不做任何事情。

我当然可以为所有可能的处理程序声明一个接口,但是我必须在每个实现中创建空方法(处理程序)。即使对于那些我不想/不能处理的事件。

我正在考虑执行以下操作:

abstract class Base
{
    public virtual void First(int i, double d) { /* no implementation */ }
    public virtual void Second(double d) { /* no implementation */ }
    public virtual void Third(string s, int i) { /* no implementation */ }
}

class Child : Base
{
    public override void First(int i, double d) { /* implementation */ }
    public override void Second(double d) { /* implementation */ }
}

class AnotherChild : Base
{
    public override void Second(double d) { /* implementation */ }
    public override void Third(string s, int i) { /* implementation */ }
}

这种方法迫使我为基础抽象类中的所有可能的处理程序创建空实现。

你能推荐一些更好的吗?不需要产生大量空方法的方法?

我正在使用 C# 2.0 并且不能使用更新版本的语言来完成这项任务。

4

2 回答 2

4

我同意@usr - 我认为空函数没有问题。如果你想调用一个函数,那么它必须存在。如果它在某些情况下什么都不做,那么该函数应该是空的。具有空函数的基类与需要一遍又一遍地实现相同空函数的接口相比,似乎是一个非常好的主意。

如果您正在寻找替代方案,您可以考虑责任链设计模式。您可以调用通用函数,然后参数化所需的行为,而不是调用特定函数。然后,您可以将对象链接在一起(不同情况下的不同链)并让它们都有机会处理行为。如果他们都没有处理它,那么什么都不会发生。

这在某些情况下会很好地工作,但实现起来比非常简单和优雅的基类方法更复杂。注意不要过度设计。

示例
根据您在问题中给出的示例,这是一个实现命令链的示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2 {

    interface ICommand {
        bool Execute( string action, params object[] parameters );
    }

    class Program {
        static void Main( string[] args ) {

            CommandChain l_chain1 = new CommandChain( new FirstCommand(), new SecondCommand() );
            CommandChain l_chain2 = new CommandChain( new SecondCommand(), new ThirdCommand() );

            // Chain 1

            if ( l_chain1.Execute( "first", (int) 1, (double) 1.1 ) )
                Console.WriteLine( "Chain 1 executed First" );
            else
                Console.WriteLine( "Chain 1 did not execute First" );

            if ( l_chain1.Execute( "second", (double) 1.2 ) )
                Console.WriteLine( "Chain 1 executed Second" );
            else
                Console.WriteLine( "Chain 1 did not execute Second" );

            if ( l_chain1.Execute( "third", "4", (int) 3 ) )
                Console.WriteLine( "Chain 1 executed Third" );
            else
                Console.WriteLine( "Chain 1 did not execute Third" );

            // Chain 2

            if ( l_chain2.Execute( "first", (int) 1, (double) 1.1 ) )
                Console.WriteLine( "Chain 2 executed First" );
            else
                Console.WriteLine( "Chain 2 did not execute First" );

            if ( l_chain2.Execute( "second", (double) 1.2 ) )
                Console.WriteLine( "Chain 2 executed Second" );
            else
                Console.WriteLine( "Chain 2 did not execute Second" );

            if ( l_chain2.Execute( "third", "4", (int) 3 ) )
                Console.WriteLine( "Chain 2 executed Third" );
            else
                Console.WriteLine( "Chain 2 did not execute Third" );

            Console.ReadKey( true );

        }
    }

    class CommandChain {

        private ICommand[] _commands;

        public CommandChain( params ICommand[] commands ) {
            _commands = commands;
        }

        public bool Execute( string action, params object[] parameters ) {
            foreach ( ICommand l_command in _commands ) {
                if ( l_command.Execute( action, parameters ) )
                    return true;
            }
            return false;
        }

    }

    class FirstCommand : ICommand {
        public bool Execute( string action, params object[] parameters ) {
            if ( action == "first" &&
                parameters.Length == 2 &&
                parameters[0].GetType() == typeof( int ) &&
                parameters[1].GetType() == typeof( double ) ) {

                int i = (int) parameters[0];
                double d = (double) parameters[1];

                // do something

                return true;
            } else
                return false;
        }
    }

    class SecondCommand : ICommand {
        public bool Execute( string action, params object[] parameters ) {
            if ( action == "second" &&
                parameters.Length == 1 &&
                parameters[0].GetType() == typeof( double ) ) {

                double d = (double) parameters[0];

                // do something

                return true;
            } else
                return false;
        }
    }

    class ThirdCommand : ICommand {
        public bool Execute( string action, params object[] parameters ) {
            if ( action == "third" &&
                parameters.Length == 2 &&
                parameters[0].GetType() == typeof( string ) &&
                parameters[1].GetType() == typeof( int ) ) {

                string s = (string) parameters[0];
                int i = (int) parameters[1];

                // do something

                return true;
            } else
                return false;
        }
    }

}

(请注意,此示例并未遵循所有编程最佳实践 - 我不建议完全实现此代码。例如,action 参数作为枚举可能比字符串更好,并返回某种 CommandResult 而不是布尔值. 仅用于灵感。)

于 2012-09-18T17:20:08.010 回答
1

附带说明:如果这是出于测试目的,您可以模拟接口并立即获得几乎没有代码的无操作实现。同样,您也可以在仅部分实现接口的类中实现所有缺少的方法。不是为了测试——你也可以在生产中使用它,但是..在 prod 中使用 mock 有点味道,并且为“handler-ish”合约提供样板基础的空实现确实不是坏事,实际上这是一件好事作为进一步代码的基础——就像已经提到的 Cyborgx37 一样。

于 2012-09-18T18:09:50.960 回答