Swift和iOS Universe中的工厂方法和抽象工厂

到目前为止,“工厂”一词是程序员在讨论他们(或其他)程序时最常用的词之一。 但是其中所含的含义可能非常不同:它可以是一个生成对象的类(是否为多态的); 和创建任何类型(静态或非静态)实例的方法; 它发生了,甚至任何生成方法(包括构造方法)都发生了。

当然,并不是所有生成任何事物实例的事物都可以称为“工厂”一词。 此外,用这个词可以隐藏来自“四人帮”武器库的两种不同的生成方式- “工厂方法”“抽象工厂” ,我想深入研究它们的细节,并特别注意它们的经典理解和实现。

我受到Joshua Kerivsky“工业逻辑”负责人 )的启发,或者更确切地说,是他的著作“ Repating to Patterns” ,这本书是本世纪初出版的,该书是Martin Fowler (现代编程经典著作的著名作者,该书“重构“ )。 如果某人没有阅读甚至没有听说过第一本书(而且我知道很多),那么请确保将其添加到您的阅读清单中。 这是重构和更经典的《 目标设计技术》一书的续集 设计模式

本书除其他外,包含数十种使用设计模式摆脱代码中各种“气味”的秘诀。 包括有关所讨论主题的三(至少)条“食谱”。

抽象工厂


Kerivsky在他的书中给出了两种使用该模板的情况。

首先是封装有关通过公共接口连接的特定类知识。 在这种情况下,只有工厂类型才具有此知识。 工厂的公共API将由一组方法(无论是否静态)组成,这些方法返回一个通用接口类型的实例并具有一些“对话”名称(以便了解出于特定目的需要调用哪种方法)。

第二个示例与第一个示例非常相似(通常,使用该模式的所有场景或多或少都彼此相似)。 当在程序的不同位置创建同一组的一种或多种类型的实例时,就是这种情况。 在这种情况下,工厂将再次封装有关创建实例的代码的知识,但动机有所不同。 例如,如果创建这些类型的实例的过程很复杂,并且不仅限于调用构造函数,则尤其如此。

为了更接近“ iOS”下的开发主题,可以方便地练习UIViewController子类。 实际上,这绝对是“ iOS”开发中最常见的类型之一,几乎总是在使用前“继承”,并且特定的子类对于客户端代码通常甚至都不重要。
我将尝试使代码示例尽可能接近《四人帮》一书中的经典实现,但是在现实生活中,通常会以一种或另一种方式简化代码。 只有对模板有足够的了解,才能为模板提供更多的免费使用机会。

详细的例子


假设我们在应用程序中交易车辆,并且映射取决于特定车辆的类型:我们将针对不同车辆使用UIViewController不同子类。 此外,所有车辆的状态(新旧)有所不同:

 enum VehicleCondition{ case new case used } final class BicycleViewController: UIViewController { private let condition: VehicleCondition init(condition: VehicleCondition) { self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("BicycleViewController: init(coder:) has not been implemented.") } } final class ScooterViewController: UIViewController { private let condition: VehicleCondition init(condition: VehicleCondition) { self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("ScooterViewController: init(coder:) has not been implemented.") } } 

因此,我们有一组对象的一组,根据某些条件(例如,用户单击列表中的产品,并根据其是踏板车还是自行车),在同一位置创建其类型的实例。创建适当的控制器)。 控制器构造函数具有一些每次都需要设置的参数。 这两个论据是否都赞成建立一个“工厂”,而该工厂仅会了解创建正确控制器的逻辑?

当然,该示例非常简单,并且在类似情况下的实际项目中,引入“工厂”将是明确的“过度设计” 。 但是,如果我们假设我们没有两种类型的车辆,而设计人员具有多个参数,那么“工厂”的优势将变得更加明显。

因此,让我们声明一个将扮演“抽象工厂”角色的接口:

 protocol VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController func makeScooterViewController() -> UIViewController } 

(在Swift中 ,API的简短“设计指南”建议以make开头的方法调用工厂方法。)

(在“四人帮”一书中,有一个例子是用“ C ++”给出的 ,它基于继承“虚拟”功能 。当然,使用“ Swift”,面向协议的编程范例离我们更近了。)

抽象工厂界面仅包含两种方法:创建用于销售自行车和踏板车的控制器。 方法返回的实例不是特定子类的实例,而是公共基类的实例。 因此,有关特定类型的知识范围仅限于真正需要的领域。

作为“具体工厂”,我们将使用抽象工厂接口的两种实现:

 struct NewVehicleViewControllerFactory: VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController(condition: .new) } func makeScooterViewController() -> UIViewController { return ScooterViewController(condition: .new) } } struct UsedVehicleViewControllerFactory: VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController(condition: .used) } func makeScooterViewController() -> UIViewController { return ScooterViewController(condition: .used) } } 

从代码中可以看出,在这种情况下,特定工厂负责不同条件的车辆(新旧)。

现在,创建正确的控制器将如下所示:

 let factory: VehicleViewControllerFactory = NewVehicleViewControllerFactory() let vc = factory.makeBicycleViewController() 

工厂封装类


现在简要介绍一下Kerivsky在他的书中提供的用例。

第一种情况与特定类封装有关 。 例如,使用相同的控制器来显示有关车辆的数据:

 final class BicycleViewController: UIViewController { } final class ScooterViewController: UIViewController { } 

