使用带有派生 类 的工厂模式接受不同数量的参数

using the factory pattern with derived classes accepting diverse number of parameters

我正在使用以下 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
    }
}

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

这个例子有点过于简单化了,但是是的,你可以。

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

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

有点像 (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:问题不在于构造函数具有不同的参数,而是在您的示例中,您强迫客户端提供可能没有意义的数据。

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

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

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

工厂的用途是将如何创建对象的需求抽象出来,让客户端在不需要知道如何创建对象的细节的情况下请求对象 .

该模式最 classic 的用途之一是让应用程序根据可能 return MSSQL、SQLite、MySQL 等的密钥创建数据库连接, 联系。客户端不关心实现是什么,只要它支持所有必需的操作。

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

操作方法如下。

我稍微扩展了 Position classes:

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 class 这样的:

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 打印到控制台。