如何避免空基抽象类?

时间:2012-09-18 16:51:14

标签: c# oop

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

现在我应该有两个实现:

  • 每个实现都真正处理所有可能事件的部分
  • 每个实现对它不想/不能处理的所有事件都不做任何事。

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

我正在考虑做以下事情:

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,无法使用该语言的较新版本来完成此任务。

2 个答案:

答案 0 :(得分:4)

我同意@usr - 我没有看到空函数的问题。如果要调用函数,则必须存在。如果在某些情况下它什么也不做,那么该函数应该是空的。具有空函数的基类与需要反复执行相同空函数的接口相比,似乎是一个非常好的主意。

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

这在某些情况下可以很好地工作,但是实现非常简单和优雅的基类方法会更复杂。小心不要过度工程。

示例
以下是基于您在问题中提供的示例实现命令链的示例:

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而不是而不是布尔值。仅用于灵感。)

答案 1 :(得分:1)

只是旁注:如果这是出于测试目的,您可以模拟界面并立即执行几乎没有代码的无操作实现。您也可以类似地实现仅部分实现接口的类中所有缺少的方法。不是为了测试 - 你也可以在生产中使用它,但是......在产品中使用模拟有点气味,并且对于“handler-ish”合同有一个样板基础空实现实际上并不邪恶,实际上它是一件好事作为进一步代码的基础 - 就像Cyborgx37已经注意到的那样。