简体   繁体   中英

Generic Base Class Overriding Non-Generic Base Class Function Pattern? (.NET)

I'm wondering if anyone has a good suggestion/pattern in mind for solving the following design issue. I have a heirarchy of command classes. At the most abstract level I have an ICommand interface. The result from executing an ICommand's RunCommand() function is an Object. Different commands will have different result types, so this is an appropriate abstraction.

Building the heirarchy out a bit more, it becomes desirable to use generics. I create a Generic.ICommand(Of TResult) interface.

There's some common boiler plate code like Run, TryRun, BeginRun, BeginTryRun, etc that I want for all commands - so I create a BaseCommand class that provides all this, and which implements the non-generic ICommand interface. The BaseCommand doesn't know how to actually execute anything however, so all of these commands ultimately invoke a protected abstract function called InternalRunCommand. This all works beautifully.

Now I want to create a generic version of that class: BaseCommand(Of T). It inherits from BaseCommand and also implements the generic ICommand(Of T) interface. This works, but now there is a discrepancy: The InternalRunCommand.

In the non-generic version InternalRunCommand returns an Object. In my generic BaseCommand(Of T) class I'd like to overload that with a generic version that returns the result as type T. Unfortunately, the VB.NET/C# compilers don't let you provide an overload of a method where the only difference is the return type.

Since this is a protected function, it ultimately doesn't make much difference to the overall API, but it nevertheless irritates me that I don't have an aesthetically pleasing solution to this bit of architecture.

For the time being I have overridden the non-generic InternalRunCommand in the BaseCommand(Of T) class so that it invokes a new protected, abstract OnRunCommand function that takes the same parameters but returns a result of type T. The InternalRunCommand has also been declared NonOverridable. This maybe the closest I can get - but wanted to see if there are any better ideas out there? :)

EDIT: I've included a simplified copy of the code as requested so that you may better visualize the problem:

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

I think I've found a good pattern that allows you to override the non-generic version of such functions with generic ones, and without any messy wrapper functions like I had in the OP.

I've replaced the protected, abstract InnerRunCommand function with a protected, ReadOnly property of the same name. The type of this property is Func(Of ICommand, Device, Object). I modified the constructor of the BaseCommand class to accept such a Func object.

In the Generic.BaseCommand(Of T) class, I can Shadow the InnerRunCommand with a similar property of type Func(Of ICommand, Device, T). The constructor for the Generic.BaseCommand(Of T) similarly accepts such a Func object, and it passes that object back to the non-generic BaseCommand constructor without issue :)

I'm in the processing of modifying my architecture to support this new pattern, and will let you all know if I run into any problems. I welcome any critiques to this approach and welcome alternate answers :)

EDIT: I have constructed a simple example of the proposed pattern below. I have written it in C# instead of VB.NET. There is a little more work involved in casting the FUNC objects in C# (VB.NET handles this for you behind the scenes). In both languages, however, the use of these anonymous functions keeps the API clean and extensible.

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();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM