工厂办法形式
工厂办法形式是一种创立型规划形式, 其在父类中供给一个创立目标的办法, 允许子类决定实例化目标的类型。
遇到的问题
假定你正在开发一款物流管理运用。 开始版本只能处理货车运送, 因而大部分代码都在坐落名为 货车
的类中。
一段时间后, 这款运用变得极受欢迎。 你每天都能收到十几次来自海运公司的恳求, 期望运用能够支撑海上物流功用。
假如代码其余部分与现有类现已存在耦合关系, 那么向程序中增加新类其实并没有那么简单。
这可是个好消息。 可是代码问题该怎么处理呢? 现在, 大部分代码都与 货车
类相关。 在程序中增加 轮船
类需求修正悉数代码。 更糟糕的是, 假如你以后需求在程序中支撑另外一种运送办法, 很或许需求再次对这些代码进行大幅修正。
最终, 你将不得不编写繁复的代码, 依据不同的运送目标类, 在运用中进行不同的处理。
解决计划
工厂办法形式主张运用特别的工厂办法替代对于目标结构函数的直接调用 (即运用 new
运算符)。 不必忧虑, 目标仍将通过 new
运算符创立, 仅仅该运算符改在工厂办法中调用罢了。 工厂办法回来的目标一般被称作 “产品”。
子类能够修正工厂办法回来的目标类型。
乍看之下, 这种更改或许毫无含义: 咱们仅仅改动了程序中调用结构函数的方位罢了。 可是, 仔细想一下, 现在你能够在子类中重写工厂办法, 然后改动其创立产品的类型。
但有一点需求留意:仅当这些产品具有一起的基类或许接口时, 子类才能回来不同类型的产品, 一起基类中的工厂办法还应将其回来类型声明为这一共有接口。
一切产品都有必要运用同一接口。
举例来说, 货车
Truck和 轮船
Ship类都有必要完成 运送
Transport接口, 该接口声明晰一个名为 deliver
交给的办法。 每个类都将以不同的办法完成该办法: 货车走陆路交给货品, 轮船走海路交给货品。 陆路运送
RoadLogistics类中的工厂办法回来货车目标, 而 海路运送
SeaLogistics类则回来轮船目标。
只要产品类完成一个一起的接口, 你就能够将其目标传递给客户代码, 而无需供给额定数据。
调用工厂办法的代码 (一般被称为客户端代码) 无需了解不同子类回来实践目标之间的差别。 客户端将一切产品视为笼统的 运送
。 客户端知道一切运送目标都供给 交给
办法, 可是并不关心其详细完成办法。
工厂办法形式结构
-
产品 (Product) 将会对接口进行声明。 对于一切由创立者及其子类构建的目标, 这些接口都是通用的。
-
详细产品 (Concrete Products) 是产品接口的不同完成。
-
创立者 (Creator) 类声明回来产品目标的工厂办法。 该办法的回来目标类型有必要与产品接口相匹配。
你能够将工厂办法声明为笼统办法, 强制要求每个子类以不同办法完成该办法。 或许, 你也能够在根底工厂办法中回来默许产品类型。
留意, 尽管它的姓名是创立者, 但它最主要的责任并不是创立产品。 一般来说, 创立者类包括一些与产品相关的中心业务逻辑。 工厂办法将这些逻辑处理从详细产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部分。 可是, 这些公司的主要作业还是编写代码, 而非出产程序员。
-
详细创立者 (Concrete Creators) 将会重写根底工厂办法, 使其回来不同类型的产品。
留意, 并不一定每次调用工厂办法都会创立新的实例。 工厂办法也能够回来缓存、 目标池或其他来历的已有目标。
伪代码
以下示例演示了怎么运用工厂办法开发跨渠道 UI (用户界面) 组件, 并一起防止客户代码与详细 UI 类之间的耦合。
跨渠道对话框示例。
根底对话框类运用不同的 UI 组件烘托窗口。 在不同的操作体系下, 这些组件外观或许略有不同, 但其功用保持一致。 Windows 体系中的按钮在 Linux 体系中仍然是按钮。
假如运用工厂办法, 就不需求为每种操作体系重写对话框逻辑。 假如咱们声明晰一个在根本对话框类中生成按钮的工厂办法, 那么咱们就能够创立一个对话框子类, 并使其通过工厂办法回来 Windows 样式按钮。 子类将承继对话框根底类的大部分代码, 一起在屏幕上依据 Windows 样式烘托按钮。
如需该形式正常作业, 根底对话框类有必要运用笼统按钮 (例如基类或接口), 以便将其扩展为详细按钮。 这样一来, 无论对话框中运用何种类型的按钮, 其代码都能够正常作业。
你能够运用此办法开发其他 UI 组件。 不过, 每向对话框中增加一个新的工厂办法, 你就离笼统工厂形式更近一步。 咱们将在稍后谈到这个形式。
工厂办法形式适合运用场景
当你在编写代码的过程中, 假如无法预知目标确切类别及其依靠关系时, 可运用工厂办法。
工厂办法将创立产品的代码与实践运用产品的代码分离, 然后能在不影响其他代码的状况下扩展产品创立部分代码。
例如, 假如需求向运用中增加一种新产品, 你只需求开发新的创立者子类, 然后重写其工厂办法即可。
假如你期望用户能扩展你软件库或结构的内部组件, 可运用工厂办法。
承继或许是扩展软件库或结构默许行为的最简单办法。 可是当你运用子类替代规范组件时, 结构怎么辨识出该子类?
解决计划是将各结构中结构组件的代码集中到单个工厂办法中, 并在承继该组件之外允许任何人对该办法进行重写。
让咱们看看详细是怎么完成的。 假定你运用开源 UI 结构编写自己的运用。 你期望在运用中运用圆形按钮, 可是原结构仅支撑矩形按钮。 你能够运用 圆形按钮
RoundButton子类来承继规范的 按钮
Button类。 可是, 你需求告诉 UI结构
UIFramework类运用新的子类按钮替代默许按钮。
为了完成这个功用, 你能够依据根底结构类开发子类 圆形按钮 UI
UIWithRoundButtons , 而且重写其 createButton
创立按钮办法。 基类中的该办法回来 按钮
目标, 而你开发的子类回来 圆形按钮
目标。 现在, 你就能够运用 圆形按钮 UI
类替代 UI结构
类。 便是这么简单!
假如你期望复用现有目标来节省体系资源, 而不是每次都重新创立目标, 可运用工厂办法。
在处理大型资源密集型目标 (比如数据库连接、 文件体系和网络资源) 时, 你会经常碰到这种资源需求。
让咱们思考复用现有目标的办法:
- 首要, 你需求创立存储空间来寄存一切现已创立的目标。
- 当别人恳求一个目标时, 程序将在目标池中搜索可用目标。
- … 然后将其回来给客户端代码。
- 假如没有可用目标, 程序则创立一个新目标 (并将其增加到目标池中)。
这些代码可不少! 而且它们有必要坐落同一处, 这样才能保证重复代码不会污染程序。
或许最显而易见, 也是最便利的办法, 便是将这些代码放置在咱们试图重用的目标类的结构函数中。 可是从定义上来讲, 结构函数始终回来的是新目标, 其无法回来现有实例。
因而, 你需求有一个既能够创立新目标, 又能够重用现有目标的普通办法。 这听上去和工厂办法十分相像。
完成办法
-
让一切产品都遵从同一接口。 该接口有必要声明对一切产品都有含义的办法。
-
在创立类中增加一个空的工厂办法。 该办法的回来类型有必要遵从通用的产品接口。
-
在创立者代码中找到对于产品结构函数的一切引用。 将它们顺次替换为对于工厂办法的调用, 一起将创立产品的代码移入工厂办法。
你或许需求在工厂办法中增加暂时参数来控制回来的产品类型。
工厂办法的代码看上去或许十分糟糕。 其间或许会有杂乱的
switch
分支运算符, 用于选择各种需求实例化的产品类。 可是不要忧虑, 咱们很快就会修正这个问题。 -
现在, 为工厂办法中的每种产品编写一个创立者子类, 然后在子类中重写工厂办法, 并将根本办法中的相关创立代码移动到工厂办法中。
-
假如运用中的产品类型太多, 那么为每个产品创立子类并无太大必要, 这时你也能够在子类中复用基类中的控制参数。
例如, 想象你有以下一些层次结构的类。 基类
邮件
及其子类航空邮件
和陆路邮件
;运送
及其子类飞机
,货车
和火车
。航空邮件
仅运用飞机
目标, 而陆路邮件
则会一起运用货车
和火车
目标。 你能够编写一个新的子类 (例如火车邮件
) 来处理这两种状况, 可是还有其他可选的计划。 客户端代码能够给陆路邮件
类传递一个参数, 用于控制其期望取得的产品。 -
假如代码通过上述移动后, 根底工厂办法中现已没有任何代码, 你能够将其转变为笼统类。 假如根底工厂办法中还有其他语句, 你能够将其设置为该办法的默许行为。
工厂办法形式优缺点
-
你能够防止创立者和详细产品之间的紧密耦合。
-
单一责任准则。 你能够将产品创立代码放在程序的单一方位, 然后使得代码更简单维护。
-
开闭准则。 无需更改现有客户端代码, 你就能够在程序中引入新的产品类型。
-
运用工厂办法形式需求引入许多新的子类, 代码或许会因而变得更杂乱。 最好的状况是将该形式引入创立者类的现有层次结构中。
代码示例
Golang 工厂办法形式解说和代码示例 – 掘金 ()