基于输入变量的工厂

Factory based on input variable

我阅读了一些关于工厂和抽象工厂模式的教程,并看到了它的一些示例。在其中一篇教程中,我读到工厂模式可以替代主要的 "if" 或 "switch case" 语句,并遵循 open/closed(坚实的)原则。

在我的一个项目中有一个巨大的 "switch case",我想用 (n)(抽象)工厂替换它。它已经是基于接口的,所以实现工厂应该不会那么困难,但在我在教程中阅读的所有示例中,工厂都基于配置生成了一个具体类型。任何人都可以指出我正确的方向如何实现一个可以根据遵循 Solid 原则的枚举生产多种类型的工厂并替换大 "switch case"...或者我被误导了 "switch case" 搬到工厂了?

此时代码:

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.
  }
}

首先我要推荐CCD Webcast about factories。它对这个话题很有帮助,也展示了我们可能遇到的一些问题。 您还可以在 this objectmentor document

上找到一些有用的信息

作为您看到的网络广播的总结,您越想在 "creation" 的问题域中遵循 OpenClosed Principal,您可以工作的类型安全性就越差。

因此,抽象工厂也可以对一些更动态的值(如字符串)进行操作。例如,您可以有一个方法。

string GetAllPossibilities(); // Returns all possible kinds that can be constructed

和一个相关的

T Create<T>(string kind)

调用现在只需传递唯一标识所请求实例种类的字符串。您的标记可以是 "selfmade" 之类的 "Rectangle" 或事件类型名称,但这意味着组件之间存在更多的依赖性,因为命名空间更改可能会破坏调用者。

所以您的来电可能类似于以下之一:

Factory.Create("Acronis") // Your Marker

Factory.Create("MYNameSpace.AcronisProcessor")  //TypeName

Factory.Create<AcronisProcessor>() //Type 

所以你不会在工厂外有 switch 语句。在工厂内部,您可能有一些,或者您可以考虑创建某种动态对象。

Factory 仍然可以使用 swithc 语句切换您的自制标识符或代码执行类似

的操作
var type = Type.GetType(kind);
return Activator.CreateInstance(type) as T;

但是由于它与主域逻辑是分开的,所以它不再像以前那样重要了。

乍一看,如果您获得新选项,您不会 api 真正的重大更改。

但仍然存在某种潜在的语义依赖性。

编辑:正如您在下面的讨论中看到的那样,我删掉了一些细节,因为我认为它们会模糊要点(OpenClosed Principles 和工厂模式)。但是还有一些其他的点是不能忘记的。喜欢 "What is an application root and how has it to be designed" 。要了解所有详细信息,网络广播(也包括本网站的其他网站)是了解此详细信息的更好方法,然后 post 此处。

在你的情况下,只需将其重构为一个方法就足够了。

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);
}

我认为使用工厂不会对您有所帮助。如果 "type of mail" 是在实际创建和发送邮件本身的代码之外决定的,那么工厂是有意义的。

然后,每种类型的邮件都会有特定的工厂来发送,并带有创建发件人对象的接口。但即便如此,如果您每次都需要一个新的 sender 实例,那也是有意义的。在你的情况下,只要传入你现在拥有的界面就足够了。

您可以在此处阅读我对工厂的[​​=20=]意见:https://softwareengineering.stackexchange.com/a/253264/56655

为此您需要访客模式。

{
    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
    }
}

这样,我们就可以根据你得到的具体的动态类型来区分了Mail。只需将 Mail 抽象化,并从中派生 Acronis 邮件和其他类型的邮件。我从here.

开始实施这个例子

你在那里实施的是战略模式,它仍然是一种有效的方法。无论如何,如果你想更换整个开关并使其更易于维护,你可以使用这个

public interface IProcessMail
{
     bool CanProcess(MailType type);
     MailResult GetMailResult(Mail mail);
}

每个邮件处理器都会实现这个接口。然后你会得到这个

 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);

好的,关键是会有一个对象负责选择和执行处理器。静态方法是可选的,AutoScan 方法在任何给定的程序集中搜索具体的 IPocessMail 实现,然后实例化它们。这允许您 add/remove 任何处理器,它将自动使用(不需要其他设置)。也没有涉及开关,永远不会。请注意 GetTypesFromDerived 是我使用的辅助方法(它是我的 CavemanTools 库的一部分)并且 Activator 需要无参数构造函数。您可以使用 DI 容器代替它来获取实例(如果您正在使用一个或处理器有 deps)

如果您使用的是 DI 容器,则不需要 CreateInstance 方法。只需将 MailProcessorExecutor 注册为单例并在需要的地方注入它(将其作为构造函数参数传递)。