【问题标题】:using the factory pattern with derived classes accepting diverse number of parameters使用带有接受不同数量参数的派生类的工厂模式
【发布时间】:2016-04-27 10:42:21
【问题描述】:

我正在使用以下factory pattern:

using System;

class Program
{
    abstract class Position
    {
    public abstract string Title { get; }
    }

    class Manager : Position
    {
    public override string Title
    {
        get
        {
        return "Manager";
        }
    }
    }

    class Clerk : Position
    {
    public override string Title
    {
        get
        {
        return "Clerk";
        }
    }
    }

    class Programmer : Position
    {
    public override string Title
    {
        get
        {
        return "Programmer";
        }
    }
    }

    static class Factory
    {
    /// <summary>
    /// Decides which class to instantiate.
    /// </summary>
    public static Position Get(int id)
    {
        switch (id)
        {
        case 0:
            return new Manager();
        case 1:
        case 2:
            return new Clerk();
        case 3:
        default:
            return new Programmer();
        }
    }

使用这种模式的方法在同一来源的示例中:

static void Main()
{
for (int i = 0; i <= 3; i++)
{
    var position = Factory.Get(i);
    Console.WriteLine("Where id = {0}, position = {1} ", i, position.Title);
}
}

如果我的派生类为其构造函数使用不同数量的参数,我应该使用这种模式吗?

我可能需要进行的修改是在实例化工厂时:

var position = Factory.Get(i);

我可能需要为所有派生类传入参数,无论它们是否会使用它们:

var position = Factory.Get(i, param1, param2, param3);

需要修改switch语句:

public static Position Get(int id, param1, param2, param3) //HERE IS THE MODIFIED PARAM LIST
{
    switch (id)
    {
    case 0:
        return new Manager(param1); //MODIFIED
    case 1:
    case 2:
        return new Clerk(param2, param3); //MODIFIED
    case 3:
    default:
        return new Programmer(param3); //MODIFIED
    }
}

我对工厂模式所做的修改是否会破坏该模式,我是否应该使用不同的模式来创建对象?

【问题讨论】:

  • 您能否提供更多有关Get 方法使用者的背景信息?它是如何获取参数值的?
  • 在我看来像是一个反模式。如果您需要知道创建站点的参数,则您已经需要知道 ID 的含义,因此您无法从调用单个工厂方法中获得任何好处。如果每个构造函数的参数不同,会发生什么?您将拥有三组独立的参数,全部传递给Get()?这看起来很糟糕。
  • 构造函数中参数的数量并不重要。这只是初始化类的方便。有些类有很多构造函数,而另一些则没有(或基类的默认值)。
  • 工厂的客户不应该知道 Products 的依赖关系。这意味着工厂不应该考虑“如何创建对象”,而只考虑“创建哪个对象”。如果这不符合您的需求,您应该查看 Builder 模式。
  • @jdweng 谢谢。你能举一个例子,说明类如何在工厂模式的上下文中从基类中获取默认构造函数?

标签: c# .net visual-studio design-patterns factory-pattern


【解决方案1】:

使用工厂是为了抽象出如何创建对象的要求,让客户端不需要知道具体如何创建对象

该模式最经典的用途之一是让应用根据可能返回 MSSQL、SQLite、MySQL 等连接的键创建数据库连接。客户端并不关心实现是什么,只要它支持所有必需的操作即可。

所以客户端应该完全不知道所需的参数。

这里是怎么做的。

我稍微扩展了Position 类:

abstract class Position
{
    public abstract string Title { get; }
}

class Manager : Position
{
    public Manager(string department) { }
    public override string Title => "Manager";
}

class Clerk : Position
{
    public override string Title => "Clerk";
}

class Programmer : Position
{
    public Programmer(string language) { }
    public override string Title => "Programmer";
}

现在我创建了这样的Factory 类:

static class Factory
{
    private static Dictionary<int, Func<Position>> _registry =
        new Dictionary<int, Func<Position>>();

    public static void Register(int id, Func<Position> factory)
    {
        _registry[id] = factory;
    }

