开发人员的最终目标是开发出满足客户需求的应用。代码必须易于扩展、维护,并且足够稳定以满足未来的需求。第一次尝试就写出程序的最佳版本是很困难的。您可能需要分析代码并多次重构它。在本章中,您将看到这样一个场景,并学习如何使用工厂。为了使讨论简单,我从一个小程序开始。我们将继续分析该程序,并相应地进行修改。
为了制作一个稳定的应用,专业程序员的目标是使它松散耦合。他试图找出将来可能会发生变化的代码。一旦完成,他就将这部分代码从剩余的代码库中分离出来。工厂在这种情况下是最好的。
POINTS TO REMEMBER
显而易见的问题是:什么是工厂?简而言之,这是一段处理对象创建过程细节的代码。任何使用工厂的方法都被称为工厂的客户端。你应该注意到一个工厂可以有很多客户。这些客户端可以使用工厂来获取一个对象,然后,根据他们的需要,他们可以调用该对象的方法。因此,如何使用他从工厂收到的对象取决于客户。这就是为什么分离对象创建过程是有益的。否则,您将会在多个客户端中出现重复的代码。
假设你写了一个程序来演示两种不同动物的行为,比如老虎和猫。但是你有一个约束,说你不应该在你的 Main()
*方法中实例化动物对象。*你为什么会看到这种约束?以下是一些原因:
-
您希望对客户端隐藏实例化逻辑。你知道“变化”是编程世界中唯一不变的东西。让我们看看如果对象的实例化逻辑驻留在客户端会发生什么。当您增强应用以支持新类型的对象时,您也需要更新客户端代码。它还要求重新测试客户端代码。如何将实例化逻辑从客户端代码中分离出来?您将在接下来的演示中看到这一点。
-
可能有单独的类,它们的方法也可以创建猫或老虎。所以,最好将实例化一只猫或一只老虎的代码分离出来。在这种情况下,有多少客户端使用该代码并不重要。每个客户端都可以引用公共位置来实例化一个对象。
我希望你明白这个要求。让我们假设您编写了下面的程序,如演示 1 所示。在您完成整个程序之前,请注意以下几点:
-
在这个程序中,
AnimalFactory
类负责实例化一个对象。它包含一个名为CreateAnimal()
的方法来创建一个 tiger 或 cat 实例。因此,AnimalFactory
像一个工厂类,而CreateAnimal()
像一个工厂方法。 -
CreateAnimal()
方法是非静态的,尽管您可以使它成为静态的。我将在本书的最后一章(第十一章)讨论使用静态方法的利弊。 -
在客户端代码中,实例化这个工厂类来获得一个动物。这就是为什么在
Main()
方法中,您会看到下面的代码:AnimalFactory animalFactory = new AnimalFactory(); IAnimal animal = animalFactory.CreateAnimal("cat"); animal.DisplayBehavior();
以下是完整的程序:
using System;
namespace UsingSimpleFactory
{
interface IAnimal
{
void DisplayBehavior();
}
class Tiger : IAnimal
{
public Tiger()
{
Console.WriteLine("\nA tiger is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It roars.");
Console.WriteLine("It loves to roam in a jungle.");
}
}
class Cat : IAnimal
{
public Cat()
{
Console.WriteLine("\nA cat is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It meows.");
Console.WriteLine("It loves to stay at a home.");
}
}
class AnimalFactory
{
public IAnimal CreateAnimal(string animalType)
{
IAnimal animal;
if (animalType.Equals("cat"))
{
animal = new Cat();
}
else if (animalType.Equals("tiger"))
{
animal = new Tiger();
}
else
{
Console.WriteLine("You can create either a cat or a tiger. ");
throw new ApplicationException("Unknown animal cannot be instantiated.");
}
return animal;
}
}
class Program
{
static void Main()
{
Console.WriteLine("***Creating animals and learning about them.***");
AnimalFactory animalFactory = new AnimalFactory();
IAnimal animal = animalFactory.CreateAnimal("cat");
animal.DisplayBehavior();
animal = animalFactory.CreateAnimal("tiger");
animal.DisplayBehavior();
Console.ReadKey();
}
}
}
以下是输出:
***Creating animals and learning about them.***
A cat is created.
It meows.
It loves to stay at a home.
A tiger is created.
It roars.
It loves to roam in a jungle.
演示 1 中使用的方法在编程中很常见。它有一个名字: 简单工厂模式 。现在让我们来分析这个程序。
如果将来需要创建不同类型的动物,比如说猴子,您可能需要增强这个应用。你会怎么做?您需要修改AnimalFactory
类并扩展if-else
链来考虑猴子。但是如果你这样做,你将违反 OCP,结果,你将需要重新测试AnimalFactory
类。
POINTS TO REMEMBER
当您在类似的例子中看到switch
语句或if-else
链来创建不同类型的对象时,您会得到提示,您可能需要重新打开代码以适应未来的更改。在最坏的情况下,这些代码会在应用的几个部分重复出现。因此,您不断违反 OCP,这可能会在将来导致严重的维护问题。
在这个程序中,Visual Studio 向您显示一条消息,内容如下: CA1822:成员“CreateAnimal”不访问实例数据,可以标记为 static 。还口口声声说:不访问实例数据或调用实例方法的活动成员可以标记为静态。将方法标记为静态后,编译器将向这些成员发出非虚拟调用点。这可以为对性能敏感的代码带来可观的性能提升。我不建议现在采取这种方法。静态方法允许您在不实例化对象的情况下调用方法。但是静态方法也有缺点。例如,您不能在运行时更改静态方法的行为。如前所述,你会在第 11 章中看到关于这个的详细讨论。
你明白通过遵循 OCP 原则,你可以使程序变得更好。因此,在接下来的演示中,您将看到一个新的层次结构。
#region Factory hierarchy
abstract class AnimalFactory
{
public abstract IAnimal CreateAnimal();
}
class CatFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Cat();
}
}
class TigerFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Tiger();
}
}
#endregion
为什么这很有帮助?我以这样一种方式使用这个结构,即整个代码段都是封闭的,以便进行修改。将来,如果您需要支持一种新的动物类型,比如说猴子,您将需要执行以下操作:
-
创建一个将实现
IAnimal
接口的Monkey
类。 -
创建一个
MonkeyFactory
,它将实现AnimalFactory
并为CreateAnimal()
方法提供实现。
因此,只测试新的类就足够了。 您现有的代码未被改动,关闭修改 。注意,在这个程序中有两个独立的继承层次:一个是动物层次,另一个是工厂层次。我在这段代码中标记了它们,供您参考。为了更清楚起见,我还包括了下面的类图(见图 6-1 )。
图 6-1
在演示 2 中,类图显示了两个不同的层次结构
下面是完整的演示:
using System;
namespace SimpleFactoryModified
{
#region Animal hierarchy
interface IAnimal
{
void DisplayBehavior();
}
class Tiger : IAnimal
{
public Tiger()
{
Console.WriteLine("\nA tiger is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It roars.");
Console.WriteLine("It loves to roam in a jungle.");
}
}
class Cat : IAnimal
{
public Cat()
{
Console.WriteLine("\nA cat is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It meows.");
Console.WriteLine("It loves to stay at a home.");
}
}
#endregion
#region Factory hierarchy
abstract class AnimalFactory
{
public abstract IAnimal CreateAnimal();
}
class CatFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Cat();
}
}
class TigerFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Tiger();
}
}
#endregion
// Client
class Program
{
static void Main()
{
Console.WriteLine("***Modifying the simple factory in the demonstration 1.***");
// The CatFactory creates cats
AnimalFactory animalFactory = new CatFactory();
IAnimal animal = animalFactory.CreateAnimal();
animal.DisplayBehavior();
// The TigerFactory creates tigers
animalFactory = new TigerFactory();
animal = animalFactory.CreateAnimal();
animal.DisplayBehavior();
Console.ReadKey();
}
}
}
除了第一行,该输出与前面的输出相同。
***Modifying the simple factory in the demonstration 1.***
A cat is created.
It meows.
It loves to stay at a home.
A tiger is created.
It roars.
It loves to roam in a jungle.
您可以用以下几点来总结这个修改后的实现:
-
在
Main()
里面,你决定使用哪个动物工厂——是CatFactory
还是TigerFactory
? -
AnimalFactory
的子类创建一个Cat
实例或一个Tiger
实例。 -
这样,你就支持了 OCP 的概念。因此,您可以得到一个更好、更具可扩展性的解决方案。
在第 4 章中,我说过:完全实现这个原则并不总是容易的,但是即使部分遵守 OCP 协议也是有益的。对于每一项要求来说,实现 OCP 并不容易。一个新的需求可能会要求应用进行许多更改。在这种情况下,根据情况,你必须选择一种技术。
例如,让我们假设您有一个额外的需求:您希望允许客户为动物选择一种颜色。你应该如何进行?一种选择是在构造函数中传递color
属性,并相应地更新程序。
下面是一个满足要求的示例演示。我用粗体字做了重要的修改。
using System;
namespace UsingFactoryMethod
{
#region Animal hierarchy
interface IAnimal
{
void DisplayBehavior();
}
class Tiger : IAnimal
{
public Tiger(string color)
{
Console.WriteLine($"\nA {color} tiger is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It roars.");
Console.WriteLine("It loves to roam in a jungle.");
}
}
class Cat : IAnimal
{
public Cat(string color)
{
Console.WriteLine($"\nA {color} cat is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It meows.");
Console.WriteLine("It loves to stay at a home.");
}
}
#endregion
#region Factory hierarchy
abstract class AnimalFactory
{
public abstract IAnimal CreateAnimal(string color);
}
class CatFactory : AnimalFactory
{
public override IAnimal CreateAnimal(string color)
{
return new Cat(color);
}
}
class TigerFactory : AnimalFactory
{
public override IAnimal CreateAnimal(string color)
{
return new Tiger(color);
}
}
#endregion
// Client
class Program
{
static void Main()
{
Console.WriteLine("***Modifying demonstration 2 now.***");
// The CatFactory creates cats
AnimalFactory animalFactory = new CatFactory();
IAnimal animal = animalFactory.CreateAnimal("black");
animal.DisplayBehavior();
// The TigerFactory creates tigers
animalFactory = new TigerFactory();
animal = animalFactory.CreateAnimal("white");
animal.DisplayBehavior();
Console.ReadKey();
}
}
}
该程序产生以下输出:
***Modifying demonstration 2 now.***
A black cat is created.
It meows.
It loves to stay at a home.
A white tiger is created.
It roars.
It loves to roam in a jungle.
你可以看到许多变化是必需的。有没有替代的方法?我也这么认为演示 4 就是为此目的而做的。
由于AnimalFactory
是一个抽象类,您可以修改这个类来适应这种变化。在这个替代演示中,我引入了一个新方法MakeAnimal()
,它在调用CreateAnimal()
方法创建动物实例之前接受color
属性。下面是代码:
abstract class AnimalFactory
{
public IAnimal MakeAnimal(string color)
{
Console.WriteLine($"\nThe following animal color is {color}.");
IAnimal animal= CreateAnimal();
return animal;
}
public abstract IAnimal CreateAnimal();
}
在客户端代码中,调用MakeAnimal()
方法而不是CreateAnimal()
来查看动物的颜色。
下面是修改后的例子。
using System;
namespace FactoryMethodDemo2
{
#region Animal hierarchy
interface IAnimal
{
void DisplayBehavior();
}
class Tiger : IAnimal
{
public Tiger()
{
Console.WriteLine("\nA tiger is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It roars.");
Console.WriteLine("It loves to roam in a jungle.");
}
}
class Cat : IAnimal
{
public Cat()
{
Console.WriteLine("\nA cat is created.");
}
public void DisplayBehavior()
{
Console.WriteLine("It meows.");
Console.WriteLine("It loves to stay at a home.");
}
}
#endregion
#region Factory hierarchy
abstract class AnimalFactory
{
public IAnimal MakeAnimal(string color)
{
Console.WriteLine($"\nThe following animal color is {color}.");
IAnimal animal= CreateAnimal();
return animal;
}
public abstract IAnimal CreateAnimal();
}
class CatFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Cat();
}
}
class TigerFactory : AnimalFactory
{
public override IAnimal CreateAnimal()
{
return new Tiger();
}
}
#endregion
// Client
class Program
{
static void Main()
{
Console.WriteLine("***Modifying demonstration 2 now.***");
// The CatFactory creates cats
AnimalFactory animalFactory = new CatFactory();
IAnimal animal = animalFactory.MakeAnimal("black");
animal.DisplayBehavior();
// The TigerFactory creates tigers
animalFactory = new TigerFactory();
animal = animalFactory.MakeAnimal("white");
animal.DisplayBehavior();
Console.ReadKey();
}
}
}
以下是输出:
***Modifying demonstration 2 now.***
The following animal color is black.
A cat is created.
It meows.
It loves to stay at a home.
The following animal color is white.
A tiger is created.
It roars.
It loves to roam in a jungle.
在这一章的开始,我们看到了使用工厂的优势。从演示 2 开始,我们为工厂使用了一个新的层次结构,这样所有的具体工厂都继承自AnimalFactory
,我们将对象创建的细节传递给具体工厂(CatFactory
或TigerFactory
)。既然我们遵循 OCP 原则,我们可以添加一个新的混凝土工厂,比如说MonkeyFactory
,来创造猴子。如果我们实现了这个场景,我们就不需要重新打开现有的代码。相反,新的MonkeyFactory
类可以从AnimalFactory,
继承,并且遵循规则,它将创造猴子。在这种情况下,我们需要以创建Tiger
类或Cat
类的相同方式创建一个Monkey
类。请注意,我们永远不需要重新打开现有的代码。
我创建了演示 3 和 4 来支持一个新的需求。在当前结构中维护 OCP 以适应新的需求是非常困难的,因为在开始时没有考虑颜色属性。演示 4 向您展示了您仍然可以进行最少的更改。
工厂为对象创建提供了另一种方法。本章从一个简单的工厂类开始。它帮助您将可能与代码的其他部分不同的代码分离出来。您将实例化逻辑放在工厂类中,以便为对象创建提供统一的方式。
遵循 OCP 原则,您进一步修改了应用。您为工厂创建了另一个层次结构,并将实际的对象创建逻辑传递给了具体的工厂。
稍后,在演示 4 中,您看到了在抽象工厂类中,您可以设置一个所有派生的具体工厂都必须遵守的通用规则。这个过程可以帮助您以最小的变化适应特定的需求。