在软件开发中,尤其是在面向对象编程和设计中,创建对象是一项常见的任务。而你管理这一过程的方式会直接影响应用程序的灵活性、可扩展性和可维护性。

创建型设计模式为如何系统化且可扩展地创建类和对象提供了指导。这些模式为对象的创建提供了蓝图,从而避免了代码重复编写,同时还能保持系统的一致性,使应用程序更易于扩展。

共有五种主要的创建型设计模式:

  1. 单例模式:确保一个类只有一个实例,并提供全局访问点来使用这个实例。

  2. 工厂方法模式:提供一个用于创建对象的接口,但让子类来决定具体创建哪个类的实例。

  3. 抽象工厂模式:在不指定具体类的情况下,创建一系列相关的对象。

  4. 建造者模式:允许你逐步构建复杂的对象,将对象的构造过程与表示形式分离开来。

  5. 原型模式:通过克隆现有的对象来创建新的对象,而不是从头开始重新编写代码。

根据应用程序的复杂程度和规模,这些模式中的每一种都能解决与对象创建相关的特定问题。

在本教程中,我将解释什么是创建型设计模式以及它们是如何工作的。我们会重点讨论两种主要的模式:工厂方法模式和抽象工厂模式。

很多人会把这两种模式混淆在一起,因此在这里我们将详细探讨:

  1. 每种模式的运作原理

  2. 在Flutter中的实际应用示例

  3. 应用场景、最佳实践及使用方法

通过学习本教程,你将明白何时应该使用工厂方法模式,何时应该改用抽象工厂模式,以及如何构建结构合理、具备可扩展性和可维护性的Flutter应用程序。

目录

  1. 工厂方法模式在Flutter中的运作原理

  2. 用于安全检查的工厂模式

  3. 抽象工厂模式在Flutter中的运作原理

  4. 总结

先决条件

在开始学习本教程之前,您应该具备以下条件:

  • 对Dart编程语言有基本的了解

  • 熟悉面向对象编程的概念(尤其是类、继承和抽象类)

  • 具备Flutter开发的基础知识(有帮助但非必需)

  • 理解接口与多态性

  • 有使用Dart创建类的经验

Factory模式在Flutter中的工作原理

当您需要管理可能与某种特定类型的对象相关的数据集时,通常会使用Factory模式。

举个例子,如果您需要为Android和iOS系统管理主题设置,那么使用Factory模式可以帮助您将对象的创建过程封装起来,从而使应用程序的结构更加模块化。我们将逐步演示这一过程,以便您能够了解该模式的具体工作原理。

步骤1:定义产品类与抽象创建者类

AppTheme {
  String? data;
  AppTheme({this.data});
}

abstract ApplicationThemeData  getApplicationTheme();
}

在这里,AppTheme是一个用于存储主题信息的数据类,它代表了我们的工厂将要创建的具体“产品”;而ApplicationThemeData则是一个抽象基类。这种抽象设计非常重要,因为它为所有具体的主题实现类定义了必须遵循的规范。

由于要求所有实现类都必须提供getApplicationTheme()方法,因此我们能够确保不同平台上的应用程序在主题数据处理方面保持一致性。

步骤2:实现具体的产品类

接下来,我们将为不同的平台编写具体的实现代码,以便提供实际的主题数据。

AndroidAppTheme ApplicationThemeData {
  @override
  Future getApplicationTheme() return AppTheme(data: "这是Android主题数据";
  }
}

IOSThemeData ApplicationThemeData {
  @override
  Future getApplicationTheme() return AppTheme(data: "这是IOS主题数据";
  }
}

AndroidAppThemeIOSThemeData这两个具体实现类继承自抽象类,并提供了针对特定平台的主题数据。它们各自返回一个AppTheme对象,其中包含适用于相应平台的内容。

步骤3:创建工厂类

工厂类封装了对象的创建逻辑,因此客户端代码无需知道自己正在使用的是哪个具体的主题类。

class ThemeFactory {
  ThemeFactory({required this.theme});
  ApplicationThemeData theme;

  loadTheme() async {
    return await theme.getApplicationTheme();
  }
}

ThemeFactory本身就充当了工厂的角色。它接受任何类型的ApplicationThemeData实现,并提供统一的loadTheme()方法,从而清晰地封装了对象创建的逻辑。

步骤4:使用工厂类

最后,在我们的应用程序代码中使用这个工厂类即可。

ThemeFactory(
  theme: Platform.isAndroid ? AndroidAppTheme() : IOSThemeData()
).loadTheme();

在这里,你可以选择适合自己平台的主题(Android或iOS),然后获取相应的AppTheme对象。当你只需要实现某个功能(比如加载主题)时,这种做法既简单又高效。

