Luuk Krijnen
Luuk Krijnen

Reputation: 1192

Factory based on input variable

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

Answers (4)

Boas Enkler
Boas Enkler

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

MikeSW
MikeSW

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

ntohl
ntohl

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

Euphoric
Euphoric

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

Related Questions