【问题标题】:Factory based on input variable基于输入变量的工厂
【发布时间】:2015-07-22 17:13:04
【问题描述】:

我阅读了一些关于工厂和抽象工厂模式的教程,并看到了一些例子。在其中一个教程中,我读到工厂模式可以替换主要的“if”或“switch case”语句,并遵循开放/封闭(可靠)原则。

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

此时的代码:

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

【问题讨论】:

  • enum 的长度是固定的——你不能以任何方式扩展——所以它就像一个switch 语句。所以在某些方面它有同样的限制。您能否发布您的enum 和您要替换的if/switch 语句的示例?
  • 我添加了一些已经在项目中的代码。
  • 所以总是MailResultMail.MailType返回?
  • 是的,并且邮件结果是一个类,其中MailType是一个枚举作为Mail类的属性

标签: c# design-patterns factory-pattern


【解决方案1】:

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

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 实现,然后实例化它们。这允许您添加/删除任何处理器,它将自动使用(无需其他设置)。也没有涉及的开关,永远不会有。请注意,GetTypesFromDerived 是我使用的辅助方法(它是我的CavemanTools 库的一部分),并且 Activator 需要一个无参数的构造函数。取而代之的是,您可以使用 DI 容器来获取实例(如果您使用的是一个或处理器有 deps)

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

【讨论】:

  • 你的意思是我的方法使用了策略? GetTypesDerivedFrom 是否使用反射(您必须考虑效率和类型安全)?
  • 我的意思是 OP 代码类似于策略。是的GetTypesDerivedFrom 使用反射。效率? :))) 你不知道有多少东西在背后使用反射。关于类型安全,是的,我忘记了演员表(我已经更新了答案以包含它)。
  • 我试图为 OP 的问题考虑一个策略解决方案,但是会有一个开关,将策略的上下文分配给实际使用的 AcronisBackupStrategy。然后拨打GetResult就可以了。我认为策略不适用。我不知道幕后使用了多少反射,但我有一个项目,只需消除 1 个 LINQ 即可恢复软件的可用性。
  • 是的,OP 代码中有一个开关,他的 代码是 Strategy。我的是一个组合。关于您的 1 Linq,这是您的特定边缘案例。顺便说一句,只有反射的某些部分很慢,而我使用的不是。
  • 尚未尝试过,但为我的问题找到了一个合法的解决方案。我要试试!
【解决方案2】:

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

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

我认为在这里使用工厂不会对您有所帮助。如果“邮件类型”是在实际创建和发送邮件本身的代码之外决定的,工厂将是有意义的。

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

您可以在这里阅读我对工厂的意见https://softwareengineering.stackexchange.com/a/253264/56655

【讨论】:

  • Don't fall for the "YAGNI" excuse,第一条评论提到。正如 Luuk 所说,这个开关已经太大了。
  • 我同意我看到的主要问题是创建实例只是一个完全独立的问题,不应该成为业务领域的一部分。不管怎么提取,都应该提取
【解决方案3】:

您将需要访问者模式。

{
    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开始实现这个例子。

【讨论】:

    【解决方案4】:

    首先我想推荐CCD Webcast about factories。它对这个主题非常有帮助,也显示了我们可能遇到的一些问题。 你也可以在this objectmentor document 上找到一些好的信息

    作为网络广播的总结,您可以看到,您越想在“创建”问题域中遵循 OpenClosed 主体,您可以工作的类型安全性就越低。

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

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

    和相关的

    T Create<T>(string kind)
    

    调用现在只需传递唯一标识请求实例种类的字符串。您的标记可能是“自制”的东西,例如“矩形”或事件类型名称,但这意味着组件之间存在更多的依赖关系,因为命名空间更改可能会破坏调用者。

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

    Factory.Create("Acronis") // Your Marker
    
    Factory.Create("MYNameSpace.AcronisProcessor")  //TypeName
    
    Factory.Create<AcronisProcessor>() //Type 
    

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

    工厂仍然可以有 swithc 语句来切换你的自制标识符或代码做类似的事情

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

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

    如果您有新的选项,乍一看,您不会对 api 进行重大更改。

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

    编辑:正如您在下面的讨论中看到的那样,我切断了一些细节,因为我认为它们会模糊主要观点(OpenClosed Principales 和 Factory Pattern)。但是还有一些其他的点不应该忘记。比如“什么是应用程序根以及如何设计它”。要获取所有详细信息,网络广播(也包括本网站的其他内容)是了解此详细信息的更好方法,而不是此处的这篇文章。

    【讨论】:

    • 答案甚至没有提到示例中的开关案例在哪里。在您的示例中,Create 将包含取决于kind 的开关盒。
    • 几分钟前我才注意到,我现在正在扩展我的帖子以清除它
    • 恕我直言,这不是工厂的好用法。您已经链接了 Bob 叔叔的文章,并且 U. Bob 说开关应该包含在 Main 方法中。只是为了初始化工厂。你不用这个参数来创建,而是用正确的类型初始化工厂。
    • 好点,但 imo 这真的取决于问题域。当然,我通常会尽量避免切换,但我看不出这是如何在 luuk 域中进行的。也可能给工厂一个允许类型的 IEnumerable 或 IENumerable> ,但这是非常详细的,而且更重要的是让原理更清晰。例如,如果他目前只有两个选项,那么 imo 一个 if 语句就足以开始了。 (注意优化和 YAGNI)。重点是域没有与应用程序根取得联系
    • 投反对票的不是我,只是为了记录。我们不能考虑 YAGNI,因为需求已经存在。开关太大了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-24
    • 1970-01-01
    • 2016-03-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多