假设我们正在处理一个单独的模块,例如,一个插件库。 在这种情况下,上述声明的类(默认情况下)保持为internal ,并且工厂internal库的公共“ API”,该库在其方法中返回控制器的基类,从而将有关特定子类的知识留在库中:

 public struct VehicleViewControllerFactory { func makeBicycleViewController() -> UIViewController { return BicycleViewController() } func makeScooterViewController() -> UIViewController { return ScooterViewController() } } 

在工厂内部移动有关创建对象的知识


第二个“案例”描述了对象复杂初始化 ,而Kerivsky作为简化代码和保护封装原理的一种方法,建议限制关于初始化过程的知识在工厂外的传播。

假设我们想同时出售汽车。 无疑,这是一种更复杂的技术,具有更多的特征。 例如,我们将自己限制在所使用的燃料类型,传输类型和轮辋尺寸上:

 enum Condition { case new case used } enum EngineType { case diesel case gas } struct Engine { let type: EngineType } enum TransmissionType { case automatic case manual } final class CarViewController: UIViewController { private let condition: Condition private let engine: Engine private let transmission: TransmissionType private let wheelDiameter: Int init(engine: Engine, transmission: TransmissionType, wheelDiameter: Int = 16, condition: Condition = .new) { self.engine = engine self.transmission = transmission self.wheelDiameter = wheelDiameter self.condition = condition super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { fatalError("CarViewController: init(coder:) has not been implemented.") } } 

相应控制器的初始化示例:

 let engineType = EngineType.diesel let engine = Engine(type: engineType) let transmission = TransmissionType.automatic let wheelDiameter = 18 let vc = CarViewController(engine: engine, transmission: transmission, wheelDiameter: wheelDiameter) 

我们可以将所有这些“小事”的责任放在专业工厂的“肩膀”上:

 struct UsedCarViewControllerFactory { let engineType: EngineType let transmissionType: TransmissionType let wheelDiameter: Int func makeCarViewController() -> UIViewController { let engine = Engine(type: engineType) return CarViewController(engine: engine, transmission: transmissionType, wheelDiameter: wheelDiameter, condition: .used) } } 

并以这种方式创建控制器:

 let factory = UsedCarViewControllerFactory(engineType: .gas, transmissionType: .manual, wheelDiameter: 17) let vc = factory.makeCarViewController() 

工厂方法


第二个“单根”模板还封装了有关特定生成类型的知识,但不是通过将这种知识隐藏在专门的类中而是通过多态来封装。 Kerivsky在他的书中用Java给出了示例,并建议使用抽象类 ,但是Swift宇宙的居民并不熟悉这个概念。 我们在这里有自己的氛围...和协议。
《四人帮》一书报道该模板也被称为“虚拟构造函数”,这并非徒劳。 在“ C ++”中,虚函数是在派生类中重新定义的函数。 该语言没有给设计者提供声明虚拟的机会,并且可能是试图模仿导致这种模式发明的预期行为。

多态对象创建


作为模板有用性的经典示例,我们考虑以下情况: 在层次结构中,不同类型具有相同方法的一种实现,但在该方法中创建和使用的对象除外 。 作为解决方案,提出了以单独的方法创建该对象并单独实现该对象的方法,并在层次结构中将通用方法提高了。 因此,不同类型将使用该方法的一般实现,并且此方法所需的对象将被多态创建。

例如,让我们返回到用于显示车辆的控制器:

 final class BicycleViewController: UIViewController { } final class ScooterViewController: UIViewController { } 

并假设使用某个实体来显示它们,例如, coordinator ,它从另一个控制器模态地表示这些控制器:

 protocol Coordinator { var presentingViewController: UIViewController? { get set } func start() } 

除了创建不同的控制器外, start()方法始终以相同的方式使用:

 final class BicycleCoordinator: Coordinator { weak var presentingViewController: UIViewController? func start() { let vc = BicycleViewController() presentingViewController?.present(vc, animated: true) } } final class ScooterCoordinator: Coordinator { weak var presentingViewController: UIViewController? func start() { let vc = ScooterViewController() presentingViewController?.present(vc, animated: true) } } 

提出的解决方案是通过一种单独的方法来创建所使用的对象:

 protocol Coordinator { var presentingViewController: UIViewController? { get set } func start() func makeViewController() -> UIViewController } 

主要方法是提供基本实现:

 extension Coordinator { func start() { let vc = makeViewController() presentingViewController?.present(vc, animated: true) } } 

在这种情况下,特定类型将采用以下形式:

 final class BicycleCoordinator: Coordinator { weak var presentingViewController: UIViewController? func makeViewController() -> UIViewController { return BicycleViewController() } } final class ScooterCoordinator: Coordinator { weak var presentingViewController: UIViewController? func makeViewController() -> UIViewController { return ScooterViewController() } } 

结论


我试图通过结合三种方法来涵盖这个简单的主题:

  • 受“四人帮”一书的启发,对接待处存在的经典宣言;
  • 使用动机,受Kerivsky的书公开启发;
  • 应用程序应用程序作为我附近编程行业的一个示例。

同时,我尝试尽可能地接近模板的教科书结构,而不破坏iOS系统现代开发方法的原理并使用Swift语言的功能(而不是更常见的C ++和Java)。

事实证明,要找到包含应用示例的主题的详细材料相当困难。 现有的大多数文章和手册仅包含表面的评论和删节的示例,与实施的教科书版本相比,它们已经被截断了。

我希望至少部分能够实现自己的目标,并且读者-至少部分对学习或更新有关此主题的知识感兴趣或至少好奇。

我关于设计模式的其他材料:


这是我的“ Twitter”的链接,我在其中发布文章的链接,还有更多内容。

Source: https://habr.com/ru/post/zh-CN451324/


All Articles