C++抽象工厂模式详解(附带实例)
假设正在开发一个需要在 Windows、macOS 和 Linux 上运行的图形编辑软件。每个操作系统的用户界面风格和交互设计存在显著差异,例如按钮、文本框和滚动条在不同系统中的视觉效果和行为都不同。如果为每个平台以硬编码方式实现各个组件,不仅会导致代码冗余、难以维护,还会降低未来对新平台支持的扩展性。
让我们思考以下问题:
要设计一个能够在不同操作系统中保持界面一致性,同时尊重并适应每个系统原生外观和用户体验的系统,抽象工厂模式提供了一个极佳的解决方案。我们通过这个设计模式来回答几个关键的疑问,以展示它如何应对跨平台 UI 组件库的挑战。
1) 设计跨平台一致性系统的方法是什么?
抽象工厂模式用于在不指定具体类的情况下创建一系列相关或依赖对象的接口。
在跨平台 UI 组件库的情景中,这意味着我们可以设计一个抽象的 UIComponentFactory 接口,它声明了创建基本 UI 组件(如按钮、文本框和滚动条)的方法。然后,为每个操作系统实现具体的工厂类(如 WindowsUIComponentFactory、MacOSUIComponentFactory),这些具体工厂类负责产生符合各自操作系统风格的组件实例。
2) 直接在应用代码中创建具体的组件对象有哪些问题?
如果在应用代码中直接使用如 new WindowsButton() 的方式来创建具体组件,将会面临以下几个问题:
3) 如何通过设计模式简化对新操作系统的支持?
通过实施抽象工厂模式,我们只需增加新的具体工厂实现即可支持新的操作系统。应用代码不需要改变,因为它仅依赖于抽象工厂接口。这种方法大大简化了适配新操作系统的工作,同时保持了代码的清洁和易于管理。
此外,这种设计使得单个应用能够在运行时动态适应用户的操作系统,无须重新编译或重写大量代码。
解答完以上疑问后,我们可以总结出抽象工厂模式在应对跨平台UI设计和支持新操作系统时所具有的主要优势,如下表所示。
接下来,为每个平台实现这些接口:
为每个操作系统提供具体的工厂实现:

图 1 抽象工厂模式
抽象工厂模式中的角色行为有以下几点:
这两种模式的本质区别在于:
理解这两种模式的设计理念以及它们各自的应用场景,可以帮助开发者在实际开发中做出更合适的架构选择。
让我们思考以下问题:
- 如何设计一套系统,使它能够在不同操作系统中保持界面的一致性,同时又能尊重并适应每个系统的原生外观和用户体验?
- 如果直接在应用代码中创建具体的组件对象(如 new WindowsButton()),会带来哪些问题?这种做法的可扩展性、可维护性和灵活性如何?
- 如何通过设计模式来解决这些问题,同时使得新增对另一个操作系统的支持变得简单?
要设计一个能够在不同操作系统中保持界面一致性,同时尊重并适应每个系统原生外观和用户体验的系统,抽象工厂模式提供了一个极佳的解决方案。我们通过这个设计模式来回答几个关键的疑问,以展示它如何应对跨平台 UI 组件库的挑战。
1) 设计跨平台一致性系统的方法是什么?
抽象工厂模式用于在不指定具体类的情况下创建一系列相关或依赖对象的接口。
在跨平台 UI 组件库的情景中,这意味着我们可以设计一个抽象的 UIComponentFactory 接口,它声明了创建基本 UI 组件(如按钮、文本框和滚动条)的方法。然后,为每个操作系统实现具体的工厂类(如 WindowsUIComponentFactory、MacOSUIComponentFactory),这些具体工厂类负责产生符合各自操作系统风格的组件实例。
2) 直接在应用代码中创建具体的组件对象有哪些问题?
如果在应用代码中直接使用如 new WindowsButton() 的方式来创建具体组件,将会面临以下几个问题:
- 可扩展性低:新增对另一个操作系统的支持时,必须遍历整个代码库,为新平台添加特定的组件创建代码,这使得维护成本极高。
- 可维护性差:硬编码具体组件类使得任何对UI组件的修改都需要修改使用它们的代码,违反了开闭原则(对扩展开放,对修改关闭)。
- 灵活性低:难以适应变化的需求,例如不能在不同的操作系统版本之间切换或者支持新的用户界面风格。
3) 如何通过设计模式简化对新操作系统的支持?
通过实施抽象工厂模式,我们只需增加新的具体工厂实现即可支持新的操作系统。应用代码不需要改变,因为它仅依赖于抽象工厂接口。这种方法大大简化了适配新操作系统的工作,同时保持了代码的清洁和易于管理。
此外,这种设计使得单个应用能够在运行时动态适应用户的操作系统,无须重新编译或重写大量代码。
解答完以上疑问后,我们可以总结出抽象工厂模式在应对跨平台UI设计和支持新操作系统时所具有的主要优势,如下表所示。
优势 | 说明 |
---|---|
系统的独立性 | 抽象工厂模式允许系统配置为多个不同的产品族,无须在代码中绑定具体类,增加了系统的灵活性 |
系列产品族的支持 | 当产品有多个变体时,确保客户端仅使用同一变体集合,如操作系统间的视觉风格或操作控件的一致性 |
促进一致性 | 确保所有客户端使用的产品来自同一个产品族,这对于需要强调一致性的设计尤为重要 |
替代直接构造调用 | 允许通过具体的工厂类间接创建对象,这样可以引入新的产品变体或更换产品组合,而不需改变客户端代码 |
封装多个具体工厂 | 提供封装具体工厂的接口,使得客户端编程时只需面对接口,而不是具体的工厂实现,便于切换不同工厂逻辑 |
抽象工厂模式的设计实例
基于抽象工厂模式,我们可以设计一个简单的跨平台的UI组件库。1) 抽象产品和具体产品
首先,定义抽象产品,即UI组件的接口。这些接口包括 Button 和 TextBox:class Button { public: virtual void paint() = 0; virtual ~Button() {} }; class TextBox { public: virtual void render() = 0; virtual ~TextBox() {} };
接下来,为每个平台实现这些接口:
// Windows特定实现 class WindowsButton : public Button { public: void paint() override { std::cout << "Rendering a button in Windows style\n"; } }; class WindowsTextBox : public TextBox { public: void render() override { std::cout << "Rendering a text box in Windows style\n"; } }; // macOS特定实现 class MacOSButton : public Button { public: void paint() override { std::cout << "Rendering a button in MacOS style\n"; } }; class MacOSTextBox : public TextBox { public: void render() override { std::cout << "Rendering a text box in MacOS style\n"; } };
2) 抽象工厂和具体工厂
定义一个抽象工厂接口,它声明了创建各种 UI 组件的方法:class GUIFactory { public: virtual Button* createButton() = 0; virtual TextBox* createTextBox() = 0; virtual ~GUIFactory() {} };
为每个操作系统提供具体的工厂实现:
class WindowsFactory : public GUIFactory { public: Button* createButton() override { return new WindowsButton(); } TextBox* createTextBox() override { return new WindowsTextBox(); } }; class MacOSFactory : public GUIFactory { public: Button* createButton() override { return new MacOSButton(); } TextBox* createTextBox() override { return new MacOSTextBox(); } };
3)使用工厂
在应用中,根据当前操作系统环境选择使用哪个工厂:GUIFactory* factory; if (runningOnWindows()) { factory = new WindowsFactory(); } else if (runningOnMacOS()) { factory = new MacOSFactory(); } Button* button = factory->createButton(); button->paint(); TextBox* textBox = factory->createTextBox(); textBox->render(); delete button; delete textBox; delete factory;
抽象工厂模式中的角色
抽象工厂模式如下图所示:
图 1 抽象工厂模式
抽象工厂模式中的角色行为有以下几点:
- 客户请求产品:客户开始请求一系列相关的产品,但他们不直接创建产品实例;
- 抽象工厂:这个分区表示抽象工厂接口,它定义创建产品的方法,但不实现这些方法;
- 具体工厂 A和具体工厂 B:这些分区展示了具体的工厂,它们通过实现抽象工厂接口中的方法来创建具体的产品。具体工厂决定了应该创建哪个产品族;
- 返回产品:具体工厂创建完产品后,将它们返回给客户端;
- 使用产品:客户端收到产品实例后使用它们,客户端与具体产品的创建过程完全解耦,只依赖于产品接口。
工厂模式之间的差异
在了解了抽象工厂模式的概念后,我们再回顾一下工厂方法模式,虽然两者看起来有些相似——都属于创建型设计模式,用于帮助我们更优雅地创建对象,但它们各自最适用的场景和实现方式有显著的不同。1) 工厂方法模式
想象你在一个餐厅点餐,每道菜都是由专门的厨师负责做的。如果想要一份比萨,就去找做比萨的厨师;如果想要一份汤,就去找做汤的厨师。这里,每个厨师都有他们的专长,这就像工厂方法模式,你有一个创建对象的方法,这个方法会根据情况调用不同的构造器或工厂方法来创建特定类型的对象。2) 抽象工厂模式
想象有一家提供不同国家美食(比如意大利餐区、墨西哥餐区等)的大型自助餐厅,每个区域都能提供一套完整的菜单,包括前菜、主菜、甜点等。在这里,你不是找单独的厨师,而是选择一个区域,这个区域的厨师团队会为你准备所有类型的菜。这就类似于抽象工厂模式,它不仅仅创建一个产品,而是创建一系列相关或依赖的产品族。这两种模式的本质区别在于:
- 工厂方法专注于一个产品的构建,并允许子类决定实现逻辑,适用于一个产品有多个变体的情况;
- 抽象工厂则关注生产一系列产品,这些产品通常是相关的,需要一起使用,使得客户端可以不依赖于具体的产品实现,适用于产品组内部构成复杂或产品类别多样的情况。
理解这两种模式的设计理念以及它们各自的应用场景,可以帮助开发者在实际开发中做出更合适的架构选择。