基类作为工厂?


14

我在周末写一些代码,发现自己想写一个工厂作为基类中的静态方法。

我的问题只是想知道这是否是ac#惯用语方法?

我认为可能不是因为基类具有派生类的知识。

也就是说,我不确定要获得相同结果的简单方法。整个其他工厂类(至少对我而言)似乎不需要的复杂性(?)

就像是:

class Animal
{
  public static Animal CreateAnimal(string name)
  {
     switch(name)
     {
        case "Shark":
          return new SeaAnimal();
          break;
        case "Dog":
          return new LandAnimal();
          break;
        default:
          throw new Exception("unknown animal");
     }
  }
}
class LandAnimal : Animal
{
}

class SeaAnimal : Animal
{
}

您将如何测试您的工厂?

与这个问题的代码,我不会。但得到的答复一直沿着更多的测试融入我的编码风格的路径帮助英寸我
阿伦Anodide

考虑到在动物类中加入了海洋世界和陆地世界,所有这些都使得在孤立的环境中处理起来更加困难。

Answers:


13

好吧,单独的工厂类的优点是可以在单元测试中模拟它。

但是,如果您不打算这样做,或者以任何其他方式使其变为多态,则可以在类本身上使用静态Factory方法。


谢谢,您能举一个例子说明您以其他方式说多态时的含义吗?
亚伦·奥诺德

我的意思是,如果您希望能够将一种工厂方法替换为另一种工厂方法。为了测试目的而对其进行模拟只是最常见的例子之一。
pdr 2012年

18

您可以使用泛型来避免使用switch语句,也可以使下游实现与基类脱钩:

 public static T CreateAnimal<T>() where T: new, Animal
 {
    return new T();
 }

用法:

LandAnimal rabbit = Animal.CreateAnimal();  //Type inference should should just figure out the T by the return indicated.

要么

 var rabbit = Animal.CreateAnimal<LandAnimal>(); 


2
@PeterK。Fowler在Code Smells中引用了switch语句,但是他的解决方案是应将它们提取到Factory类中,而不是被替换。就是说,如果您总是在开发时就知道类型,那么泛型是一个更好的解决方案。但是,如果您不这样做(如原始示例所示),那么我会辩称,使用反射来避免在工厂方法中进行切换可能是错误的经济做法。
pdr 2012年

switch语句和if-else-if链相同。我之所以使用前者,是因为编译时检查会强制处理默认情况
Aaron Anodide 2012年

4
@PeterK,在switch语句中,代码位于单个位置。在成熟的OO中,相同的代码可以分布在多个单独的类中。在灰色区域中,与开关语句相比,具有大量摘要的增加的复杂性不那么可取。

@Thorbjorn,我曾有过如此确切的想法,但从来没有这么简洁过,感谢您的讲话
Aaron Anodide 2012年

5

更极端的是,工厂也可能是通用的。

interface IFactory<K, T> where K : IComparable
{
    T Create(K key);
}

然后,可以创建任何类型的对象工厂,而工厂又可以创建任何类型的对象。我不确定这是否更简单,它肯定会更通用。

对于小型工厂实现,我没有发现switch语句有任何问题。一旦您进入大量的对象或对象的可能不同的类层次结构,我认为更通用的方法更适合。


1

馊主意。首先,它违反了开闭原则。对于任何新动物,您都必须再次弄乱您的基类,并且有可能破坏它。依赖关系会走错路。

如果您需要从配置中创建动物,那么这样的构造就可以了,尽管使用反射来获取与名称匹配的类型并使用所获得的类型信息实例化它是一个更好的选择。

但是无论如何,您都应该创建一个专用的工厂类,使其与动物类层次结构无关,并返回IAnimal接口而不是基本类型。然后它将变得有用,您将实现一些解耦。


0

这个问题对我来说是及时的-昨天我正在编写几乎完全相同的代码。只需用与项目中相关的任何内容替换“动物”,尽管为了此处的讨论,我将坚持使用“动物”。除了使用“ if”语句之外,我还使用了一系列更为复杂的“ if”语句,其中涉及的不仅仅是将一个变量与某些固定值进行比较。但这是一个细节。静态工厂方法似乎是一种精巧的设计方式,因为这种设计是通过重构早期的杂乱无章的代码而产生的。

我基于对派生类有所了解的基础类的观点而拒绝了这种设计。如果LandAnimal和SeaAnimal类很小,整洁且容易,则它们可以位于同一源文件中。但是我有很多凌乱的方法来读取不符合任何正式定义标准的文本文件-我希望LandAnimal类位于其自己的源文件中。

这导致循环文件依赖-LandAnimal源自Animal,但是Animal需要已经知道LandAnimal,SeaAnimal和另外15个类存在。我拉出了工厂方法,并将其放入自己的文件中(在我的主应用程序中,而不是在我的动物库中)拥有一个静态工厂方法看起来很聪明,但是我意识到它实际上并没有解决任何设计问题。

我不知道这与惯用的C#有什么关系,因为我经常切换语言,所以我通常会忽略我平常工作之外的语言和开发堆栈特有的习惯用法和约定。如果有意义的话,我的C#可能看起来像“ pythonic”。我的目标是大致清楚。

另外,我不知道在小的,简单的类不太可能在将来的工作中扩展的情况下,使用静态工厂方法是否有优势-在某些情况下将其全部包含在一个源文件中可能会很好。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.