可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I know I'm not doing this right, but I also know there is a way to do this. I'm trying to be as generic and abstract as possible, otherwise my code is going to get real messy. So I'm using strategy pattern here as well, which is the GetAggregateClient()
method.
I want to have an abstract class called AbstractAggregate<T>
, so that it uses generics. The generic type will be a series of data classes (BlogItem
, ResourceItem
, and AskItem
), which all inherit from ListItem
.
So that's the background info.
The problem here is that I want GetAbstractAggregate()
to return an instance of one of the client classes that implements AbstractAggregate
, with the type of item specified depending on the enum passed in. However, I cannot return an AbstractAggregate<T>
. The compiler won't let me, and that makes sense since, since the AbstractAggregateFactory
class is not a generic.
Does anyone know the best way to do this?
Thanks a lot.
public static class AggregateHelper
{
public enum AggregateTypes { TankTruckBlog, AskTankTruck, Resources }
}
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient(AggregateHelper.AggregateTypes type)
{
switch (type)
{
case AggregateHelper.AggregateTypes.AskTankTruck:
return new AskTankTruckAggregate<AskItem>();
case AggregateHelper.AggregateTypes.TankTruckBlog:
return new TankTruckBlogAggregate<BlogItem>();
case AggregateHelper.AggregateTypes.Resources:
return new ResourcesAggregate<ResourceItem>();
default:
throw new AggregateDoesNotExistException();
}
}
}
public abstract class AbstractAggregate<T>
{
public abstract List<T> GetAggregate(Guid[] resourcetypes);
public abstract T GetSingle(string friendlyname);
}
public class AskTankTruckAggregate<T> : AbstractAggregate<T>
{
// not implemented yet
}
public class TankTruckBlogAggregate<T> : AbstractAggregate<T>
{
// not implemented yet
}
public class ResourcesAggregate<T> : AbstractAggregate<T>
{
// not implemented yet
}
回答1:
The problem the compiler complains about is that you have a method which is 'open' (T) - and you're returning closed generic (with <AskItem>
etc.), concrete type really.
i.e. you have to return a <T>
- and you can do that with the method - no matter if the factory is not generic, the method still can be.
As for what's the best way to do it, that's more of a design question, and a bit longer story. I'm not entirely sure what you're trying to achieve (maybe some background story, how many types you might have etc.)
First, your items shouldn't (generally speaking, as a best practice or some 'feels good' factor) inherit from ListItem
. Use some other base class of yours, and if you need a collection, use a generic one like List<T>
, or create your own IList
implementation, etc.
Second, you don't need to make everything generic. Your base aggregator is generic but custom classes are not, usually. For example:
abstract class ItemBase { }
class AskItem : ItemBase { }
class BlogItem : ItemBase { }
class ProvderA : ProviderBase<AskItem>
{
public override AskItem Get()
{
throw new NotImplementedException();
}
}
class ProvderB : ProviderBase<BlogItem>
{
public override BlogItem Get()
{
throw new NotImplementedException();
}
}
abstract class ProviderBase<T> where T : ItemBase
{
public abstract T Get();
}
class Program
{
static void Main(string[] args)
{
ProviderBase<AskItem> provider = GetProvider<AskItem>();
var item = provider.Get();
}
static ProviderBase<T> GetProvider<T>() where T : ItemBase
{
if (typeof(T) == typeof(AskItem))
return (ProviderBase<T>)(object)new ProvderA();
if (typeof(T) == typeof(BlogItem))
return (ProviderBase<T>)(object)new ProvderB();
return null;
}
}
...that's one implementation.
Basically, making everything 'generic' is not always the best way. You have to have enough reasons or 'types' unknown to be possibly used. As with generic you also pay a certain price. Crossing generics to non-generics world is often tricky, and involves reflection if your types can't be inferred by the usage etc.
In my opinion, it's a mistake making each provider generic (<T>
), as it only accepts one type (each concrete), while base is generic. So like the above. Usually generic is also constrained per interface where/where you can.
But then you have a problem, as casting back to generic context from effectively a non-generic class is not straight (also have in mind there are caveats with value types as you often have to treat that differently), and vice versa as well.
Hence you need something like cast (object) first.
I'd rather use sort of an IOC approach here - e.g. look at the autofac (I'm not associated but I like how it works, nice framework). In that case you'd do something like this:
container.Register<ProviderBase<AskItem>>(c=> new ProvderA());
container.Register<ProviderBase<BlogItem>>(c => new ProvderB());
// and query later...
ProviderBase<AskItem> provider = container.Resolve<ProviderBase<AskItem>>();
Hope this helps some.
回答2:
I'm not sure I understand what you are trying to achieve but perhaps it's something like this
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient<T>()
{
if(T is AskItem) return new AskTankTruckAggregate();
if(T is BlogItem) return new TankTruckBlogAggregate();
if(T is ResourceItem) return new ResourcesAggregate();
}
}
public abstract class AbstractAggregate<T>
{
public abstract List<T> GetAggregate(Guid[] resourcetypes);
public abstract T GetSingle(string friendlyname);
}
public class AskTankTruckAggregate : AbstractAggregate<AskItem>
{
//not implemented yet
}
public class TankTruckBlogAggregate : AbstractAggregate<BlogItem>
{
//not implemented yet
}
public class ResourcesAggregate : AbstractAggregate<ResourceItem>
{
//not implemented yet
}
回答3:
I'm trying to be as generic and abstract as possible, otherwise my code is going to get real messy.
this is a misconception. being generic/abstract can actually complicate an otherwise simple problem. The key to clean code is encapsulation. much different that inheritance or generics.
In this case I think composition would be a better choice, rather than inheritance. with a set of adaptors you could have a common object that each entity could be adpated to. for example:
interface ICommon { ... }
class AskAdaptor: ICommon
{
private readonly Ask ask;
publick AskAdaptor(Ask ask)
{
this.ask = ask;
}
}
class AskAdaptor: ICommon
{
private readonly Blog blog;
publick AskAdaptor(Blog blog)
{
this.blog = blog;
}
}
class AskAdaptor: ICommon
{
private readonly Resource resource;
publick AskAdaptor(Resource resource)
{
this.resource = resource;
}
}
class CommonAggregate
{
public void Add(ICommon common)
{
....
}
}
回答4:
How about this:
public static class AggregateHelper
{
public enum AggregateTypes { TankTruckBlog, AskTankTruck, Resources }
}
public class AskItem { }
public class BlogItem { }
public class ResourceItem { }
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient<T>
(AggregateHelper.AggregateTypes type)
{
switch (type)
{
case AggregateHelper.AggregateTypes.AskTankTruck:
return new AskTankTruckAggregate<T>();
case AggregateHelper.AggregateTypes.TankTruckBlog:
return new TankTruckBlogAggregate<T>();
case AggregateHelper.AggregateTypes.Resources:
return new ResourcesAggregate<T>();
default:
throw new ArgumentException();
}
}
}
public abstract class AbstractAggregate<T>
{
public abstract List<T> GetAggregate(Guid[] resourcetypes);
public abstract T GetSingle(string friendlyname);
}
public class AskTankTruckAggregate<T> : AbstractAggregate<T>
{
public override List<T> GetAggregate(Guid[] resourcetypes)
{
throw new NotImplementedException();
}
public override T GetSingle(string friendlyname)
{
Console.WriteLine(friendlyname);
Type whats_t = typeof(T);
return default(T);
}
}
public class TankTruckBlogAggregate<T> : AbstractAggregate<T>
{
//not implemented yet
}
public class ResourcesAggregate<T> : AbstractAggregate<T>
{
//not implemented yet
}
Example:
AbstractAggregate<BlogItem> foo3 =
AbstractAggregateFactory.GetAggregateClient<BlogItem>(AggregateHelper.AggregateTypes.AskTankTruck);
foo3.GetSingle("test");
回答5:
One thing that is possibly clear is that your design is somewhat flawed. A switch on type is not the best thing to do in a generic method which defeats it's purpose. But what is not clear is what the purpose of your classes are.
Some speculations:
1) Seeing your pair classes AskItem
and AskTankTruckAggregate<T>
etc I dont think the latter has to be a generic class, it is a very specific class, tightly coupled to AskItem
. I would redesign it like
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient<T>() where T : ListItem
{
//use reflection to find the type that inherits AbstractAggregate<T>
//instantiate the type
//cast to AbstractAggregate<T> and return
}
}
public class AskTankTruckAggregate : AbstractAggregate<AskItem>
{
//not implemented yet
}
public class TankTruckBlogAggregate : AbstractAggregate<BlogItem>
{
//not implemented yet
}
public class ResourcesAggregate : AbstractAggregate<ResourceItem>
{
//not implemented yet
}
Call it like:
AbstractAggregateFactory.GetAggregateClient<AskItem>(); //etc
2) Another way: delegate the aggregate creation job to your ListItems.
public abstract class ListItem //or interface
{
protected abstract object Create();
}
public class AskItem : ListItem { //implement to return AskTankTruckAggregate
}
public class BlogItem : ListItem { //implement to return TankTruckBlogAggregate
}
public class ResourceItem : ListItem { //implement to return ResourcesAggregate
}
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient<T>() where T : ListItem, new()
{
return (AbstractAggregate<T>)new T().Create();
}
}
public class AskTankTruckAggregate : AbstractAggregate<AskItem>
{
//not implemented yet
}
public class TankTruckBlogAggregate : AbstractAggregate<BlogItem>
{
//not implemented yet
}
public class ResourcesAggregate : AbstractAggregate<ResourceItem>
{
//not implemented yet
}
Call it like:
AbstractAggregateFactory.GetAggregateClient<AskItem>(); //etc
3) Or the same, but make it a bit more strongly typed, with the use of generics:
public abstract class ListItem<T> where T : ListItem<T> //or interface
{
protected abstract AbstractAggregate<T> Create();
}
public class AskItem : ListItem<AskItem> { //implement to return AskTankTruckAggregate
}
public class BlogItem : ListItem<BlogItem> { //implement to return TankTruckBlogAggregate
}
public class ResourceItem : ListItem<ResourceItem> { //implement to return ResourcesAggregate
}
public static class AbstractAggregateFactory
{
public static AbstractAggregate<T> GetAggregateClient<T>() where T : ListItem, new()
{
return new T().Create();
}
}
public class AskTankTruckAggregate : AbstractAggregate<AskItem>
{
//not implemented yet
}
public class TankTruckBlogAggregate : AbstractAggregate<BlogItem>
{
//not implemented yet
}
public class ResourcesAggregate : AbstractAggregate<ResourceItem>
{
//not implemented yet
}
Call it like:
AbstractAggregateFactory.GetAggregateClient<AskItem>(); //etc
4) Lastly, may be make the return type less generic? Involves switch case, I dont like it.
public enum AggregateTypes { TankTruckBlog, AskTankTruck, Resources }
public static class AbstractAggregateFactory
{
public static AbstractAggregate GetAggregateClient(AggregateTypes type)
{
switch (type)
{
case AggregateTypes.AskTankTruck:
return new AskTankTruckAggregate<AskItem>();
case AggregateTypes.TankTruckBlog:
return new TankTruckBlogAggregate<BlogItem>();
case AggregateTypes.Resources:
return new ResourcesAggregate<ResourceItem>();
default:
throw new AggregateDoesNotExistException();
}
}
}
public abstract class AbstractAggregate
{
}
public abstract class AbstractAggregate<T> : AbstractAggregate
{
}
//or change the definition to AskTankTruckAggregate : AbstractAggregate<AskItem>
public class AskTankTruckAggregate<T> : AbstractAggregate<T>
{
//not implemented yet
}
//or change the definition to TankTruckBlogAggregate : AbstractAggregate<BlogItem>
public class TankTruckBlogAggregate<T> : AbstractAggregate<T>
{
//not implemented yet
}
//or change the definition to ResourcesAggregate : AbstractAggregate<ResourceItem>
public class ResourcesAggregate<T> : AbstractAggregate<T>
{
//not implemented yet
}
Call it like:
AbstractAggregateFactory.GetAggregateClient(AggregateTypes.AskTankTruck); //etc
Imo, this approach is worse than the reflection approach. Its easy to forget some enum checking in future.
Of all, 3rd looks the best to my eyes, but again without knowing your design goal, its very difficult to predict. Few suggestions:
Your factory name sounds better like AggregateFactory
. "Abstract" in it makes it more about implementation.
In case you need an enum to denote type, do not make it nested. Nested public types are harder to call. Take out the wrapping static class (as in my 5th approach).
Rename your base class as Aggregate<T>
or AggregateBase<T>
. Again "Abstract" in it makes it more about implementation, quite needless.