泛型基类重写非泛型基类功能模式? (。净)

时间:2018-10-17 01:28:38

标签: c# .net vb.net generics design-patterns

我想知道是否有人对解决以下设计问题有好的建议/模式。我有一个命令类的层次结构。在最抽象的层次上,我有一个ICommand接口。执行ICommand的RunCommand()函数的结果是一个对象。不同的命令将具有不同的结果类型,因此这是一个适当的抽象。

更多地构建层次结构,使用泛型就变得很有必要。我创建一个Generic.ICommand(Of TResult)接口。

我对所有命令都有一些通用的样板代码,例如Run,TryRun,BeginRun,BeginTryRun等-因此,我创建了一个BaseCommand类来提供所有这些功能,并实现了非通用ICommand接口。但是,BaseCommand不知道如何实际执行任何操作,因此所有这些命令最终都会调用一个受保护的抽象函数,称为InternalRunCommand。这一切都很漂亮。

现在,我想创建该类的通用版本:BaseCommand(Of T)。它继承自BaseCommand,还实现了通用的ICommand(Of T)接口。可以,但是现在有一个差异:InternalRunCommand。

在非通用版本中,InternalRunCommand返回一个Object。在我的通用BaseCommand(Of T)类中,我想使用以版本T返回结果的通用版本来重载它。不幸的是,VB.NET / C#编译器不允许您提供方法的重载,唯一的区别是返回类型。

由于这是一个受保护的函数,因此最终与整个API并没有太大区别,但是仍然令我感到恼火的是,我对这种体系结构没有美观的解决方案。

暂时,我在BaseCommand(Of T)类中重写了非通用的InternalRunCommand,以便它调用一个新的受保护的抽象OnRunCommand函数,该函数采用相同的参数,但返回类型T的结果。InternalRunCommand具有也被宣布为NonOverridable。这也许是我能找到的最接近的-但想看看是否有更好的主意? :)

编辑:我已根据要求提供了代码的简化副本,以便您可以更好地可视化问题:

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 个答案:

答案 0 :(得分:0)

我认为我找到了一个很好的模式,它允许您使用泛型函数覆盖此类函数的非泛型版本,而无需像OP中那样使用任何混乱的包装函数。

我用相同名称的受保护ReadOnly属性替换了受保护的抽象InnerRunCommand函数。此属性的类型为Func(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();
    }
}