具有 class 的工厂模式可以具有不同的 class 子类型

Factory pattern with a class that can has different class sub types

我有来自多个组织(警察、消防、办公室)的数据,需要以不同格式输出。

为了实现这一点,我定义了以下内容(这有点简化):

T运行行动class -

  1. "Success" 指标 - 布尔值。
  2. "Type of department"- 字符串或枚举。
  3. A class 可以是任何类型 - 警察、消防或办公室(如您所见,我的问题是关于此的)。
  4. GenerateOutput() 方法 - 处理文件格式的生成。

警察class

  1. 年龄 - 字符串
  2. VehicleNumber - 整数
  3. 主管 - 字符串

火 class

  1. 名称 - 字符串
  2. FireEngineNumber - 整数
  3. 县 - 枚举
  4. WorkTimings - 枚举

办公室Class

  1. 年龄 - 字符串
  2. DeskNumber - 整数
  3. 部门 - 字符串
  4. PayScale - 枚举
  5. IsManagement - 布尔值

如您所见,警察、消防和办公室 classes 没有任何共同点,主要用作数据承载实体。我打算使用工厂来 return 一个适当的泛型(不是 C# 泛型)T运行saction 对象与数据(T运行saction 对象与 Police, Fire或其中的 Office 数据),然后将 returned 对象传递给确定文件格式的策略模式(CSV、Excel 或 XML;在配置文件)每个都需要。

我的问题出在 T运行saction 对象的定义上。

  1. “3”中的class是什么类型。需要 T运行saction class 吗?每个组织的数据不同,没有共同的成员,我无法为所有人定义一个共同的class。

  2. 整体设计是否合适?我还应该考虑哪些其他设计?

根据彼得的以下评论: 我认为使用泛型可能有效,但我 运行 遇到了问题。我想使用工厂 return 请求的对象,使用 GetT运行sactionObject,如下所示。 return 类型的 GetT运行sactionObject 应该是什么来适应这个。

class T运行sactionFactory {

        Dictionary<string, Type> typeClassLookup;

    public TransactionFactory()
    {
        typeClassLookup = new Dictionary<string, Type>();
        typeClassLookup.Add("Police", typeof(PoliceData));
        typeClassLookup.Add("Fire", typeof(FireData));
    }

    Transaction<????> GetTransactionObject(string org)
    {

        if( typeClassLookup.TryGetValue(org, out typeValue))
        {
            switch (typeValue.ToString())
            {
                case "policeData":
                    transactionObject = new Transaction<PoliceData>() { Data = new PoliceData(), params = null};
                case "FireData":
                    transactionObject = new Transaction<FireData>() {Data = new FireData(), params = null};
            }
        }
        return transactionObject;

如果类型真的没有任何共同点,那么您不需要显式基class。 System.Object 就足够了,就像许多其他泛型类型一样(即任何没有约束的泛型类型)。

换句话说,您可以声明为:

class Transaction<T>
{
    public bool Success { get; private set; }
    public T Entity { get; private set; }

    public Transaction(bool success, T entity)
    {
        Success = success;
        Entity = entity;
    }

    public void GenerateOutput() { /* something goes here */ }
}

就个人而言,我会避免添加 "department type" 成员。毕竟,这是类型参数 T 中隐含的。但是如果你愿意,你可以很容易地将它添加到上面。

如果您发现这些类型确实有一些共同点,那么您的 Transaction<T> 类型需要做的不仅仅是简单地保留其中一种类型的实例(这几乎是它所能做的)做没有约束),那么您将能够将该通用性放入接口或基础 class (取决于具体需要),并在 Transaction<T> [=85= 的约束中指定].

请注意,您不清楚 GenerateOutput() 的意思,或者它应该如何工作。但是假设您想要针对每个 Entity 值的特定输出,在我看来 that 就是您的 "something in common"。即,根本不需要 Transaction<T> class 来实现该方法,而是每个实体类型。在那种情况下,你有这样的东西:

interface IDepartmentEntity
{
    void GenerateOutput();
}

class Office : IDepartmentEntity
{
    public void GenerateOutput() { /* department-specific logic here */ }
}

// etc.

然后你可以声明:

class Transaction<T> where T : IDepartmentEntity
{
    public bool Success { get; private set; }
    public T Entity { get; private set; }

    public Transaction(bool success, T entity)
    {
        Success = success;
        Entity = entity;
    }

    public void GenerateOutput() { Entity.GenerateOutput(); }
}


编辑:

根据 Prasant 的后续编辑,请求对 GetTransactionObject()

提出建议

执行此操作的正确方法取决于调用者和上下文,问题中未提供详细信息。恕我直言,best 场景是调用者知道类型的地方。这允许使用泛型的全部功能。

例如:

class TransactionFactory
{
    public Transaction<T> GetTransactionObject<T>()
        where T : IDepartmentEntity, new()
    {
        return new Transaction<T>()
        {
            Data = new T(),
            params = null
        }
    }
}

然后你这样调用:

Transaction<FireData> transaction = factory.GetTransactionObject<FireData>();

调用者当然已经知道它正在创建的类型,然后可以填写 transaction.Data 对象的适当属性。

如果该方法不可行,那么您将需要 Transaction<T> 本身有一个基础 class,或实现一个接口。注意,在我原来的例子中,IDepartmentEntity接口只有一个方法,和Transactionclass.

接口中的GenerateOutput()方法是一样的

也许,该接口实际上是关于生成输出而不是数据实体。调用它,而不是 IDepartmentEntity,类似于 IOutputGenerator.

在那种情况下,你可能会遇到这样的事情:

class Transaction<T> : IOutputGenerator
{
    // all as before
}

class TransactionFactory
{
    public IOutputGenerator GetTransactionObject(string org)
    {
        if( typeClassLookup.TryGetValue(org, out typeValue))
        {
            switch (typeValue.ToString())
            {
                case "policeData":
                    transactionObject = new Transaction<PoliceData>() { Data = new PoliceData(), params = null};
                case "FireData":
                    transactionObject = new Transaction<FireData>() {Data = new FireData(), params = null};
            }
        }
        return transactionObject;
    }
}

这是一个较差的解决方案,因为这意味着调用者只能直接访问 IOutputGenerator 功能。其他任何事情都需要进行一些类型检查和特殊情况代码,真正应该尽可能避免的事情。

注意:如果 Transaction 类型有其他成员,像 GenerateOutput() 方法一样,独立于此处包含的类型 T,这对调用者有用不知道 T,那么上述的一个可能变体是不重用用于部门特定数据类型的接口,而是为 Transaction<T> 声明一个基础 class,命名为当然 Transaction,包含与 T 无关的所有成员。那么return值可以是Transaction.

  1. What type does the class in "3." of the Transaction class need to be?

要将您的部门 class 与各种导出类型分离,我建议您让部门 class 实现一个通用接口。像这样:

public interface Exportable {
    // return a list of attribute names, values, and types to export
    IList<Tuple<String, String, Type>> GetAttributes();
}

例如:

public class Police : Exportable {
    public IList<Tuple<String, String, Type>> GetAttributes() {
        // return list size 3 - attribute info for Age, VehicleNumber, Supervisor
    }
}
  1. Is the overall design appropriate? What other designs should I consider?

Transaction class 设计似乎不太适合这个问题。

考虑一个 Export class,每个导出类型都有一个方法,每个方法接收从 Exportable 接口方法返回的属性。基本大纲:

public static class Export {
    public static boolean CSV(IList<Tuple<String, String, Type>> attributes) {
        // export attributes to CSV, return whether succeeded
    }

    public static boolean Excel(IList<Tuple<String, String, Type>> attributes) {
        // export attributes to Excel, return whether succeeded
    }

    // same thing for XML
}