Dertisch 0.6.4

Dertisch 0.6.4

Richard Willis 维护。



Dertisch 0.6.4

  • 作者:
  • Richard Willis

Dertisch

Swift应用的swifty MVP框架


序言

以下部分内容已过时,正等待更新。


Dertisch 是一个围绕依赖注入构建的轻量级Swift框架。部分遵循MVVM和部分遵循VIPER,其混合特性使其不属于严格意义上的任一模式,而是一种专为通过Swift的协议导向特性设计的MVP框架。框架的名称构成一个烹饪类比的缩写词,旨在解释其自身混合特性。

  • D 餐厅领班
  • E 菜单
  • R 餐厅员工
  • T 桌子
  • I 原材料
  • S 副厨师
  • C 客户
  • H 大厨

Venn diagram of Dertisch relationships


作为设计模式的餐厅

大多数设计模式是简单设计模式,因为它们在不考虑与给定应用中其他模式关系的情况下很好地实现了自己的目的。当组合在一起时 - 一个工厂、一些观察者和一个装饰器,比如 - 听起来就像一部蒙提·派森飞行的圆桌涡的剧集的角色名单一样:没有将这些隐喻联系起来的共同元素。作家描述这种情况为混合隐喻:这是反模式的文学等价物或不良代码的信号。Dertisch 通过实现一个复杂设计模式来消除这种不良气味:隐喻在孤立情况下有道理,但集体起来也有道理。一个优秀的餐厅与一个好的应用有非常相似的结构。

  • 大多数餐厅的设计是基于餐厅/厨房主题的变体,而大多数应用程序架构则是基于视图/模型主题的变体。
  • La salle是一个优秀的餐厅,其视觉吸引人,氛围温馨舒适,并且符合其特色菜系。优秀的应用程序视图亦如此。
  • 一个好的餐厅厨房干净、高效、井井有条,专注于制作原材料。优秀的应用程序模型亦然。
  • 一个好的餐厅在其La salle和厨房之间保持清晰划分,服务员连接两者。优秀的应用程序也是如此。

这种类似复杂的设计模式使我们能够像看电影中的情节发展一样,观察给定过程中的步骤,这使得它们:1.更容易理解;2.更容易整体概念化。

引用RxSwift背后团队的说明

人类是拥有巨大视觉皮层的大生物体。当我们能够轻松地 visualize(可视化)一个概念时,推理起来就更容易。

例如,而不是

user interaction ->
event fired ->
data fetched ->
data parsed ->
view updates;

我们有

customer makes order ->
waiter takes order ->
ingredients sourced ->
chefs prepare food ->
table laid with dishes.

Dertisch的餐厅设计模式由多个简单的设计模式组成,分为三个类别

  • 客户模式:客户,以及餐厅桌
  • 厨房模式:主厨副厨原料;以及
  • La salle模式:服务员餐厅经理酒侍

在这里,La salle是一个法语名词,意思是“餐厅里的餐厅”。


如何做到既“快速”又“直观”

Dertisch的“快速”特性来自其《多面手》哲学,其中对象根据给定环境显示不同的功能和属性。

CustomerIngredient再到一系列手,所有这些手都扮演着具有特定角色的责任链。

  • 原料是数据;
  • 副厨解析数据;
  • 主厨组合数据;
  • 服务员展示数据;以及
  • 客户消费数据。

理论上,两个重叠的对象——服务员和其主厨——可以通过委托方式访问彼此,因为它们只能访问对方功能的有限子集。以一个例子来说,主厨的责任包括:

  • 接受服务员的订单;
  • 分配任务给副厨;
  • 从副厨那里准备餐点;以及
  • 给服务员 müşterilere (客户)的点菜。

在真实的餐厅中,服务员只能让HeadChef承担上述第一种责任,反之亦然,主厨只能让服务员承担最后一种责任。然而,如果服务员拥有对主厨的真实委托访问,它就可能通过将主厨转换为HeadChefForSousChef来访问其他两个责任。为了阻止这种情况,HeadChefs不是实现HeadChefForSousChefHeadChefForWaiter协议的类的实例,而是包含实现这些协议的类的离散具体实例的类的实例。

