泛型基类覆盖非泛型基类函数模式?(.NET)

3
我想知道是否有人对解决以下设计问题有好的建议或模式。我有一组命令类,最抽象的级别是ICommand接口。执行ICommand的RunCommand()函数的结果是一个对象。不同的命令将具有不同的结果类型,因此这是一个适当的抽象。
在构建层次结构时,使用泛型会更加方便。我创建了一个Generic.ICommand(Of TResult)接口。
有一些通用的样板代码,例如Run、TryRun、BeginRun、BeginTryRun等,我希望所有命令都能使用 - 因此我创建了一个BaseCommand类,它提供了所有这些内容,并实现了非泛型ICommand接口。但是,BaseCommand并不知道如何实际执行任何操作,因此所有这些命令最终都会调用一个受保护的抽象函数InternalRunCommand。这一切都很顺利。
现在我想创建该类的通用版本:BaseCommand(Of T)。它继承自BaseCommand,并实现了通用的ICommand(Of T)接口。这很有效,但现在存在一个差异:InternalRunCommand。
在非泛型版本中,InternalRunCommand返回一个对象。在我的通用BaseCommand(Of T)类中,我想用返回类型为T的通用版本重载它。不幸的是,VB.NET/C#编译器不允许您提供一个方法的重载,其中唯一的区别是返回类型。
由于这是一个受保护的函数,它对整个API的影响并不大,但我仍然感到烦恼,因为我没有一个美观的解决方案来解决这个架构问题。
目前,我已经在BaseCommand(Of T)类中重写了非泛型InternalRunCommand,以便调用一个新的受保护的抽象OnRunCommand函数,该函数具有相同的参数但返回类型为T。InternalRunCommand也已声明为不可重载。这可能是我能接受的最接近的解决方案 - 但我想知道是否还有更好的想法? :)
编辑:如请求所示,我已包含了代码的简化副本,以便您可以更好地理解问题:
Public Interface ICommand
    Property Name as String
    Property Description As String
    Property ResultType as Type
    Function RunCommand(target as Device) As Object
    Function TryRunCommand(target as Device, Byref result as Object) AS Boolean
    Function BeginRunCommand(target as Device) as Task(Of Object)
    Function BeginTryRunCommand(target as Device) As Task(of Boolean)
End Interface

Namespace Generic
Public Interface ICommand(Of TResult)
    Function RunCommand(target as Device) as T
    Function BeginRunCommand(target as Device) As Task(Of T)
End Interface
End Namespace

Public MustInherit Class BaseCommand
    Implements ICommand

    Public Function RunCommand(target as Device) As Object Implements ICommand.RunCommand
        Return InternalRunCommand(device)
    End Function

    Public Function BeginRunCommand(target as Device) As Task(of Object) Implements ICommand.BeginRunCommand
        Return Task(Of Object).Factory.StartNew( Function() InternalRunCommand(target))
    End Function

    ' Other boiler plate code goes here'

    Protected MustOverride Function InternalRunCommand(target as Device) As Object

End Class

Namespace Generic
Public Class BaseCommand(Of TResult)
    Inherits BaseCommand
    Implements ICommand(Of TResult)

    Public Function BeginRunCommand(target as Device) As Task(of TResult) Implements ICommand(Of TResult).BeginRunCommand
        Return Task(Of TResult).Factory.StartNew( Function() OnRunCommand(target))
    End Function

    Protected NotOverridable Overrides Function InternalRunCommand(target as Device) As Object
        ' Re-route to the generic version'
        Return OnRunCommand(device)
    End Function

    Protected MustOverride Function OnRunCommand(target as Device) As T
End Class

1
最好将您的代码实际包含在问题中,而不是像您现在这样用英语描述代码。我的大脑比对代码的描述更容易理解代码本身。 - Dave M
1
嘿,Dave M,我认为在这种情况下少即是多,因为实际上有很多样板代码。不过,我提供了一个简化的代码示例,展示了我正在做的事情。希望能有所帮助 :) - iam1me
这不是C#代码,对吧? - vasily.sib
1
我专门使用VB.NET进行工作,但是你可以通过进行小的语法更改直接将其翻译成C#。这个问题与语言无关 - 它是由.NET的限制引起的问题。我会接受用C#编写的答案。 - iam1me
1
几点想法:首先,您是否考虑过将继承关系反转,使得Object版本从泛型中继承,而不是相反?其次,另一个可能要考虑的事情是使用Shadows来用更具体的版本替换泛型中的Object版本。 - Craig
显示剩余2条评论
1个回答

