创建型模式——抽象工厂(Abstract Factory)
问题背景
当希望能够替换和扩展一族相关类的实现来给予程序定制性时,可以考虑使用抽象工厂。举一个常见的例子,界面换肤:用户可以自由选择Windows或Mac风格的界面,并且在以后的版本迭代中可能推出更多款式。在这种需求下,如果把客户端实现成这样就会造成很多麻烦:
这是典型的“非OO”做法,在OOP中,要实现对象行为的动态切换,应该使用多态,而不是将各种行为枚举然后硬编码到程序中。
这种做法会导致程序的弹性降低,后续需求变更时必须修改已有代码,这是大型软件开发中的大忌。如果只是修改私有成员还好,一旦修改了接口,就可能引入回归错误,严重拖慢工作进度。
解决方案
考虑到后续的版本迭代中可能出现更多风格的皮肤,我们从同类界面控件中提取出接口,把引用类型全部改为接口类型,并把控件的创建工作交给一个专门负责创建对象的类,改进后的程序结构是这样的:
效果
- 实现了抽象和实现的分离,降低了模块间的耦合度。
- 将创建工作封装到工厂中,简化了创建和切换产品族的操作。
- 同一时刻只能有一款皮肤运行,保证了程序的一致性。
缺陷
抽象工厂虽然实现了产品族的可扩展性,却没有实现产品项的可扩展性,即只能适应一个维度的变化。如果在以后的需求中,程序要增加单选框、复选框、下拉列表等控件,这种结构就又变得很麻烦了。
为了进一步提高程序的可扩展性,可以桥接整合到抽象工厂中,也可以使用反射工厂(后者是目前的主流做法)。
相关模式
- 工厂方法:抽象工厂可以通过工厂方法来实现。
- 原型:抽象工厂也可以通过传入对象原型来实现。
- 单例:一般情况下,只需要一个全局工厂,故抽象工厂常被设计成单例类。
实现
using System;
namespace AbstractFactory
{
public interface IForm
{
void Show();
}
public interface IButton
{
void Show();
}
public class WindowsForm : IForm
{
public void Show()
{
Console.WriteLine("我是WindowsForm");
}
}
public class MacForm : IForm
{
public void Show()
{
Console.WriteLine("我是MacForm");
}
}
public class WindowsButton : IButton
{
public void Show()
{
Console.WriteLine("我是WindowsButton");
}
}
public class MacButton : IButton
{
public void Show()
{
Console.WriteLine("我是MacButton");
}
}
public interface IControlFactory
{
IForm CreateForm();
IButton CreateButton();
}
public class WindowsControlFactory : IControlFactory
{
public IForm CreateForm()
{
return new WindowsForm();
}
public IButton CreateButton()
{
return new WindowsButton();
}
}
public class MacControlFactory : IControlFactory
{
public IForm CreateForm()
{
return new MacForm();
}
public IButton CreateButton()
{
return new MacButton();
}
}
public class Client
{
static IForm form;
static IButton button;
static void Show()
{
form.Show();
button.Show();
}
static void Main(string[] args)
{
Console.WriteLine("使用Windows皮肤...");
IControlFactory factory = new WindowsControlFactory();
form = factory.CreateForm();
button = factory.CreateButton();
Show();
Console.WriteLine("切换到Mac皮肤...");
factory = new MacControlFactory();
form = factory.CreateForm();
button = factory.CreateButton();
Show();
}
}
}