Reputation: 73253
I have classes for entities like Ship
, Shampoo
, Horse
etc and each of them will have a manager class like ShipManager
, ShampooManager
, HorseManager
etc. All the manager classes implement IManager
and entities implement IEntity
.
Now I have a static function written just to save the entities which would look like:
public static bool Save<S, T>(S entity, string msg) where S : IEntity
where T : IManager<S>, new()
{
string possibleError;
switch (new T().Save(entity, out possibleError))
{
//---------------------
}
return true;
}
The inelegance here is that I have to call the Save
like Save<Radio, RadioManager>
etc. What I would love to have is something like this:
public static bool Save<T>(T entity, string msg) where T : IEntity
{
string possibleError;
switch ((/*find manager here*/).Save(entity, out possibleError))
{
//---------------------
}
return true;
}
There are few ways to do this, but I would love to have something that involves generics and type inference. I am thinking it would be good to couple the entity classes with corresponding manager classes by design to achieve some type safety as well.
Here is what I could do, with the help of an extension method:
public static bool Save<T>(this IManager<T> mgr, T entity, string msg) where T : IEntity
{
string possibleError;
switch (mgr.Save(entity, out possibleError))
{
//---------------------
}
return true;
}
So that I can call:
FooManager fMgr = new FooManager();
fMgr.Save(foo, "success");
But here I always need to instantiate the IManager
and then call the Save
method on its instance. I would love to avoid that much repetitive code and give the duty to a static function. How can I design the classes to have a relationship between IManager
and IEntity
so that manager of entity is automatically inferred?
Edit: Please note that I dont have the luxury to move the Save
method in manager class to entity class (which would have made life easier). Our entire design is based on one entity class and a corresponding manager class. So I am thinking of sticking to it.
Upvotes: 3
Views: 746
Reputation: 73253
Here is how I solved it.
I created a function Manager
in the IEntity
like this:
IManager<T> Manager<T>() where T : IEntity;
Now whenever I implement IEntity
in any of the entity classes, I am forced to implement their respective Manager
as well. For eg.
public class Foo : IEntity
{
public IManager<T> Manager<T>() where T : IEntity
{
return (IManager<T>)new FooManager();
}
}
Now I can call:
public static bool Save<T>(T entity, string msg) where T : IEntity, new()
{
string possibleError;
switch (entity.Manager<T>().Save(entity, out possibleError))
{
//--------------------------------------
}
return true;
}
Not the best of designs, but this does the job..
Upvotes: 0
Reputation: 9153
Does the Client of FooManager care that it's a FooManager or that it's an IManager<Foo> ? If it just wants an IManager<Foo>, you probably want to look at the AbstractFactory pattern. Your factory would be responsible for instantiating/retrieving the correct IManager on demand. With the factory in place your save method would look something like this
public static bool Save<S>(S entity, string msg)
where S : IEntity
{
string possibleError;
switch (ManagerFactory.GetManagerFor<S>().Save(entity, out possibleError))
{
//---------------------
}
return true;
}
The simplest path for implementing your ManagerFactory is to use a service locator/dependency injector, and have it auto discover your IManager<T> implementations. Then your GetManagerFor method would just ask for an instance from the DI Container. For example, here's how to do it with AutoFac
var dataAssembly = Assembly.GetExecutingAssembly();
builder.RegisterAssemblyTypes(dataAssembly)
.Where(t => t.Name.EndsWith("Manager"))
.AsClosedTypesOf(typeof(IManager<>);
This will cause autofac to find all classes that end with "Manager" in their name in the current assembly and register as closed types of IManager<T>. So it would find FooManager and register it as an instance of IManager<Foo>
Now ManagerFactory just needs to call
return container.Resolve<IManager<Foo>>()
And you're golden. I'm not going to write your application for you but this should give you enough to get you going. Read the docs on AutoFac, it's really powerful because it can also build up objects for you using dependency injection as well and it's a great tool to have in your belt.
Upvotes: 4