工厂阶级知道得太多了

时间:2009-03-30 16:40:30

标签: c# design-patterns refactoring factory

更新我已更新示例以更好地说明我的问题。我意识到它缺少一个特定点 - 即CreateLabel()方法始终采用标签类型,因此工厂可以决定要创建的标签类型。事实上,它可能需要获得更多或更少的信息,具体取决于它想要返回的标签类型。

我有一个工厂类,它返回表示要发送到打印机的标签的对象。

工厂类看起来像这样:

public class LargeLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public LargeLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class SmallLabel : ILabel
{
    public string TrackingReference { get; private set; }

    public SmallLabel(string trackingReference)
    {
        TrackingReference = trackingReference;
    }
}

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
        }
    }
}

假设我创建了一个名为CustomLabel的新标签类型。我想从工厂返回,但需要一些额外的数据:

public class CustomLabel : ILabel
{
    public string TrackingReference { get; private set; }
    public string CustomText { get; private set; }

    public CustomLabel(string trackingReference, string customText)
    {
        TrackingReference = trackingReference;
        CustomText = customText;
    }
}

这意味着我的工厂方法必须改变:

public class LabelFactory
{
    public ILabel CreateLabel(LabelType labelType, string trackingReference, string customText)
    {
        switch (labelType)
        {
            case LabelType.Small:
                return new SmallLabel(trackingReference);
            case LabelType.Large:
                return new LargeLabel(trackingReference);
            case LabelType.Custom:
                return new CustomLabel(trackingReference, customText);
        }
    }
}

我不喜欢这样,因为工厂现在需要满足最低的公分母,但同时CustomLabel类需要来获取自定义文本值。我可以提供额外的工厂方法作为覆盖,但我想强制执行CustomLabel需要值的事实,否则它只会被赋予空字符串。

实施此方案的正确方法是什么?

8 个答案:

答案 0 :(得分:5)

那么,你想如何调用工厂方法?

专注于您希望如何使用您的API,并且实施通常会使自己相当清楚。如果您将API的所需结果写为单元测试,则可以更轻松地完成此任务。

过载可能是正确的做法,但这实际上取决于你想如何使用工厂。

答案 1 :(得分:2)

如何使用Factory方法确定您需要的标签?

public class LabelFactory {
    public ILabel CreateLabel(string trackingReference, string customText) {
        return new CustomLabel(trackingReference, customText);
    }

    public ILabel CreateLabel(String trackingReference) {
        return new BasicLabel(trackingReference);
    }
}

您的工厂仍然需要了解每种类型(尽管您可以使用接口实现动态加载)但客户端需要知道的很少 - 根据提供的数据,工厂会生成正确的实现。 / p>

这是您所描述的简单问题的简单解决方案。我认为问题是对一个更复杂问题的过度简化,但不知道你的真正问题是什么,我宁愿不设计过于复杂的解决方案。

答案 2 :(得分:1)

这可能表明工厂模式不适合您。但是,如果您确实需要或希望坚持使用它,我建议创建可以传递到工厂的初始化类/结构,而不是字符串。是否要使用基本信息类的各种子类(基本上创建模仿标签类的初始化类层次结构)或者包含所有信息的一个类由您决定。

答案 3 :(得分:1)

您应该尝试使用配置类并将其实例传递给工厂。配置类将构建一个层次结构,其中将为您希望从工厂获得的每个结果存在一个特殊的配置类。每个配置类都会捕获工厂结果的特定属性。

对于您给出的示例,我将编写BasicLabelConfiguration和从中派生的CustomLabelConfiguration。 BasicLabelConfiguration捕获跟踪引用,而CustomLabelConfiguration捕获自定义文本。

最后,工厂根据传递的配置对象的类型做出决定。


以下是代码示例:

public class BasicLabelConfiguration
{
  public BasicLabelConfiguration()
  {
  }

  public string TrackingReference { get; set; }
}

public class CustomLabelConfiguration : BasicLabelConfiguration
{
  public CustomLabelConfiguration()
  {
  }

  public string CustomText { get; set; }
}

public class LabelFactory
{
  public ILabel CreateLabel(BasicLabelConfiguration configuration)
  {
    // Possibly make decision from configuration
    CustomLabelConfiguration clc = configuration as CustomLabelConfiguration;
    if (clc != null)
    {
      return new CustomLabel(clc.TrackingReference, clc.CustomText);
    }
    else
    {
      return new BasicLabel(configuration.TrackingReference);
    }
  }
}

最后你会像这样使用工厂:

// Create basic label
ILabel label = factory.CreateLabel(new BasicLabelConfiguration 
{
  TrackingReference = "the reference"
});

// Create basic label
ILabel label = factory.CreateLabel(new CustomLabelConfiguration 
{
  TrackingReference = "the reference",
  CustomText = "The custom text"
});

答案 4 :(得分:1)

如果没有进一步的信息,很难给出任何建议,但假设工厂模式是您真正需要的,您可以尝试以下方法:

将所需的参数打包在某种属性映射中(例如,字符串映射到字符串),并将其作为参数传递给工厂的create方法。使用众所周知的标记作为地图中的关键字,允许专业工厂根据自己的喜好提取和解释映射的值。

