Быстрые протоколы, вызывающие недопустимую переоценку и загромождение таблицы функций - PullRequest
3 голосов
/ 24 мая 2019

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, и я могу подключить свой объект модели данных к setup(fromDecorator:).

extension MyDataClass: MyCellDecorator {
    var headingText: String {
        return “Some Heading“
    }
    var descriptionText: String {
        return “Some Description“
    }
}

Это упрощает проверку ячейки;он четко разделяет обязанности, MyCell и UIViewController, управляющий им, теперь не нужно ничего знать о MyDataModel ..

НО теперь MyDataModel имеет два дополнительных свойства: headingText и descriptionText - доступновезде.Но MyDataModel уже расширяет 10 других протоколов декораторов для конкретного пользовательского интерфейса во всем моем проекте, и так получилось, что другой протокол уже определяет headingText, поэтому я получаю ошибку компиляции «недопустимое переобъявление« headingText »».

Со всей этой головной болью я решаю выйти, идти вперед и просто передать MyDataModel в MyCell, все это компилируется, но я теряю все вышеупомянутые преимущества.

Что хорошокаким образом, в таком большом проекте, как этот, выиграть эти сладкие сладкие протоколы, не загромождая таблицы функций моего класса и не сталкиваясь с переобъявлениями между различными расширениями?

Ответы [ 3 ]

1 голос
/ 24 мая 2019

Я согласен с тем, куда идет Андрей, но я считаю, что это еще проще. Вам просто нужны типы декораторов, и, как вы их описали, они должны быть простыми структурами, без необходимости в протоколах.

struct MyCellDecorator {
    let headingText: String
    let descriptionText: String
}

(я сделал их необязательными, и я настоятельно рекомендую, если только у вас нет различия пользовательского интерфейса между «пустой строкой» и «нет».)

Расширения работают почти так же, как и раньше:

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, который будет учитывать ваш взрыв свойств.

Обратите внимание, что при каждом обращении к нему будет создаваться новый Декоратор, поэтому я использую make ( factory ) соглашение об именах. Есть и другие подходы, которые вы могли бы рассмотреть, например ластик типа AnyMyCellDecorator (но я бы начал с простого; это, вероятно, очень маленькие типы, и их копирование не дорого).

Вы можете разделить пользовательский интерфейс на модули и использовать внутренние расширения. Они не появятся в других модулях, что предотвратит появление myCellDecorator везде. Если вам удобнее, вы можете поместить расширения myCellDecorator в один файл с MyCell и пометить их как частные.

Поскольку это большая существующая кодовая база, я настоятельно рекомендую разрешить любое существующее дублирование кода для управления вашим дизайном. Не существует единого шаблона, который идеально подходит для всех систем. Нет необходимости даже в том, чтобы каждый декоратор следовал одному и тому же шаблону (в некоторых случаях может иметь смысл использовать протокол; в других - структура; в других вы можете захотеть и то, и другое). Вы можете создать шаблон «язык», не ограничивая себя миром, в котором вы создаете дополнительные протоколы только потому, что «это шаблон».

1 голос
/ 24 мая 2019

Но MyDataModel уже расширяет 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: ...)
    } 
}
0 голосов
/ 27 мая 2019

Один из основанных на структурах способов, с которыми я играл, состоит в следующем:

Вместо расширения 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.

...