Venn diagram of Waiter/HeadChef relationships

HeadChef 是一个半公开半内部类,它拥有自己的 HeadChefForWaiterHeadChefForSousChef 对象,这些对象本身被定义为必须具体实现的协议。这些特定类被称为 Facets,就像个性的各个层面(同时参考了 Facade 设计模式)。

public protocol HeadChefFacet: class {
	init(_ headChef: HeadChef, _ key: String)
}

public protocol HeadChefForWaiter: HeadChefFacet {
	func give(_ order: CustomerOrder, _ key: String)
}

public protocol HeadChefForSousChef: HeadChefFacet {
	func give(_ prep: InternalOrder)
}

public protocol HeadChefProtocol {
	func forWaiter(_ key: String) -> HeadChefForWaiter?
	func forSousChef(_ key: String) -> HeadChefForSousChef?
	func waiter(_ key: String) -> WaiterForHeadChef?
}

internal protocol HeadChefInternalProtocol: ComplexColleagueProtocol, StaffMember {
	init(
		_ key: String,
		_ forWaiterType: HeadChefForWaiter.Type?,
		_ forSousChefType: HeadChefForSousChef.Type?,
		_ resources: [String: KitchenResource]?)
	func inject(_ waiter: WaiterForHeadChef?)
}

public class HeadChef: HeadChefInternalProtocol {
	...
}

extension HeadChef: HeadChefProtocol {
	...
}

HeadChef 的内部功能完全涉及依赖注入,而其公开功能完全涉及其其他侧面的访问授权。密钥在内部传递以确保只有 HeadChef 的侧面可以访问:其其他侧面;以及重叠对象中的侧面,在本例中是 Waiter 实例的 WaiterForHeadChef 对象。

class SomeHeadChefForWaiter: HeadChefForWaiter {
	private let headChef: HeadChef
	private let key: String

	required init(_ headChef: HeadChef, _ key: String) {
		self.headChef = headChef
		self.key = key
	}

	func give(_ order: CustomerOrder, _ key: String) {
		headChef.waiter(key)?.serve(main: FulfilledOrder("dishCooked"))
	}
}

在 Dertisch 中的角色

D 指的是 Maître D

总服务员。Maître D 传统上是 VIPER 路由,它控制 视图 的添加和删除,并管理顾客、服务员和大厨之间的关系。

E 指的是 开胃菜

顾客开始的菜品。开胃菜利用数据初始化视图控制器,并且传统上是 VIPER 实体,它是简单的数据对象。

R 指的是 餐厅工作人员

服务员。服务员传统上是部分 VIPER 展示器 和部分 MVVM 视图模型(因此是 MVP 展示器),它们由大厨提供数据来填充和控制视图,而服务员(也就是酒保)传统上是 代理,它特别提供文本的多语言支持。

T代表表格

顾客就坐的实体餐桌。餐厅餐桌通常被视为viewControllers,即用户看到的屏幕。

I代表原料

任何菜肴的成品原料。原料可以被视为services,它们查询API等以获取数据。

S代表副厨师

负责将原料组合成菜肴的二把手厨师。副厨师通常被视为proxies,它们在内部获取和设置数据。

C代表顾客

订购食物的人。顾客在通常情况下是用户。

H代表主厨

控制厨房人员和菜肴的人。主厨通常被视为VIPER的interactors,他们可以访问特定的副厨师以创建特定组合的数据。


Dertisch中的交互示例

  • 顾客向服务员下单,服务员将其带给厨房(用户与view交互,发送请求到其presenter,然后presenter将请求传递给其interactor);
  • 主厨向员工下达所需菜肴指令(交互者查询其代理);
  • 员工烹制食材,并将菜肴献给主厨(代理结合自身已有数据和从服务中异步获取所需数据);
  • 主厨将菜肴交给服务员,服务员前往顾客桌前(交互者向其展示器传递数据,并在通知视图有新数据可用前保存这些数据);
  • 服务员和酒保为顾客服务(视图通过其展示器文本代理更新自身);以及
  • 餐桌上摆放菜肴(视图根据用户原始交互进行更新)。

