生成器(构建器)模式-原理到实战应用(Dart版)

简介: 生成器(构建器)模式-原理到实战应用(Dart版)

面向对象之设计模式生成器(构建器)模式-原理到实战应用(Dart版)


1. 引例

前两篇博文中,我们分别使用了两个与游戏相关的例子取构建游戏角色和带有装备的游戏角色。现在我们仍然使用一个例子来讲解生成器模式。假设我们正在开发一个餐厅 点餐系统,需要创建菜品对象。菜品对象具有多个 可选 属性,例如名称、描述、价格、热量等。当我们需要创建多个不同的菜品时,需要如何实现呢?

经过思考,我们首先想到了两种方法。一种方法是为多个菜品写一个庞大的菜品基类,然后基于这个基类去生产多个菜品子类,这种方法下你不得不去面对数量众多的菜品子类,可选参数越多时,结构越复杂。另一种方法是将所有不同类的参数都放在菜品类的构造函数中,或者实现一个庞大的单一构造函数,或者使用构造函数的重载来处理不同参数,最终构造出不同的菜品。显然第二种方法有可能导致构造函数相对复杂,尤其是当参数增加,有新可选参数的菜品时,都将导致代码的大量更改。

当菜品越来越复杂时,那么有什么办法规避这种复杂局面的出现呢?这时我们可以将菜品的构造过程划分为一组构建步骤,通过一个包含这一系列创建步骤的新对象去构建这个菜品对象。由于不同菜品对象用到了不同的可选参数,因此实际上当我们具体创建某一个菜品时只会用到有限的步骤。

通过这个例子,我们实际上讨论了一个构建复杂对象的方法,通过拆分将一个复杂的构建过程分解为若干个步骤,通过组合不同的步骤生成一个具体的对象。这个有一系列步骤方组成的专门用于生成一个对象的对象称之为生成器,相应的通过这种方式来创建对象的编程模式称之为生成器模式。下一节我们将具体分析该模式中的各个组成部分及其功能原理。

2. 什么是生成器模式

2.1 生成器模式原理

生成器模式 又称 建造者模式(Builder Pattern),它允许我们使用多个简单的对象逐步构建一个复杂的对象。该模式通过将对象的 构建 过程与其 表示 分离,可以灵活地创建不同类型的对象。其结构图如 图 1 所示:

builder

Builder

+buildPartA()

+buildPartB()

+getResult()

ConcreteBuilder

+buildPartA()

+buildPartB()

+getResult()

Director

-builder: Builder

+construct()

+setBuilder(builder: Builder)

Product

-partA

-partB

+setPartA(partA)

+setPartB(partB)

图 1 生成器模式结构图

图中包括实现生成器模式的四种角色,其中 构建者(Builder)和 具体构建者(ConcreteBuilder)负责构建产品的各个部分,产品(Product)表示正在构建的复杂对象,指导者(Director)协调构建者的工作并控制构建过程。它们协同工作来实现对象的构建和表示分离,以及灵活创建复杂对象的目标。各个角色在该模式中的功能的详细描述如表1所示:

角色 描述
构建者(Builder) 定义了创建对象各个部分的抽象接口,包括构建不同部分的方法和获取最终产品的方法。
具体构建者(ConcreteBuilder) 实现了构建者接口,负责实际构建产品的各个部分,并返回最终的产品对象。
产品(Product) 表示正在构建的复杂对象,通常具有多个部分。具体构建者负责构建这些部分并定义产品的组装方式。
指导者(Director) 负责使用构建者对象,按照一定的步骤来创建复杂对象。指导者可以隐藏产品的具体创建过程,简化客户端代码。

表 1 生成器模式中的角色描述

2.2 生成器模式的使用场景

要讨论生成器模式的使用场景还需要先了解使用该模式时我们能够获得的相关优势。归纳起来生成器模式的优点主要包括:

  1. 它分离了复杂对象的构建过程和其表示,使得构建过程更加灵活,易于扩展和修改。
  2. 可以控制对象的构建过程,使得构建过程可重复使用,并且可以逐步构建对象,方便构建复杂对象。
  3. 隐藏了产品的内部结构,使得产品的创建过程对客户端透明。

生成器模式适用于以下场景:

  1. 当需要创建的对象包含复杂的部分和创建步骤时,可以使用生成器模式来简化对象的构建过程。
  2. 当对象的构建过程需要根据不同的参数或配置选项而有所不同时,可以使用生成器模式来灵活地构建不同类型的对象。
  3. 当希望通过一系列步骤来创建对象,并希望将对象的创建过程与其表示分离时,生成器模式是一个有用的选择。
  4. 当需要在构建过程中对产品进行精细控制,或者需要在构建过程中进行特定操作或检查时,可以使用生成器模式。

3. 用 Dart 语言编写生成器模式代码

现在回到本文开头的例子上来,我们最终需要构建的目标是不同的 菜品(Dish),它也就相当于生成器模式中的产品(Product)。产品是由 指导者(Director)使用具体构建者(ConcreteBuilder)按照一定的步骤来创建复杂对象。而(抽象)构建者(Builder)中的抽象方法实际上是对构造过程做了具体的步骤划分。比如 buildName() 方法用于构造菜品的名字, buildDescription 方法用于构造菜品的描述,buildPrice 方法用于构造菜品的价格,等等。整体上,这个例子的结构图如下:

builder

Builder

+buildName(name: String)

+buildDescription(description: String)

+buildPrice(price: double)

+buildCalories(calories: int)

+getResult()

ConcreteBuilder

-dish: Dish

+buildName(name: String)

+buildDescription(description: String)

+buildPrice(price: double)

+buildCalories(calories: int)

+getResult()

Director

-builder: Builder

+setBuilder(builder: Builder)

+construct(name: String, description: String, price: double, calories: int)

Dish

-name: String

-description: String

-price: double

-calories: int

+display()

// Dish: 菜品类
class Dish {
  String name; // 名称
  String description; // 描述
  double price; // 价格
  int calories; // 热量
  Dish(this.name, this.description, this.price, this.calories);
  void display() {
    print(
        'Dish: $name, Description: $description, Price: $price, Calories: $calories');
  }
}
// Builder: 抽象建造者
abstract class Builder {
  void buildName(String name); // 构建名称
  void buildDescription(String description); // 构建描述
  void buildPrice(double price); // 构建价格
  void buildCalories(int calories); // 构建热量
  Dish getResult(); // 获取最终结果
}
// ConcreteBuilder: 具体建造者
class ConcreteBuilder implements Builder {
  late Dish dish; // 菜品对象
  ConcreteBuilder() {
    reset();
  }
  void reset() {
    dish = Dish('', '', 0.0, 0);
  }
  @override
  void buildName(String name) {
    dish.name = name;
  }
  @override
  void buildDescription(String description) {
    dish.description = description;
  }
  @override
  void buildPrice(double price) {
    dish.price = price;
  }
  @override
  void buildCalories(int calories) {
    dish.calories = calories;
  }
  @override
  Dish getResult() {
    final createdDish = dish;
    reset();
    return createdDish;
  }
}
// Director: 指导者
class Director {
  late Builder builder; // 建造者对象
  void setBuilder(Builder builder) {
    this.builder = builder;
  }
  void construct(String name, String description, double price, int calories) {
    builder.buildName(name);
    builder.buildDescription(description);
    builder.buildPrice(price);
    builder.buildCalories(calories);
  }
}

可以使用如下代码进行简单测试:

// Client 代码
void main() {
  final director = Director();
  final builder = ConcreteBuilder();
  director.setBuilder(builder);
  // 菜品1:小炒黄牛肉
  director.construct('小炒黄牛肉', '特殊小炒菜品,黄牛肉鲜嫩可口', 68.99, 350);
  final dish1 = builder.getResult();
  dish1.display();
  // 菜品2:剁椒鱼头
  director.construct('剁椒鱼头', '鱼头搭配剁椒酱烹饪', 58.99, 400);
  final dish2 = builder.getResult();
  dish2.display();
  // 菜品3:小炒猪肚
  director.construct('小炒猪肚', '麻辣鲜香的湖南猪肚菜品', 48.99, 300);
  final dish3 = builder.getResult();
  dish3.display();
  // 菜品4:酥香大鲫鱼
  director.construct('酥香大鲫鱼', '经酥香可口的大鲫鱼', 88.99, 450);
  final dish4 = builder.getResult();
  dish4.display();
  // 菜品5:酸辣大肠
  director.construct('酸辣大肠', '酸辣可口的大肠烹饪', 38.99, 250);
  final dish5 = builder.getResult();
  dish5.display();
  // 菜品6:香辣猪蹄
  director.construct('香辣猪蹄', '香辣多汁的猪蹄', 68.99, 400);
  final dish6 = builder.getResult();
  dish6.display();
}

