【问题标题】:Factory pattern with a class that can has different class sub types具有可以具有不同类子类型的类的工厂模式
【发布时间】:2015-01-29 04:39:55
【问题描述】:

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

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

事务类 -

  1. “成功”指示符 - 布尔值。
  2. “部门类型”- 字符串或枚举。
  3. 可以是任何类型的类 - 警察、消防或办公室(您将看到我的问题是关于此的)。
  4. GenerateOutput() 方法 - 处理文件格式的生成。

警察课

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

消防类

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

办公类

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

如您所见,Police、Fire 和 Office 类没有任何共同点,主要用作数据承载实体。我打算使用工厂返回带有数据的适当通用(不是 C# 通用)事务对象(其中包含 Police、Fire 或 Office 数据的事务对象),然后将返回的对象传递给确定每个人需要的文件格式(CSV、Excel 或 XML;在配置文件中指定)的策略模式。

我的问题在于 Transaction 对象的定义。

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

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

基于以下 Peter 的 cmets: 我认为使用泛型可能会起作用,但我遇到了一个问题。我想使用工厂返回请求的对象,使用 GetTransactionObject,如下所示。 GetTransactionObject 的返回类型应该是什么来适应这个。

类事务工厂 {

        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;

【问题讨论】:

  • #2 当然是基于意见的。对于#1,即使它们没有共同的字段,您仍然可以拥有一个共同的基类。它只是一个没有成员的空(可能是抽象的)基类(或者它可能包含派生类必须实现的一些通用抽象函数)。或者您可以使用界面作为标记。没有更多信息,实在是太宽泛了。
  • 我相信你可以有一个通用的接口,可以称为IWorkPlace,可以是办公室或警察局,也可以是定义一些常见行为的消防部门。缺陷在于,如果类型定义了其他不常见的行为,那么您可能不得不不时地强制转换该对象以使用特定行为,但它仍然可能解决目的。
  • 谢谢马特,如果你现在看一下这个问题,将不胜感激,已经添加了更多细节。
  • 没错,Nikhil,作为最后的手段,我想继续将这些对象转换为适当的类型。您是否会考虑其他设计以及附加信息?
  • 您应该发布实际代码。

标签: c# design-patterns


【解决方案1】:

如果类型真的没有共同点,那么你不需要显式的基类。 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 */ }
}

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

如果并且当您发现这些类型确实有一些共同点时,您的 Transaction&lt;T&gt; 类型需要做的不仅仅是简单地保留其中一种类型的实例(这几乎是它在没有约束的情况下所能做的所有事情),然后您将能够将该通用性放入接口或基类(取决于具体需要),并在 Transaction&lt;T&gt; 类的约束中指定。

请注意,尚不清楚您对GenerateOutput() 的作用是什么,或者它应该如何工作。但是假设您想要特定于每个 Entity 值的输出,在我看来 that 是您的“共同点”。即,需要实现该方法的根本不是Transaction&lt;T&gt; 类,而是每个实体类型。在这种情况下,你有这样的事情:

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


编辑:

Per Prasant 的后续编辑,在GetTransactionObject() 上征求意见...

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

例如:

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&lt;T&gt; 本身拥有一个基类,或实现一个接口。请注意,在我原来的示例中,IDepartmentEntity 接口只有一个方法,与Transaction 类中的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&lt;T&gt;声明一个基类,当然命名为Transaction,包含所有与T无关的成员.那么返回值可以是Transaction

【讨论】:

  • 谢谢,我认为这可能有效。如果您可以查看已编辑的问题以了解有关工厂方法的问题,那就太好了。
  • 我其实更喜欢劣质的解决方案。只是因为我认为工厂的调用者不需要知道要返回的类型。这不是调用者传递参数并获取适当对象的工厂的重点吗?我可能错了,想知道你的想法。
  • @Prasant:不同的工厂目标不同,工作方式也不同。我不同意工厂的要点 是不知道返回对象的类型。对我来说,更突出的问题是:调用者是否真的知道类型。如果调用者由于某种原因不能或不应该知道类型,那么让工厂方法完成这项工作是有意义的。但是如果调用者总是知道期望返回什么类型的对象,那么让调用者将该类型提供给工厂方法会更有意义。我对您的来电者了解不多,无法知道这里的最佳选择
  • @Prasant:作为调用者已知类型的工厂方法的示例,考虑各种Tuple.Create() 重载。显然,这是一个有效且有用的工厂模式实现,但调用者同样清楚地规定了返回对象的类型。另请注意,至少维基百科区分factory method pattern 和更一般的factory pattern。在他们看来,factory method pattern 就是您所描述的:调用者具体不知道返回的类型。但一般工厂不需要这样做。
【解决方案2】:
  1. “3”中的类是什么类型。 Transaction 类的需要是什么?

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

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. 整体设计合适吗?我还应该考虑哪些其他设计?

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

考虑一个Export 类,每个导出类型都有一个方法,每个方法接收从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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多