Dertisch旨在提供大多数应用程序共有的功能,具体而言(目前)如下。

在模型方面

  • API调用;
  • 外部图像管理;
  • 简化对Core Data的访问;
  • 对运行时属性进行简单数据存储;
  • 简化内置json文件集成;以及
  • 添加定制代理和服务的功能。

在视图方面

  • 注册与相关服务员和主厨相关的顾客;以及
  • 多语言文本支持。

主厨通过实现HeadChef协议工作;服务员通过实现Waiter协议工作;顾客通过继承Customer进行工作。


使用Dertisch

Dertisch允许您根据应用程序的具体需求创建定制的副厨师食材,并且还包含四个内置的食材类和两个内置的餐厅类,用于提供所有应用程序共有的功能。

KITCHEN: INGREDIENTS

FoodDelivery
// provides access to RESTful APIs

Freezer
// provides simplified access to Core Data storage

Images
// provides capacity to load and get copies of images

Larder
// provides simplified access to json bundled with the app

SALLE

MaitreD
// manages the addition and removal of Customers and their relationships with Head Chefs and Waiters

Sommelier
// provides multi-language support for screen text

内置的厨房食材非常轻量。例如,FoodDelivery有一个单一的call(_:from:method:flagged:)函数,该函数使用URLSession.shared.dataTask(with:)。如果您需要从API服务中获取更多功能,可以简单地创建一个自己的,例如:AlamofireIngredient,并在其中加入您需要的所有功能。

Dertisch中的所有厨房类都作为小s型唯一实例进行注入。例如,这意味着两个都有Images实例注入的独立主厨将注入相同的Images实例,因此一个主厨在该实例上设置的任何属性都会被另一个主厨读取,反之亦然。对于所有其他地方的Images注入也是同样的道理。

MaitreD负责启动Dertisch应用程序;Sommelier是所有Dertisch视图控制器的一个强制要求;并且Sommelier依赖于Larder,因此这三个默认实例化。其他的则根据需要使用时进行实例化。

通过在AppDelegate中调用MaitreD.greet(firstCustomer:)来启动您的Dertisch应用程序

class AppDelegate: UIResponder, UIApplicationDelegate {

	var window: UIWindow?

	func application(
		_ application: UIApplication,
		didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
		window = UIWindow(frame: UIScreen.main.bounds)
		MaitreD().greet(firstCustomer: "SomeCustomer", window: window!)
		return true
	}
}

MaitreD的启动程序包括对其自身的registerStaff(with: key: String)函数的调用,其中必须注册应用程序所需的厨房和餐厅员工。通过实现MaitreDExtension来扩展MaitreD以利用此功能。

extension MaitreD: MaitreDExtension {
	public func registerStaff(with key: String) {
//		register(Images.self, with: key, injecting: [FoodDelivery.self])
		register(SomeSousChef.self, with: key)
		register(SomeIngredient.self, with: key, injecting: [SomeSousChef.self])
		introduce(
			"SomeCustomer",
			as: SomeCustomer.self,
			with: key,
			waiter: SomeWaiter.self,
			chef: SomeHeadChef.self,
			kitchenStaff: [Images.self])
		introduce(
			"SomeOtherCustomer",
			 as: SomeOtherCustomer.self,
			 with: key)
	}
}

在上面的示例中,因为已经注释掉了Images,所以不会实例化这个厨房类的可注入实例,因为使用此代码的应用程序可能不需要它的功能(更合理的是简单地删除此行,但在此处保留以演示如果需要将如何使用它)。

Dertisch 厨房类可以注入其他厨房类。例如,在上面的代码示例中,因为 Images 依赖于它来加载外部图像,因此 Images 注入了 FoodDelivery

上面第一个 introduce(...) 函数中,SomeCustomerSomeWaiterSomeHeadChef 是为特定应用定制的类,它们的注册函数创建了一个 viewController -> presenter <- interactor 关系。`kitchenStaff` 是一个可选数组,用以列出 SomeHeadChef 为完成工作所需的副厨师类。