这至少可以让你暂时维护一个工厂界面,并且如果(或什么时候)你发现工厂模式不是正确的,那么推迟处理架构问题。

(哦,如果你真的想在这里使用工厂模式,我强烈建议你让它可插拔,以避免为每种新标签类型修改工厂。)

答案 5 :(得分:1)

您正试图将模式强制进入不适合的场景。我建议放弃这个特定的模式,而不是让最简单的解决方案成为可能。

我认为在这种情况下,我只有一个类Label,它有一个自定义文本的文本字段,通常为空/空,但如果标签需要自定义,可以设置。它简单,不言自明,不会给维护程序员带来任何噩梦。

public class Label
{
    public Label(string trackingReference) : this(trackingReference, string.Empty)
    {
    }

    public Label(string trackingReference, string customText)
    {
        CustomText = customText;
    }

    public string CustomText ( get; private set; }

    public bool IsCustom
    {
        get
        {
            return !string.IsNullOrEmpty(CustomText);
        }
    }
}

答案 6 :(得分:1)

问题更新后的答案更新 - 见下文

我仍然认为您正确使用Factory模式,并在重载CreateLabel方法时更正;但我认为在将LabelType传递给CreateLabel方法时,您忽略了使用Factory模式的重点。

关键点:Factory模式的整个目的是封装选择哪个具体子类进行实例化和返回的逻辑。调用代码不应该告诉Factory要实例化哪个类型。这样做的好处是调用工厂的代码可以避免将来对该逻辑的更改,也可以防止将新的具体子类添加到工厂。您需要的所有调用代码都是Factory,以及从CreateLabel返回的Interface类型。

您调用Factory的代码中的逻辑当前必须看起来像这样的伪代码......

// Need to create a label now
ILabel label;
if(we need to create a small label)
{
   label = factory.CreateLabel(LabelType.SmallLabel, "ref1");
}
else if(we need to create a large label)
{
   label = factory.CreateLabel(LabelType.LargeLabel, "ref1");
}
else if(we need to create a custom label)
{
   label = factory.CreateLabel(LabelType.CustomLabel, "ref1", "Custom text")
}

...所以你明确告诉工厂要创建什么。这很糟糕,因为每次向系统添加新标签类型时,您都需要......

  1. 更改工厂代码以处理新的LabelType值
  2. 去工厂的名字
  3. ,然后添加一个新的else-if

    但是,如果将选择LabelType值的逻辑移动到工厂中,则可以避免这种情况。逻辑与其他所有内容一起封装在工厂中。如果系统中添加了新类型的标签,则只需更改工厂。调用工厂的所有现有代码保持不变,没有重大变化。

    您当前的调用代码用于确定是否需要大标签或小标签的数据是什么?该数据应该传递给工厂的CreateLabel()方法。

    您的工厂和标签类可能如下所示......

    // Unchanged
    public class BasicLabel: ILabel
    {
        public LabelSize Size {get; private set}
        public string TrackingReference { get; private set; }
    
        public SmallLabel(LabelSize size, string trackingReference)
        {
            Size = size;
            TrackingReference = trackingReference;
        }
    }
    
    
    
    // ADDED THE NULL OR EMPTY CHECK
    public class CustomLabel : ILabel
    {
        public string TrackingReference { get; private set; }
        public string CustomText { get; private set; }
    
        public CustomLabel(string trackingReference, string customText)
        {
            TrackingReference = trackingReference;
            if(customText.IsNullOrEmpty()){
               throw new SomeException();
            }
            CustomText = customText;
        }
    }
    
    
    
    public class LabelFactory
    {
        public ILabel CreateLabel(string trackingReference, LabelSize labelSize)
        {
             return new BasicLabel(labelSize, trackingReference);
        }
    
        public ILabel CreateLabel(string trackingReference, string customText)
        {
             return new CustomLabel(trackingReference, customText);
        }
    }
    

    我希望这有用。

答案 7 :(得分:0)

通过阅读您的问题,听起来您的UI收集信息,然后使用工厂创建适当的标签。我们在开发的CAD / CAM应用程序中使用了不同的方法。

在启动期间,我的应用程序使用工厂方法创建标签的主列表。 我的一些标签有初始化参数,因为它们是彼此的变体。例如,我们有三种类型的扁平部件标签。而其他参数则具有用户定义或在设置时未知的参数。

在第一种情况下,初始化在工厂方法中处理。所以我创建了三个FlatPartLabel实例,传递了所需的参数。

在第二种情况下,Label接口有一个configure选项。这将由标签打印机对话框调用以填充设置面板。在您的情况下,这是传递跟踪引用和CustomText的位置。

我的标签界面还为每种Label类型返回一个唯一的ID。如果我有一个特定的命令来处理这种类型的标签,那么我将遍历我的应用程序中的标签列表,找到哪个与ID匹配,将其转换为特定类型的标签,然后进行配置。当用户想要仅为特定的平面部件打印一个标签时,我们这样做。

这样做意味着您可以在标签所需的参数中任意复杂,而不会给您的工厂带来不必要的参数负担。