0

我认为我找到了一个很好的模式,允许您用通用函数重写非通用函数版本,而不需要像 OP 中那样使用任何混乱的包装函数。

我用具有相同名称的受保护、抽象 InnerRunCommand 函数替换了其受保护、只读属性。此属性的类型是 Func(Of ICommand, Device, Object)。我修改了 BaseCommand 类的构造函数,使其接受这样的 Func 对象。

在 Generic.BaseCommand(Of T) 类中,我可以用类型为 Func(Of ICommand, Device, T) 的类似属性隐藏 InnerRunCommand。Generic.BaseCommand(Of T) 的构造函数也类似地接受这样的 Func 对象,并将该对象返回给非泛型 BaseCommand 构造函数,没有问题 :)

我正在修改我的架构以支持这个新模式,并将在遇到任何问题时告诉大家。我欢迎对这种方法进行任何批评,并欢迎其他答案 :)

编辑:我已经构建了一个简单的示例,展示了所提出的模式。我用 C# 编写了它,而不是 VB.NET。在 C# 中需要更多的工作来转换 FUNC 对象(VB.NET 在幕后为您处理这一点)。但是,在两种语言中,使用这些匿名函数可以保持 API 的清晰和可扩展性。

public interface ICommand
{
    object Execute();
    Boolean TryExecute(out object result);
    Task<object> BeginExecute();        
}

namespace Generic
{
    public interface ICommand<TResult> : ICommand
    {
        new TResult Execute();
        Boolean TryExecute(out TResult result);
        new Task<TResult> BeginExecute();
    }
}

public class Command : ICommand
{
    private Func<ICommand, object> _execFunc = null;
    protected Func<ICommand, object> ExecFunc { get { return _execFunc; } }

    public Task<object> BeginExecute()
    {
        return Task<object>.Factory.StartNew(() => _execFunc(this) );
    }

    public object Execute()
    {
        return _execFunc(this);
    }

    public bool TryExecute(out object result)
    {
        try
        {
            result = _execFunc(this);
            return true;
        }
        catch(Exception ex)
        {
            result = null;
            return false;
        }
    }

    public Command (Func<ICommand, object> execFunc)
    {
        if (execFunc == null) throw new ArgumentNullException("execFunc");
        _execFunc = execFunc;
    }
}

namespace Generic
{
    public class Command<TResult> : Command, ICommand<TResult> where TResult : class            
    {
        new protected Func<ICommand<TResult>, TResult> ExecFunc => (ICommand<TResult> cmd) => (TResult)base.ExecFunc(cmd);

        public bool TryExecute(out TResult result)
        {
            try
            {
                result = ExecFunc(this);
                return true;
            }
            catch(Exception ex)
            {
                result = null;
                return false;
            }
        }

        Task<TResult> ICommand<TResult>.BeginExecute()
        {
            return Task<TResult>.Factory.StartNew(() => ExecFunc(this) );
        }

        TResult ICommand<TResult>.Execute()
        {
            return ExecFunc(this);
        }

        public Command(Func<ICommand<TResult>, TResult> execFunc) : base((ICommand c) => (object)execFunc((ICommand<TResult>)c))
        {
        }
    }
}

public class ConcatCommand : Generic.Command<string> 
{
    private IEnumerable<string> _inputs;
    public IEnumerable<String> Inputs => _inputs;

    public ConcatCommand(IEnumerable<String> inputs) : base( (Generic.ICommand<string> c) => (string)String.Concat(((ConcatCommand)c).Inputs) )
    {
        if (inputs == null) throw new ArgumentNullException("inputs");
        _inputs = inputs;
    }

}

class Program
{
    static void Main(string[] args)
    {
        string[] inputs = { "This", " is ", " a ", " very ", " fine ", " wine!" };
        ICommand c = new ConcatCommand(inputs );
        string results = (string)c.Execute();
        Console.WriteLine(results);
        Console.ReadLine();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接