TLDR:在大型项目中使用许多Swift协议非常适合测试和SOLID编码,但是我遇到了功能混乱和无效的重新声明冲突的问题。在大量使用协议的同时避免在Swift中出现这些问题的最佳实践是什么?
具体来说,我想使用协议将职责与视图类分开,以使他们无需了解用于“修饰”它们的数据模型的任何知识。但这为我的数据模型类创建了很多功能,这些功能在整个应用程序中都暴露出来,并且开始与其他协议发生冲突。
作为一个例子,假设我要根据项目中的某个数据模型设置自定义表格视图单元。我们称之为MyDataModel
。我创建一个装饰协议,如下所示:
protocol MyCellDecorator {
var headingText: String?
var descriptionText: String?
}
然后我的细胞就像
class MyCell: UITableViewCell {
@IBOutlet weak var headingLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!
func setup(fromDecorator decorator: MyCellDecorator) {
headingLabel.text = decorator.headingText
descriptionLabel.text = decorator.descriptionText
}
}
现在我需要做的就是提供实现MyCellDecorator
的数据模型类的扩展,提供headingText
和descriptionText
,然后可以将数据模型对象插入{{1 }}。
setup(fromDecorator:)
这使得电池易于测试;它将责任extension MyDataClass: MyCellDecorator {
var headingText: String {
return “Some Heading“
}
var descriptionText: String {
return “Some Description“
}
}
和驱动它的UIViewController明确分开,现在无需了解MyCell
。.
但现在MyDataModel
有两个额外的属性MyDataModel
和headingText
-随处可用。但是descriptionText
已经在整个项目中扩展了10个针对特定UI的其他装饰器协议,并且碰巧另一个协议已经定义了MyDataModel
,因此我收到了编译错误“无效的'headingText'的重新声明”。
带着所有这些麻烦,我决定退出,继续前进,只是将headingText
传递到MyDataModel
中,这些都可以编译,但是我失去了上述所有优点。
在如此大的项目中,有什么好的方法来获得那些甜蜜的甜蜜协议胜利,而又不会弄乱我班的函数表,并且在不同扩展之间存在重新声明冲突?
答案 0 :(得分:1)
但是MyDataModel已经为整个项目中的特定UI扩展了10个其他装饰器协议,并且碰巧另一个协议已经定义了headingText,因此出现编译错误“无效的'headingText'的重新声明”。
我认为这是这里的主要陷阱,您使用单一模型为应用程序的不同部分提供数据。如果我们在谈论MVC模式,那么单个模型应该只为相应的控制器提供数据。我认为在这种情况下,模型中采用的协议会少得多。
另一方面,您可以尝试在模型内部拆分功能:
例如,如果我们有
protocol CellDecorator {
var headingText: String?
var descriptionText: String?
init(withSomeData data: ...) {}
}
我们可以创建类似这样的东西
class MyCellDecorator: CellDecorator {
var headingText: String?
var descriptionText: String?
}
class MyDataClass {
lazy var cellDecorator: CellDecorator = {
return CellDecorator(withSomeData: ...)
}
}
答案 1 :(得分:1)
我同意安德烈的发展方向,但我认为这更简单。您只需要装饰器类型,并且按照它们的描述方式,它们应该能够成为简单的结构,而无需固有的协议。
struct MyCellDecorator {
let headingText: String
let descriptionText: String
}
(我将这些设置为非可选,因此强烈建议您除非在“空字符串”和“无”之间有UI区分,否则)
扩展几乎完全像以前一样工作:
extension MyDataClass {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: "Some Heading",
description: "Some Description")
}
}
在某些情况下,您可能会发现模型对象具有非常一致的方式来生成装饰器。在这里,协议将允许您提取诸如以下的代码:
protocol MyCellDecoratorConvertible {
var headingText: String { get }
var descriptionText: String { get }
}
extension MyCellDecoratorConvertible {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: headingText,
description: description)
}
}
此示例捕获单元恰好具有正确名称的情况。然后,您只需添加MyCellDecoratorConvertible,该属性即可免费获得。
所有这些的关键点在于,您将拥有model.headingText
,而不是拥有model.makeMyCellDecorator().headingText
,这将解决您的财产爆炸问题。
请注意,这将在您每次访问它时生成一个新的Decorator,这就是为什么我使用make
(factory)命名约定的原因。您可能还会考虑其他方法,例如AnyMyCellDecorator
类型的橡皮擦(但我会从简单开始;这些可能是非常小的类型,并且复制它们并不昂贵)。
您可以将UI分成模块并使用内部扩展。那些不会出现在其他模块中,这将阻止myCellDecorator
出现在任何地方。如果更方便,则可以将myCellDecorator
扩展名与MyCell放在同一文件中,并将其标记为私有。
由于这是一个庞大的现有代码库,因此我强烈建议允许任何现有代码重复来驱动您的设计。没有一种模式适合所有系统。甚至没有必要让每个装饰器都遵循完全相同的模式(在某些情况下,使用协议可能更有意义;在其他情况下,则使用结构;在其他情况下,您可能两者都想要)。您可以创建模式“语言”而无需将自己装箱在要创建额外协议的世界中,而仅仅是因为“这就是模式”。
答案 2 :(得分:0)
我玩过的一种基于结构的方式是
我没有扩展MyDataClass
,而是创建了一个简单的结构(它可以是视图控制器类的文件专用,也可以不是),
struct MyDataClassCellDecorator: MyCellDecorator {
var headingText: String? {
return "Some heading with \(data.someText)"
}
var descriptionText: String? {
return data.someOtherText
}
let data: MyDataClass
}
通过这种方式,MyCell
仍可以使用协议对其进行修饰,MyDataClass
根本不需要任何扩展,并且在我想要的任何访问范围内,我都会得到一个执行修饰逻辑的结构为MyDataClass
+ MyCellDecorator
。