在上面的Client代码中,我们先创建了指导者的实例和具体构建者的实例,通过 setBuilder 方法将具体构建者的实例传给指导者。这样指导者的 construct 方法就可以使用该具体构建者实例。

当我们具体用于创建某一个菜品的实例时,实际上使用的是指导者的 construct 方法,该方法的多个参数对应了多个创建的步骤,最后返回一个实际的菜品。运行结果如下:

Dish: 小炒黄牛肉, Description: 特殊小炒菜品,黄牛肉鲜嫩可口, Price: 68.99, Calories: 350
Dish: 剁椒鱼头, Description: 鱼头搭配剁椒酱烹饪, Price: 58.99, Calories: 400
Dish: 小炒猪肚, Description: 麻辣鲜香的湖南猪肚菜品, Price: 48.99, Calories: 300
Dish: 酥香大鲫鱼, Description: 经酥香可口的大鲫鱼, Price: 88.99, Calories: 450
Dish: 酸辣大肠, Description: 酸辣可口的大肠烹饪, Price: 38.99, Calories: 250
Dish: 香辣猪蹄, Description: 香辣多汁的猪蹄, Price: 68.99, Calories: 400

通过使用生成器模式,当我们的菜品构造的过程越来越复杂时,只需要对构造的步骤方法进行改动,然后通过指导者对象使用新的具体构造者中的步骤方法去构造各种不同的菜品。

目录
相关文章
|
5月前
|
开发者
简述函数和框架的区别
简述函数和框架的区别
36 1
|
开发者
Flutter笔记:build方法、构建上下文BuildContext解析
本文主要介绍Flutter中的build方法和构建上下文对象。
403 2
Flutter笔记:build方法、构建上下文BuildContext解析
|
5月前
|
开发者
简述库和框架的区别
简述库和框架的区别
64 2
|
3月前
|
机器学习/深度学习 自然语言处理 算法
LangChain 构建问题之AgentExecutor的定义如何解决
LangChain 构建问题之AgentExecutor的定义如何解决
92 0
|
5月前
|
Java Maven Docker
几种常见的构建模式及其使用方法
几种常见的构建模式及其使用方法
65 3
|
6月前
|
开发框架 Dart Java
Flutter的核心:Dart语言基础——语法与特性深度解析
【4月更文挑战第26天】Flutter框架背后的Dart语言,以其简洁的语法和独特特性深受开发者喜爱。本文深入解析Dart的语法与特性,如类型推导、动态静态类型系统、统一的类接口、访问权限控制以及并发编程支持。了解并掌握Dart,能助开发者更高效地利用Flutter构建高性能移动应用。
|
6月前
|
开发框架 Dart API
Flutter引擎工作原理:深入解析FlutterEngine
【4月更文挑战第26天】FlutterEngine是Flutter应用的关键,负责Dart代码转换为原生代码,管理应用生命周期、渲染和事件处理。它初始化Flutter运行时环境,加载并编译Dart代码,创建渲染树,处理事件并实现跨平台兼容。通过理解其工作原理,开发者能更好地掌握Flutter应用内部机制并优化开发。随着Flutter生态系统发展,FlutterEngine将持续提供强大支持。
|
6月前
|
算法 关系型数据库 MySQL
Go语言中的分布式ID生成器设计与实现
【5月更文挑战第6天】本文探讨了Go语言在分布式系统中生成全局唯一ID的策略,包括Twitter的Snowflake算法、UUID和MySQL自增ID。Snowflake算法通过时间戳、节点ID和序列号生成ID,Go实现中需处理时间回拨问题。UUID保证全局唯一,但长度较长。MySQL自增ID依赖数据库,可能造成性能瓶颈。选择策略时需考虑业务需求和并发、时间同步等挑战,以确保系统稳定可靠。
204 0
|
6月前
|
开发框架 JavaScript 前端开发
什么是渐进式框架?作用是什么?如何使用?
什么是渐进式框架?作用是什么?如何使用?
415 0
|
JavaScript 前端开发 API
深入解析JavaScript Generator 生成器的概念及应用场景
本文讲解了JS生成器的概念和应用场景。生成器是一个可以暂停和恢复执行的函数。利用生成器我们可以很方便地实现自定义的可迭代对象、状态机、惰性计算等,并且还能用它来简化我们的异步操作代码。
536 0