上面第二个 introduce(...) 函数展示了不需要服务员或主厨的视图控制器的示例,这意味着这是一个简单的页面,不依赖于数据。

上面的代码示例中包含了两个模型类 SomeSousChefSomeIngredient。这些是特定于实现应用的定制厨房类,但不包括在 Dertisch 中。下面是 SomeSousChef 的示例模板代码。

class SomeSousChef {
	var headChef: HeadChefForKitchenMember?
	required init(_ resources: [String: KitchenResource]?) {}
}

extension SomeSousChef: KitchenResource {}

下面是 SomeIngredient 的示例模板代码。

class SomeIngredient {
	required init(_ resources: [String: KitchenResource]?) {}
}

extension SomeIngredient: Ingredients {}

以下是 Customer 的示例模板代码。

class SomeCustomer {
	required init(maitreD: MaitreD, restaurantTable: RestaurantTable, waiter: WaiterForCustomer, sommelier: Sommelier?) {}
}

extension SomeCustomer: Customer {}

以下是 Waiter 的示例模板代码。

class SomeWaiter: Waiter {
	required init(maitreD: MaitreD) {}
}

extension SomeWaiter: WaiterForMaitreD {
	func introduce(_ customer: CustomerForWaiter, and headChef: HeadChefForWaiter?) {}
}

深入文档

Dertisch 除了上述内容外,还有很多其他元素,包括许多客户、服务员、副厨师等需要的可选项函数。然而,由于没有人除了我自己现在知道在用它,我觉得没有必要更详细地记载。如果您想了解更多,请提问。

自用笔记 - 需要记录的事项

  • 项目设置 [主目标] >常规 >部署信息 >主接口 [留空]

开发路线图

Dertisch 仍在测试版,尽管没有正式的开发时间表,以下是一些建议:

  • CustomerWaiterRxSwift 兼容;
  • 将可选的 KitchenMembers 移入独立的仓库以最小化核心框架的大小;
  • 如果 Waiter 的实例没有注入必要的依赖项,则发出警告;
  • 将图像成分重命名;
  • 将类、结构和协议变为内部的或最终的;
  • 将实用函数作为本地类扩展;
  • 将类等名称更改为其设计模式原始名称(例如,将 Waiter 改为 Presenter),然后为它们创建餐厅设计模式的包装器(类似于 RxSwift 特性);
  • 为特定设备创建新的 MetricsSousChef 数值常数;
  • 创建新的 FirebaseIngredient
  • 开始对厨房类实施 Redux 风格的 'reducer' 流程,以便它们可以成为可重写自身的结构。
  • endShift() 函数替换为弱变量等?;
  • 强制 Freezer 在启动时获取 dataModelName;
  • 将超时计时器重新引入到 FoodDelivery 中;
  • FoodDelivery 中的完整 MIME 类型列表;
  • 创建示例模板应用程序;
  • 移除致命错误。

关于名字 "Dertisch" 和 "JosephBeuysMum"

1984年,德国画家马丁·基彭贝格创作了一幅题为 "约瑟夫·贝库斯的母亲" 的肖像。贝库斯也是一位德国艺术家,主要作品是雕塑和概念作品,是基彭贝格的同行。这幅肖像并没有捕捉到贝库斯母亲的形象,即弗朗索瓦丝·贝库斯女士。甚至没有捕捉到一位女性的形象。据说这是一幅自画像,但并没有很好地捕捉到基彭贝格的形象。然而,它却惊人地捕捉到了一个名叫 "理查德·威利斯" 的人的形象。理查德是 Dertisch 的作者,出生于真正的弗朗索瓦丝·贝库斯去世的那一年。他是线上各种 "JosephBeuysMum" 用户名的背后之人,而他在这类账户中使用的头像就是基彭贝格绘画的缩略图。

"Dertisch" 在德语中意为 "桌子",是约瑟夫·贝库斯一件名为《桌子》的艺术品的名字。鉴于框架建立在服务于餐馆顾客的隐喻概念之上,从贝库斯的所有作品中,《Dertisch》作为名字非常合适。

听起来也有一点像 "Dirtish",很有趣。