这种设计模式的优势在于:客户端代码结构清晰,而且如果以后需要支持新的平台,也无需对代码进行修改。

用于安全检查的工厂模式

在应用程序启动过程中进行安全检查时,工厂模式也是一个非常实用的解决方案。

例如,Android和iOS在内部安全机制方面需要采用不同的处理方式:Android会检测设备是否处于开发者模式或已被root权限激活,而iOS则会检查设备是否被越狱。这种场景正是应用工厂模式的理想例子——因为它能够让你以清晰、易于维护的方式封装针对不同平台的特定安全逻辑。让我们一步步来实现这个方案吧。

步骤1:定义安全检查结果类与抽象检测器

首先,我们需要一种标准化的方式来表示安全检查的结果,并制定相应的接口规范用于执行安全检查操作。

// 安全检查结果基类
class SecurityCheckResult {
  final bool isSecure;
  final String message;

  SecurityCheckResult({required this.isSecure, required this.message});
}

// 抽象安全检测器类
abstract class SecurityChecker {
  Future<SecurityCheckResult>> performSecurityCheck();
}

SecurityCheckResult类为跨平台传递安全检查结果提供了一种标准化的方式。

该类包含一个用于表示安全状态的布尔值,以及一条供用户阅读的描述性信息。抽象类SecurityChecker定义了所有特定于平台的安全部件实现都必须遵循的规范。

这样一来,无论使用的是哪个平台,我们都可以调用performSecurityCheck()方法,并且总能得到相同类型的结果。

步骤2:实现特定于平台的安全部件

现在,我们需要为每个平台分别编写具体的安全检查实现代码。

// Android平台特有的安全检查实现
class AndroidSecurityChecker extends SecurityChecker @override
  Future performSecurityCheck() bool isRooted = await checkIfDeviceIsRooted();
    return SecurityCheckResult(
        isSecure: "设备已越狱,应用程序无法在越狱设备上运行。"
      );
    }

    bool isDeveloperMode = await checkDeveloperMode();
    return SecurityCheckResult(
        isSecure: "开发者模式已启用。请关闭它以继续操作。"
      );
    }

    true,
      message: "设备的安全检查已通过。"
    );
  }

  Future checkIfDeviceIsRooted() return async {
    false; // 示例代码,实际实现可能不同
  }
}

// iOS平台特有的安全检查实现
class extends SecurityChecker @override
  Future performSecurityCheck() bool isJailbroken = if (isJailbroken) {
      false,
        message: "设备已越狱,应用程序无法在越狱设备上运行。"
      );
    }

    true,
      message: "设备的安全检查已通过。"
    );
  }

  Future checkIfDeviceIsJailbroken() return 步骤3:创建安全检查机制

这种安全检查机制会将所选用的安全检测工具封装起来,提供一个简洁的接口来执行检测操作。

// 安全检查机制
SecurityCheckFactory {
  SecurityCheckFactory({this.checker});
  SecurityChecker checker;

  Future runSecurityCheck() return 步骤4:在应用程序启动过程中使用安全检查机制

最后,我们需要将这种安全检查机制集成到应用程序的初始化流程中。

// 在你的应用程序启动阶段
Future initializeApp() final securityFactory = SecurityCheckFactory(
    checker: Platform.isAndroid 
      ? AndroidSecurityChecker() 
      : IOSSecurityChecker()
  );

  await securityFactory.runSecurityCheck();

  // 显示错误提示并阻止应用程序继续运行
    showSecurityErrorDialog(result.message);
    // 继续进行正常的应用程序初始化流程
  runApp(MyApp());
}

这个使用示例说明了如何通过工厂模式让应用程序的初始化代码变得更加简洁且易于维护。平台检测工作在一处完成,安全检查机制负责创建相应的检测工具,而你的代码只需处理标准化后的结果即可。
关键要点:当你需要某种类型的对象,但又希望将对象的创建逻辑抽象出来时,工厂模式是一个非常实用的选择。

抽象工厂模式在Flutter中的实现原理

当你需要比较两个以上的数据集,并且每个数据集都包含多种功能时,抽象工厂模式就派上用场了。

例如,假设你现在需要为Android、iOS和Linux系统管理主题、小部件以及应用程序架构。如果仅使用普通的工厂模式来处理这些任务,会显得非常繁琐;而抽象工厂模式则为处理不同平台上的相关对象提供了一种结构化的方式。

那么,让我们来看看如何利用抽象工厂模式来解决这个问题吧。

步骤1:定义抽象产品接口

在开始具体实现之前,首先需要了解什么是抽象产品接口。抽象产品接口本质上是一种契约,它规定了产品必须实现哪些方法,但并不指定这些方法的实现细节。

