在平时编程中,构建对象最常用的方式是 new 一个对象。乍一看这种做法没什么不好,而实际上这也属于一种硬编码。每 new 一个对象,相当于调用者多知道了一个类,增加了类与类之间的联系,不利于程序的松耦合。其实构建过程可以被封装起来,工厂模式便是用于封装对象的设计模式。

1. 简单工厂模式

问题来了,我是调用者,我需要水果,new一个苹果,梨子,香蕉………我知道很多种水果名字,我就能new出这些吗?答案不一定,因为每个水果怎么创建出来的不一样。我要new出不同的水果,我不光要知道水果名字,我还要知道这个水果的创建方式(构造函数)。

这时候有个水果工厂,我只需要跟工厂说我要什么水果,给他一个水果名字,它就给我返回一个相应的水果。我不用知道这个水果是怎么创建的,工厂帮我做。

水果类

  1. class Fruit//水果父类
  2. {
  3. }
  4. class Apple: Fruit//苹果
  5. {
  6. }
  7. class Pear: Fruit //梨子
  8. {
  9. }
  10. class Banana: Fruit//香蕉
  11. {
  12. }
  13. class Strawberry: Fruit //草莓
  14. {
  15. }

简单的水果工厂

  1. class FruitsFactory//水果工厂
  2. {
  3. //给一个水果名,返回一个水果
  4. public Fruit Create(string fruitname)
  5. {
  6. switch (fruitname)
  7. {
  8. case "苹果":return new Apple();
  9. case "梨":return new Pear();
  10. case "香蕉":return new Banana();
  11. case "草莓":return new Strawberry();
  12. default:return null;
  13. }
  14. }
  15. }

调用工厂

  1. static void Main(string[] args)
  2. {
  3. FruitsFactory factory = new FruitsFactory();
  4. //有了工厂,现在我要一个苹果
  5. Fruit apple = factory.Create("苹果");
  6. //我要草莓
  7. Fruit strawberry = factory.Create("草莓");
  8. }

事实上,将构建过程封装的好处不仅可以降低耦合,如果某个产品构造方法相当复杂,使用工厂模式可以大大减少代码重复。比如,如果生产一个苹果需要苹果种子、阳光、水分,将工厂修改如下:

  1. class FruitsFactory//水果工厂
  2. {
  3. //给一个水果名,返回一个水果
  4. public Fruit Create(string fruitname)
  5. {
  6. switch (fruitname)
  7. {
  8. case "苹果":
  9. AppleSeed appleSeed = new AppleSeed();
  10. Sunlight sunlight = new Sunlight();
  11. Water water = new Water();
  12. return new Apple(appleSeed, sunlight, water);
  13. case "梨":return new Pear();
  14. case "香蕉":return new Banana();
  15. case "草莓":return new Strawberry();
  16. default:return null;
  17. }
  18. }
  19. }

调用者的代码则完全不需要变化,而且调用者不需要在每次需要苹果时,自己去构建苹果种子、阳光、水分以获得苹果。苹果的生产过程再复杂,也只是工厂的事。这就是封装的好处,假如某天科学家发明了让苹果更香甜的肥料,要加入苹果的生产过程中的话,也只需要在工厂中修改,调用者完全不用关心。

总结

总而言之,简单工厂模式就是让一个工厂类承担构建所有对象的职责。调用者需要什么产品,让工厂生产出来即可。
它的弊端也显而易见:

  • 一是如果需要生产的产品过多,此模式会导致工厂类过于庞大,承担过多的职责,变成超级类。当苹果生产过程需要修改时,要来修改此工厂。梨子生产过程需要修改时,也要来修改此工厂。也就是说这个类不止一个引起修改的原因。违背了单一职责原则。

  • 二是当要生产新的产品时,必须在工厂类中添加新的分支。而开闭原则告诉我们:类应该对修改封闭。我们希望在添加新功能时,只需增加新的类,而不是修改既有的类,所以这就违背了开闭原则。

    2. 工厂方法模式

    为了解决简单工厂模式的这两个弊端,工厂方法模式应运而生,它规定每个产品都有一个专属工厂。比如苹果有专属的苹果工厂,梨子有专属的梨子工厂
    不同的水果工厂 ```csharp class AppleFactory//苹果工厂 { public Fruit Create() {

    1. return new Apple();

    } }

class PearFactory//梨工厂 { public Fruit Create() { return new Pear(); } }

class BananaFactory//香蕉工厂 { public Fruit Create() { return new Banana(); } }

  1. 调用工厂
  2. ```csharp
  3. static void Main(string[] args)
  4. {
  5. //创建一个苹果工厂
  6. AppleFactory factory = new AppleFactory();
  7. //通过苹果工厂可以获得苹果
  8. Fruit apple = factory.Create();
  9. //需要梨子的时候,就创建梨子工厂
  10. PearFactory pearFactory = new PearFactory();
  11. Fruit pear = pearFactory.Create();
  12. }

