导致无效的重新声明和功能表混乱的Swift协议

时间:2019-05-24 09:43:19

标签: swift protocols

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的数据模型类的扩展,提供headingTextdescriptionText,然后可以将数据模型对象插入{{1 }}。

setup(fromDecorator:)

这使得电池易于测试;它将责任extension MyDataClass: MyCellDecorator { var headingText: String { return “Some Heading“ } var descriptionText: String { return “Some Description“ } } 和驱动它的UIViewController明确分开,现在无需了解MyCell。.

但现在MyDataModel有两个额外的属性MyDataModelheadingText-随处可用。但是descriptionText已经在整个项目中扩展了10个针对特定UI的其他装饰器协议,并且碰巧另一个协议已经定义了MyDataModel,因此我收到了编译错误“无效的'headingText'的重新声明”。

带着所有这些麻烦,我决定退出,继续前进,只是将headingText传递到MyDataModel中,这些都可以编译,但是我失去了上述所有优点。

在如此大的项目中,有什么好的方法来获得那些甜蜜的甜蜜协议胜利,而又不会弄乱我班的函数表,并且在不同扩展之间存在重新声明冲突?

3 个答案:

答案 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,这就是为什么我使用makefactory)命名约定的原因。您可能还会考虑其他方法,例如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