可以把这种接口看作是确保所有相关产品都具有相同结构的蓝图。在我们的例子中,我们需要定义三种每个平台都必须提供的核心功能:

  1. 主题管理

  2. 小部件处理

  3. 应用程序架构配置

通过首先创建这些抽象接口,我们可以为所有特定于平台的实现提供一个统一的规范。

abstract class ThemeManager {  
  Future getTheme();  
}

abstract class WidgetHandler {  
  Future getWidget();  
}

abstract class ArchitechtureHandler {  
  Future getArchitechture();  
}

在这里,我们定义了三个所有平台都必须实现的基本功能:主题管理、小部件处理以及应用程序架构配置。

每个接口都定义了一个方法,用于返回与特定平台相关的信息。

ThemeManager负责获取主题数据,WidgetHandler判断小部件的兼容性,而ArchitechtureHandler则提供应用程序架构的详细信息。

步骤2:实现特定于平台的产品

现在我们已经定义好了抽象接口,接下来就需要为每个平台创建具体的实现代码。这一步就是为每种类型的产品提供真正符合其平台特性的行为逻辑。可以把这个过程看作是用具体的细节来填充之前的蓝图。<虽然抽象接口告诉了我们需要哪些方法,但这些具体类则说明了这些方法在各个特定平台上的表现方式。每个平台(Android、iOS、Linux)都会对主题、小部件以及架构进行独特的实现。>

Android:

class AndroidThemeManager extends ThemeManager {
@override
Future>String> getTheme() return "Android Theme";
}
}

class AndroidWidgetHandler extends WidgetHandler {
@override
Future>bool> getWidget() return true;
}
}

class AndroidArchitechtureHandler extends ArchitectureHandler {
@override
Future>String> getArchitechture() return "Android Architecture";
}
}

对于Android,我们创建了三个特定的产品类。AndroidThemeManager用于返回Material Design主题相关数据,AndroidWidgetHandler会返回`true`以表示Android支持桌面小部件,而AndroidArchitechtureHandler则提供有关Android架构的信息(这些信息可能包括关于ARM、x86或其他处理器架构的详细内容)。

iOS:

class IOSThemeManager extends ThemeManager {
@override
Future>String> getTheme() return "IOS Theme";
}
}

class IOSWidgetHandler extends WidgetHandler {
@override
Future>bool> getWidget() return false;
}
}

class IOSArchitechtureHandler extends ArchitectureHandler {
@override
Future>String> getArchitechture() return "iOS Architecture";
}
}

iOS版本的实现遵循相同的结构,但会提供适用于iOS的特定值。需要注意的是,IOSWidgetHandler会返回false,这可能表示在iOS平台上,某些小部件的功能不可用,或者它们的行为与在Android平台上的表现不同。

Linux:

LinuxThemeManager ThemeManager {  
  @override  
  Future<String>> getTheme() return "Linux Theme";  
  }  
}

LinuxWidgetHandler WidgetHandler {  
  @override  
  Future<bool>> getWidget() return LinuxArchitechtureHandler ArchitechtureHandler {  
  @override  
  Future<String>> getArchitechture() return "Linux Architecture";  
  }  
}

同样地,Linux也有自己的一套实现方式,这些实现会提供适用于Linux的主题数据及架构信息。

步骤3:定义抽象工厂接口

既然我们的产品类已经准备好了,现在我们需要创建用于生成这些产品的工厂。

抽象工厂接口就是这个“蓝图”——它规定了我们的工厂必须能够创建哪些产品。这个接口本身并不负责创建任何东西,它只是声明所有的具体工厂都必须提供方法来创建这三种类型的产品(主题处理类、小部件处理类以及架构处理类)。这样一来,无论我们使用的是哪种平台对应的工厂,都能始终获得这三种功能。

AppFactory {  
  ThemeManager themeManager();  
  WidgetHandler widgetManager();  
  ArchitechtureHandler architechtureHandler();  
}

在这里,我们定义了一个工厂的“蓝图”。任何特定于某个平台的工厂都必须实现这三种功能。这样就能保证一致性:无论在哪个平台上,都能使用到这三种功能。

步骤4:实现特定平台的工厂类

此时,所有环节都汇聚到了一起。我们正在创建那些能够生成之前定义的特定平台产品的工厂类。每个工厂类负责为其所对应的平台生成所有相关产品。这种设计模式的关键优势在于封装性:每个工厂类都知道如何为该平台生成所有相关的对象,并能确保这些对象之间能够相互兼容。例如,AndroidFactory会生成专门适用于Android平台的主题管理器、小部件处理类以及架构处理类,而这些组件可以无缝协作。