这样和直接 new 出苹果和梨子有什么区别?上文说工厂是为了减少类与类之间的耦合,让调用者尽可能少的和其他类打交道。

用简单工厂模式,我们只需要知道 FruitFactory,无需知道 Apple 、Pear 类,很容易看出耦合度降低了。

但用工厂方法模式,调用者虽然不需要和 Apple 、Pear 类打交道了,但却需要和 AppleFactory、PearFactory 类打交道。有几种水果就需要知道几个工厂类,耦合度完全没有下降啊,甚至还增加了代码量!

工厂模式的第二个优点在工厂方法模式中还是存在的。当构建过程相当复杂时,工厂将构建过程封装起来,调用者可以很方便的直接使用,同样以苹果生产为例:

  1. class AppleFactory
  2. {
  3. public Fruit Create()
  4. {
  5. AppleSeed appleSeed = new AppleSeed();
  6. Sunlight sunlight = new Sunlight();
  7. Water water = new Water();
  8. return new Apple(appleSeed, sunlight, water);
  9. }
  10. }

总结

调用者无需知道苹果的生产细节,当生产过程需要修改时也无需更改调用端。同时,工厂方法模式解决了简单工厂模式的两个弊端。

  • 当生产的产品种类越来越多时,工厂类不会变成超级类。工厂类会越来越多,保持灵活。不会越来越大、变得臃肿。如果苹果的生产过程需要修改时,只需修改苹果工厂。梨子的生产过程需要修改时,只需修改梨子工厂。符合单一职责原则。

  • 当需要生产新的产品时,无需更改已有的工厂,只需要添加新的工厂即可。保持了面向对象的可扩展性,符合开闭原则。

    3. 抽象工厂模式

    工厂方法模式可以进一步优化,提取出工厂接口。然后不同的水果工厂都实现这个接口

    1. interface IFactory//工厂接口
    2. {
    3. Fruit Create();
    4. }
    5. class AppleFactory: IFactory
    6. {
    7. public Fruit Create()
    8. {
    9. return new Apple();
    10. }
    11. }
    12. class PearFactory: IFactory
    13. {
    14. public Fruit Create()
    15. {
    16. return new Pear();
    17. }
    18. }
    19. class BananaFactory: IFactory
    20. {
    21. public Fruit Create()
    22. {
    23. return new Banana();
    24. }
    25. }

    可以看到,我们在创建时指定了具体的工厂类后,在使用时就无需再关心是哪个工厂类,只需要将此工厂当作抽象的 IFactory 接口使用即可。这种经过抽象的工厂方法模式被称作抽象工厂模式

    1. static void Main(string[] args)
    2. {
    3. //需要苹果时,就创建苹果工厂
    4. IFactory factory = new AppleFactory();
    5. //通过苹果工厂可以获得苹果
    6. Fruit fruit = factory.Create();
    7. }

    如果要替换成梨子,只需要修改一行代码

    1. static void Main(string[] args)
    2. {
    3. IFactory factory = new PearFactory();
    4. Fruit fruit = factory.Create();
    5. }

    总结

    抽象工厂模式很好的发挥了开闭原则、依赖倒置原则,但缺点是抽象工厂模式太重了,如果 IFactory 接口需要新增功能,则会影响到所有的具体工厂类。使用抽象工厂模式,替换具体工厂时只需更改一行代码,但要新增抽象方法则需要修改所有的具体工厂类。所以抽象工厂模式适用于增加同类工厂这样的横向扩展需求,不适合新增功能这样的纵向扩展。