    public static Position Get(int id)
    {
        return _registry[id].Invoke();
    }
}

然后使用工厂变得很容易。当你初始化应用程序时,你会编写这样的代码:

Factory.Register(1, () => new Manager("Sales"));
Factory.Register(2, () => new Clerk());
Factory.Register(3, () => new Programmer("C#"));

现在,稍后,当客户端代码需要 Position 对象时,它只需要这样做:

var position = Factory.Get(3);

在我的测试中,当我输出position.Title 时,我将Programmer 打印到控制台。

【讨论】:

  • 这是一个绝妙的答案,但是我想请您谈谈您是否认为这可能是过度设计的?
  • @l--''''''---------'''''''''''' - 不,这不是过度设计。如果您想从使用对象的代码中抽象出对象的创建,这正是您需要做的事情。请记住,有大量的控制反转/依赖注入框架以非常强大的方式为您执行此操作。如果您的需求不仅仅是基本的,您应该考虑使用它们。
  • 知道了。非常感谢。您能否推荐其中一种框架,或许是一种易于使用的框架?
  • @l--''''''------''''''''''''' - 我会看看这个列表 - hanselman.com/blog/… - 我没有广泛使用任何这些,所以我不能推荐。几年前我自己写的,一直在用。
【解决方案2】:

这个例子有点太愚蠢了,但是是的,你可以。

也就是说,这个例子可能会导致一些问题,因为程序员可能会对如何使用工厂感到困惑。例如,假设您必须定义一个 GUI 来创建位置:GUI 是否会要求用户提供所有 3 个param 值,即使它们对首先定义的位置没有意义?如果您回答“是”,用户会感到困惑,如果您回答“否”,那么工厂就不是应有的黑匣子了。

我使用这种方法的一个例子是计费;我的应用程序将同一个月的服务批量计费给很多人。有的人按月的自然天数计费,有的人按可工作的天数计​​费。由于获得可工作的日子有点慢(它必须咨询数据库以了解当地和国定假日)我将其缓存并将其传递给需要它的实例。

类似于(Java):

public class BillerFactory {
  private HashMap<Date, ListOfHolidaysInMonth> holidayCache =
     new HashMap<>();

  ...

  public getBiller(BillingType billingType, Date firstOfMonth) {
    switch (billingType) {
      case BillingType.NATURAL:
         return new NaturalBiller(firstOfMonth);
      case BillingType.LABORAL:
         ListOfHoliday holidays = this.holidayCache.get(firstOfMonth);
         if (holidays == null) {
            holidays = this.calculateHolidays(firstOfMonth);
            holidayCache.put(firstOfMonth, holidays);
         }
         return new LaboralBiller(firstOfMonth, holidays);
       }
     }

TLDR:问题不在于构造函数具有不同的参数,而是在您的示例中,您强制客户端提供可能没有意义的数据。

【讨论】:

  • 好点!! “问题不在于构造函数有不同的参数,而是在你的例子中你强迫客户端提供可能没有意义的数据。” --- 你能为我的用例推荐一种不同类型的模式吗?
  • 您可以扩展工厂,使其不为您提供最终实例,而是最终提供最终实例的代码。在 GUI 示例中,它可以为您提供一个 JFrame 子类,其中包含每个实例所需的营地。也就是说,它开始散发出过度工程的味道,所以也许不值得。
【解决方案3】:

下面的代码将使用 Position 类中的默认构造函数。

         static void Main(string[] args)
        {
            Manager mngr = new Manager();
        }
    }
    public abstract class Position
    {
        public abstract string Title { get; }
        public Position()
        {
        }
    }
    public class Manager : Position
    {
        public override string Title
        {
            get
            {
                return "Manager";
            }
        }
    }

【讨论】:

    【解决方案4】:

    我没有足够的上下文来建议一个好的替代方案,但是您的示例使用起来不舒服:工厂模式的想法是封装对象的创建,但是由于您对不同的对象有一组不同的参数,所以您有要了解具体实现之间的一些差异,或者您每次都必须提供无用的数据。也许只使用构造函数会更好?

    【讨论】:

    • 你的意思是保持模式?但是我如何将信息传递给每个派生类?
    • @l--''''''------'''''''''''' - 我认为他是说这种模式不合适.
    • @l--''''''---------'''''''''''' 不,我的意思是不要在在这种情况下,像往常一样创建你的对象。
    猜你喜欢
    • 1970-01-01
    • 2014-11-14
    • 1970-01-01
    • 2020-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    相关资源
    最近更新 更多