class AndroidFactory extends AppFactory @override
  ThemeManager themeManager() => AndroidThemeManager();

  @override
  WidgetHandler widgetManager() => AndroidWidgetHandler();

  @override
  ArchitechtureHandler architechtureHandler() => AndroidArchitechtureHandler();
}

class IOSFactory extends AppFactory @override
  ThemeManager themeManager() => IOSThemeManager();

  @override
  WidgetHandler widgetManager() => IOSWidgetHandler();

  @override
  ArchitechtureHandler architechtureHandler() => IOSArchitechtureHandler();
}

class LinuxFactory extends AppFactory @override
  ThemeManager themeManager() => LinuxThemeManager();

  @override
  WidgetHandler widgetManager() =>> LinuxWidgetHandler();

  @override
  ArchitechtureHandler architechtureHandler() =>> LinuxArchitechtureHandler();
}

每个具体的工厂类(AndroidFactory、IOSFactory、LinuxFactory)都会实现AppFactory接口中的所有三个方法。当你调用AndroidFactorythemeManager()方法时,会得到一个AndroidThemeManager对象;而调用IOSFactory的相同方法时,则会得到一个IOSThemeManager对象。所有相关产品都是按照这种模式实现的。

步骤5:使用抽象工厂类的客户端代码

最后,我们需要编写使用这些抽象工厂类的客户端代码。这部分代码才是你的应用程序实际会与之交互的部分。这种设计模式的优点在于,客户端代码完全不需要了解具体平台的实现细节,它只需要遵守抽象工厂接口的规定即可正常工作。

AppBaseFactory类可以接受任何实现了AppFactory接口的工厂类,并提供了一种简便的方法来初始化所有与平台相关的设置。CheckDevice类则会根据当前运行的平台来决定应该使用哪个工厂类,从而将这一决策过程完全从应用程序的其他部分中分离出来。

class AppBaseFactory required this.factory;

  getAppSettings() {
    factory
      ..architechtureHandler()
      ..themeManager()
      ..widgetManager();
  }
}

class CheckDevice {
  static if (Platform.isAndroid) if (Platform.isIOS) if (Platform.isLinux) throw UnsupportedError("不支持的平台"// 使用方法
AppBaseFactory(factory: CheckDevice.get()).getAppSettings();

这段代码的具体工作原理如下:

AppBaseFactory类相当于任何AppFactory实现类的包装器。它提供了getAppSettings()方法,该方法利用Dart语言的链式调用机制来初始化架构处理器、主题管理器和部件管理系统这三个组件。

CheckDevice类负责检测当前运行的平台类型,并根据检测结果返回相应的工厂类。通过这种方式,所有的平台相关逻辑都被集中到了这个类中。当您调用AppBaseFactory(factory: CheckDevice.get()).getAppSettings()时,代码会自动识别当前平台,选择合适的工厂类进行初始化,而调用该方法的代码本身无需了解任何与特定平台相关的细节。

每个针对不同平台的工厂类都会生成与该平台相关的一切组件。客户端只需与AppBaseFactory进行交互,而无需了解其内部的实现机制。这样的设计使得代码具有更好的可扩展性、可维护性以及一致性。

实际应用案例:支付提供商管理

抽象工厂模式的另一个典型应用场景是,当您需要在应用程序中切换不同的支付提供商时,而同时又希望只向客户端(即表现层)暴露必要的功能接口时,这种模式就能发挥重要作用。

抽象工厂设计模式在具体实现、代码封装、编写简洁清晰的代码、职责分离以及合理的代码结构与管理方面,能够有效帮助你应对这类场景。例如,你的应用程序可以同时支持Stripe、PayPal和Flutterwave等支付方式。
不同的支付服务提供商需要不同的初始化流程、事务处理机制以及Webhook处理方式。通过使用抽象工厂模式,你可以为所有的支付操作创建一个统一的接口,同时将各服务提供商特有的细节封装在相应的工厂实现中。

结论

现在你应该能够更清楚地判断在什么情况下应该使用工厂设计模式,而在什么情况下应该使用抽象工厂设计模式了。
正确理解工厂模式和抽象工厂模式及其应用场景,将有助于你根据具体的需求来创建对象。
当你只需要创建一种产品,并且希望将相关的创建逻辑封装起来时,工厂模式是非常理想的选择;而当你需要在多个平台上处理多种相关产品,同时要求代码具有一致性并具备可扩展性时,抽象工厂模式则会更加适用。使用这些设计模式,能够帮助你编写出简洁、易于维护且具有可扩展性的Flutter应用程序。
它们为对象创建提供了一种系统化的方法,能够有效避免随着应用程序的发展而产生杂乱、难以维护的代码。

Comments are closed.