Reputation: 1192
I read some tutorials about a the factory an abstract factory pattern and saw some examples of it. In one of the tutorials i read that the factory pattern could replace major "if" or "switch case" statements and follows the open/closed (solid) principles.
In one of my projects a have a huge "switch case" which i wanna replace by a(n) (abstract) factory. It's already interface based so implementing an factory shouldn't be that difficult but in all the examples i read in tutorials the factory produced a single concrete type based on configuration. Can anyone point me in the right direction how to implement a factory that could produce multiple types based on an enum that follows the Solid principles an replaces the large "switch case"....or am I misinformed and is the "switch case" moved to the factory?
code at this moment:
public interface ISingleMailProcessor : IMailProcessor
{
MailResult ProcesMail(Mail mail);
}
public MailResult GetMailResult(mail)
{
ISingleMailProcessor mailprocessor;
switch (mail.MailType)
{
case MailConnector.MailType.AcronisBackup:
mailprocessor = new AcronisProcessor();
return mailprocessor.ProcesMail(mail);
case ......etc.
}
}
Upvotes: 1
Views: 1747
Reputation: 12557
First of all I want to recommand the CCD Webcast about factories. It is very helpfull on this topic and also shows some problems we may encounter. Also you can find some good informations on this objectmentor document
As a summarization of the webcast you could see, the more you want to follow the OpenClosed Principal in the problem domain of "creation" the less typesafe you can work.
As a result the abstract factory could also operate on a some more dynamic values like strings. For example you could have an Method.
string GetAllPossibilities(); // Returns all possible kinds that can be constructed
and a related
T Create<T>(string kind)
the call would now only have to pass the string which uniquely identifies the requested instance kind . Your marker could be something "selfmade" like "Rectangle" or event TypeNames, but that would mean that there is more depedency between the component, as a Namespace change could break the caller.
So your call may be something like one of these:
Factory.Create("Acronis") // Your Marker
Factory.Create("MYNameSpace.AcronisProcessor") //TypeName
Factory.Create<AcronisProcessor>() //Type
So you wouldn't have switch statements outside the Factory. Inside the factory you may have some or you could think about some kind of dynamic object creation.
The Factory could still have swithc statements switching your selfmade identifier or code do something like
var type = Type.GetType(kind);
return Activator.CreateInstance(type) as T;
But as this is seperated from the main domain logik it doesn't have anymore this importance like before-.
With this at first look you wouldn't have api realted breaking changes if you get new options.
But there is still some kind of underlying semantic dependency.
EDIT: As you can see in the discussion below i cut off some details as I think they would blure up the main point (OpenClosed Principales and Factory Pattern). But there are still some other Points that should not be forgotten. Like "What is an application root and how has it to be designed" . To get all details the webcast (also the others of this site too) are a much better approach to learn this details then this post here.
Upvotes: 0
Reputation: 16348
What you have there implemented is the Strategy pattern which is still a valid approach. Anyway, if you want to replace the whole switch and to make it more maintainable you can use this
public interface IProcessMail
{
bool CanProcess(MailType type);
MailResult GetMailResult(Mail mail);
}
Each mail processor will implement this interface. Then you'll have this
public class MailProcessorExecutor
{
public MailProcessorSelector(IEnumerable<IProcessMail> processors)
{
_processors=processors;
}
public MailResult GetResult(Mail mail)
{
var proc=_processor.FirstOrDefault(p=>p.CanProcess(mail.MailType));
if (proc==null)
{
//throw something
}
return proc.GetMailResult(mail);
}
static IProcessMail[] _procCache=new IProcessMail[0];
public static void AutoScanForProcessors(Assembly[] asms)
{
_procCache= asms.SelectMany(a=>a.GetTypesDerivedFrom<IProcessMail>()).Select(t=>Activator.CreateInstance(t)).Cast<IProcessMail>().ToArray();
}
public static MailProcessorExecutor CreateInstance()
{
return new MailProcessorExecutor(_procCache);
}
}
//in startup/config
MailProcessorExecutor.AutoScanForProcessors([assembly containing the concrete types]);
//usage
var mailProc=MailProcessorExecutor.CreateInstance();
var result=mailProc.GetResult(mail);
Ok, so, the point is there will be an object in charge of selecting and executing the processors. The static methods are optional, the AutoScan
method searches any given assemblies for concrete IPocessMail
implementations then instantiates them. This allows you to add/remove any processor and it will be used automatically (no other setup required). Also there is no switch involved and there will never be. Note that GetTypesFromDerived
is a helper method I use (it's part of my CavemanTools library) and the Activator requires a parameterless constructor. Instead of it you can use a DI Container to get the instances (if you're using one or the processors have deps)
If you're using a DI Container, you don't need the CreateInstance
method. Just register MailProcessorExecutor
as a singleton and inject it (pass it as a constructor argument) where you need it.
Upvotes: 1
Reputation: 2125
You will need Visitor pattern for that.
{
public MailResult GetMailResult(mail)
{
_objectStructure.Visit(mail)
}
ObjectStructure _objectStructure= new ObjectStructure();
constructor() {
_objectStructure.Attach(new AcronisBackupVisitor());
_objectStructure.Attach(new ...)
}
}
class AcronisBackupVisitor: Visitor {
public override void Visit(HereComesAConcreteTypeDerivedFromMail concreteElement)
{
// do stuff
}
public override void Visit(HereComesAConcreteTypeDerivedFromMailOther concreteElement)
{
//don't do stuff. We are not in the right concrete mail type
}
}
In this way, we can differentiate by the dynamic type of the concrete Mail
you get. Just Make Mail
abstract, and derive Acronis mail, and other types of mails from that. I have began the implementation of this example from here.
Upvotes: 0
Reputation: 12849
In your case, just refactoring it into a method should be more than enough.
private ISingleMailProcessor CreateMailProcessor(MailType type)
{
switch (type)
{
case MailConnector.MailType.AcronisBackup:
return new AcronisProcessor();
case ......etc.
}
}
public MailResult GetMailResult(mail)
{
ISingleMailProcessor mailprocessor = CreateMailProcessor(mail.MailType);
return mailprocessor.ProcesMail(mail);
}
I don't think using factory is going to help you here. Factory would make sense if "type of mail" was decided outside the code that actually creates and sends the mail itself.
Then, the there would be specific factory for each type of mail to send, with interface to create a sender object. But even then, that would make sense if you needed a new instance of sender every time. In your case, just passing in the interface you have right now should be more than enough.
You can read my opinion on factories here : https://softwareengineering.stackexchange.com/a/253264/56655
Upvotes: 1