I would like to declare a base class Command
with abstract getter GetValue
. Then I subclass this to TalkCommand
and SpendCommand
for example.
Both class must implement GetValue
, however, I want to TalkCommand
to return string
type for GetValue
(what to talk?) and I want the SpendCommand
to return int
type. (how much to spend?)
I don't know how to write this in the Command
base class. Is this design possible in C#?
In addition to Manfred's answer, I want to give you a hint to go further.
While generics provide you a valuable tool to define GetValue
return type, you wouln't be able to perform the following upcast:
abstract class Command<T>
{
public abstract T GetValue();
}
class TalkCommand : Command<string>
{
public override string GetValue()
{
return "Test";
}
}
// TalkCommand isn't Command<object>
Command<object> cmd = new TalkCommand();
Instead of defining your command as an abstract class, I would start defining it as an interface with covariance :
public interface ICommand<out T>
{
T GetValue();
}
And the whole abstract class could implement ICommand<out T>
:
abstract class Command<T> : ICommand<T>
{
public abstract T GetValue();
}
Now you can upcast an implementation of ICommand<string>
to ICommand<object>
:
ICommand<string> cmd = new TalkCommand();
ICommand<object> cmd2 = cmd;
This is good, because you get the best of two worlds:
ICommand<object>
and this let you call GetValue
and return object
in some situations where you don't know the generic argument to provide or you don't need it at all. If return type must be different depending on subclass, you would be better off not having this method in the base class.
The idea behind having a base type in the first place is to let you deal with all subclasses in a uniform way. One should be able to write
Command cmd = ... // One of subclasses
<some-type> val = cmd.GetValue();
and replace <some-type>
with the actual type. In your case, however, there is no common base type other than object
for int
and string
, so having a different return type becomes pointless.
You can implement GetValue
as generic, like this:
abstract class Command {
abstract T GetValue<T>();
...
}
but that would require the caller to pass the desired type as type parameter of GetValue
, which would defeat the purpose of having GetValue
in the base class.
Possible fix: One approach that lets callers stay away from knowing the type is visitor - make an interface that lets Command
"push" the value into the caller, like this:
IValueGetter {
void SetInt(int v);
void SetString(string v);
}
abstract class Command {
abstract void GetValue(IValueGetter g);
}
class TalkCommand {
override void GetValue(IValueGetter g) {
g.SetString("hello");
}
}
class BuyCommand {
override void GetValue(IValueGetter g) {
g.SetInt(123);
}
}
Now the caller can interact with Command
without knowing the type of its GetValue
. Commands that produce an int
would call back SetInt
, while commands that produce string
would call SetString
.
Only if your base class Command
is generic or you don't mind working with object
.
Generic Example
If you always work with a known instance type, this would be the best approach since you can with the data type you need without casting.
abstract class Command<T>
{
public abstract T GetValue();
}
class TalkCommand : Command<string>
{
public override string GetValue()
{
return "Test";
}
}
class SpendCommand : Command<int>
{
public override int GetValue()
{
return 1234;
}
}
Object Example
This is a very universal approach, pretty much what WPF does with DependencyProperties
and non-generic collections like IList
or IEnumerable
.
abstract class Command
{
public abstract object GetValue();
}
class TalkCommand : Command
{
public override object GetValue()
{
return "Test";
}
}
class SpendCommand : Command
{
public override object GetValue()
{
return 1234;
}
}
Hybrid Example
If you want to be able to use both, you'll need a non-generic base class and inherit another abstract generic class from it.
abstract class Command
{
public abstract object GetValue();
}
abstract class Command<T> : Command
{
public T GetTypedValue()
{
return (T)GetValue();
}
}
class TalkCommand : Command<string>
{
public override object GetValue()
{
return "Test";
}
}
class SpendCommand : Command<int>
{
public override object GetValue()
{
return 1234;
}
}
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.