Как использовать протоколно-ориентированное программирование для улучшения моего кода Swift?

У меня довольно большой проект, структурированный в этом формате:

class One : FirstThree {

    fileprivate var integers: [Int] {
        return [1, 2, 3, 101, 102]
    }

    override func allIntegers() -> [Int] {
        return integers
    }

    func doStuffForOne() {
        //does stuff unrelated to the other classes
    }
}

class Two : FirstThree {

    fileprivate var integers: [Int] {
        return [1, 2, 3, 201]
    }

    override func allIntegers() -> [Int] {
        return integers
    }

    func doStuffForTwo() {
        //does stuff unrelated to the other classes
    }
}

class Three : Numbers {

    fileprivate var integers: [Int] {
        return [301, 302, 303]
    }

    override func allIntegers() -> [Int] {
        return integers
    }

    func doStuffForThree() {
        //does stuff unrelated to the other classes
    }
}

class FirstThree : Numbers {

    fileprivate var integers: [Int] {
        return [1, 2, 3]
    }

    override func allIntegers() -> [Int] {
        return integers
    }

    func doStuffForFirstThree() {
        //does stuff unrelated to the other classes
    }
}

class Numbers {

    func allIntegers() -> [Int] {
        fatalError("subclass this")
    }

    func printMe() {
        allIntegers().forEach({ print($0) })
    }
}

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

Numbers также имеет allIntegers() функция, которую я хочу, чтобы любой экземпляр этих подклассов мог вызывать. Что, вероятно, будет лучше в качестве переменной, верно? Но я не могу переопределить переменные в подклассе. Поэтому вместо этого я использую ту же личную переменную integers в каждом подклассе, который читается и возвращается allIntegers(),

Также обратите внимание на случай Numbers сам никогда не должен звонить allIntegers(), он должен вызываться только на подклассе.

Наконец, обратите внимание, что некоторые из подклассов содержат одинаковые объекты 1, 2, 3 и тогда у каждого есть несколько пользовательских целых чисел. Но не все подклассы. Если позже я решу, что все эти подклассы нуждаются в 4 целое число, я должен вручную пройти каждый класс и ударить в 4 в массив, который, очевидно, подвержен ошибкам.

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

Спасибо!

РЕДАКТИРОВАТЬ

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

Представьте себе, данный класс, как One инициализируется много раз по всей базе кода и всегда инициализируется с одинаковыми integers, Ввод:

let one = One(integers: [1, 2, 3, 101, 102])

Вся база кода будет подвержена ошибкам.

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

РЕШЕНИЕ

Спасибо всем за вашу помощь. Вот решение, которое я придумал (пожалуйста, предположим, что все классы имеют свои уникальные методы).

class One : FirstThree {

    override init() {
        super.init()
        self.integers = super.integers + [101, 102]
    }
}

class Two : FirstThree {

    override init() {
        super.init()
        self.integers = super.integers + [201]
    }
}

class Three : Numbers {
    var integers  = [301, 302, 303]
}

class FirstThree : Numbers {
    let integers = [1, 2, 3]
}

protocol Numbers {
    var integers: [Int] { get }
    func printMe()
}

extension Numbers {
    func printMe() {
        integers.forEach({ print($0) })
    }
}

3 ответа

Решение

Определите протокол с вашими общими операциями, включая objects сбруя:

protocol Numbers {

    /// My objects. By default, `Numbers.commonObjects`. Subclasses can override to include more objects.
    var objects: [Int] { get }

    func printMeThatConformersCanOverride()

}

Предоставить реализации по умолчанию в расширении:

extension Numbers {

    /// The default implementation of `objects`, which just returns `Numbers_defaultObjects`.
    var objects: [Int] { return Numbers_defaultObjects }

    /// Since this is declared in the protocol, conformers can override it.
    func printMeThatConformersCanOverride() {
        Swift.print("Numbers " + objects.map({ "\($0)" }).joined(separator: " "))
    }

}

/// It would be nice to make this a member of `Numbers`, but Swift won't let us.
private let Numbers_defaultObjects = [1, 2, 3]

Поскольку эти определения реализуют вещи, объявленные в протоколе, соответствующие типы могут переопределять их. Вы также можете определить вещи в расширении, которое не могут переопределить соответствующие типы:

extension Numbers {

    /// Since this is not declared in the protocol, conformers cannot override it. If you have a value of type `Numbers` and you call this method on it, you get this version.
    func printMeThatConformersCannotOverride() {
        Swift.print("Numbers " + objects.map({ "\($0)" }).joined(separator: " "))
    }

}

Затем мы можем реализовать класс, соответствующий протоколу. Мы можем использовать let переопределить objects:

class One: Numbers {

    /// You can use a `let` to override `objects`.
    let objects: [Int] = Numbers_defaultObjects + [101, 102]

    func doStuffForOne() {
        Swift.print("I'm doing One-specific stuff with \(objects)")
    }

    func printMeThatConformersCanOverride() {
        Swift.print("One wins! You don't care what type I am.")
    }

    func printMeThatConformersCannotOverride() {
        Swift.print("One wins! You think I'm type One, not type Numbers.")
    }

}

Мы можем использовать сохраненное свойство для переопределения objects:

class Two: Numbers {

    /// You can use a stored property to override `objects`.
    var objects: [Int] = Numbers_defaultObjects + [201]

    func doStuffForTwo() {
        Swift.print("I'm doing Two-specific stuff with \(objects)")
    }

}

Мы можем использовать вычисленное свойство для переопределения objects:

class Three: Numbers {

    /// You can use a computed property to override `objects`.
    var objects: [Int] { return [301, 302, Int(arc4random())] }

    func doStuffForThree() {
        Swift.print("I'm doing Three-specific stuff with \(objects)")
    }

}

Нам даже не нужно использовать тип класса. Вместо этого мы можем использовать тип struct:

struct Four: Numbers {
    func doStuffForFour() {
        Swift.print("I'm doing Four-specific stuff with \(objects)")
    }
}

Я сказал выше, что вы можете определять вещи в расширении, и если они не были объявлены в протоколе, то соответствующие типы не могут их переопределить. Это может быть немного запутанным на практике. Что произойдет, если вы попытаетесь, как One переопределить метод, который был определен в расширении, но не является частью протокола?

let one = One()

one.printMeThatConformersCanOverride()
// output: One wins! You don't care what type I am.

(one as Numbers).printMeThatConformersCanOverride()
// output: One wins! You don't care what type I am.

one.printMeThatConformersCannotOverride()
// output: One wins! You think I'm type One, not type Numbers.

(one as Numbers).printMeThatConformersCannotOverride()
// output: Numbers 1 2 3 101 102

Для методов, объявленных в протоколе, вы запускаете версию, относящуюся к типу времени выполнения значения. Для методов, не объявленных в протоколе, вы запускаете версию, принадлежащую типу значения времени компиляции.

Обновить

Учитывая новую информацию, которую вы добавили в свой вопрос, вот возможный подход

Я действительно не мог устоять, поэтому я немного изменил название:D

Теперь у вас есть протокол

protocol HasIntegers {
    var integers: [Int] { get }
    func printMe()
}

и расширение протокола, которое добавляет функцию printMe.

extension HasIntegers {
    func printMe() {
        integers.forEach { print($0) }
    }
}

Наконец, у вас есть 2 класса (это 4 класса в вашем коде, но идея не меняется).

Класс А всегда содержит [1, 2, 3, 101, 102] и имеет собственный набор методов (doSomething())

class A: HasIntegers {
    fileprivate (set) var integers: [Int] = [1, 2, 3, 101, 102]
    func doSomething() { }
}

Класс B всегда содержит [1, 2, 3, 201] и имеет другой набор методов (doSomethingElse())

class B: HasIntegers {
    fileprivate (set) var integers: [Int] = [1, 2, 3, 201]
    func doSomethingElse() { }
}

И A, и B соответствуют HasInteger, а затем автоматически получают printMe() метод.


СТАРЫЙ ОТВЕТ

Тебе это не нужно

Я вижу много вещей в вашем коде:

  • наследование
  • протоколы (ну, действительно, нет никаких протоколов:D)
  • вычисляемые свойства
  • функции
  • классы (их много!)

Но нет никакой очевидной причины для использования всех этих вещей:)

Вы можете просто написать

class Box {
    fileprivate (set) var integers: [Int]

    init(integers:[Int]) {
        self.integers = integers
    }

    func printMe() {
        integers.forEach { print($0) }
    }
}

let one = Box(integers: [1, 2, 3, 101, 102])
let two = Box(integers: [1, 2, 3, 201])
let three = Box(integers: [1, 2, 3, 301, 302, 303])
let four = Box(integers: [401, 402])

Я предполагаю, что у вас очень сложное приложение, и эти простые классы с некоторыми фиктивными функциями являются лишь простым примером. Таким образом, это способ, которым он может быть реорганизован с использованием протоколов:

Первым шагом можно изменить Numbers Базовый класс для протокола с реализацией по умолчанию, например:

class One : Numbers {

    fileprivate var _objects: [Int] {
        return [1, 2, 3, 101, 102]
    }

    func objects() -> [Int] {
        return _objects
    }
}

class Two : Numbers {

    fileprivate var _objects: [Int] {
        return [1, 2, 3, 201]
    }

    func objects() -> [Int] {
        return _objects
    }
}

class Three : Numbers {

    fileprivate var _objects: [Int] {
        return [1, 2, 3, 301, 302, 303]
    }

    func objects() -> [Int] {
        return _objects
    }
}

class Four : Numbers {

    fileprivate var _objects: [Int] {
        return [401, 402]
    }

    func objects() -> [Int] {
        return _objects
    }
}

protocol Numbers {
    func objects() -> [Int];
    func printMe() ;
}

//Default implementation of the services of Numbers.
extension Numbers {
    func objects() -> [Int] {
        fatalError("subclass this")
    }

    func printMe() {
        objects().forEach({ print($0) })
    }
}

Тогда давайте создадим переменную из objects(), как вы сказали в своем вопросе (в данном случае objects только для чтения, вы не можете обновить его в экземпляре):

class One : Numbers {

    //Protocol specifying only the getter, so it can be both `let` and `var` depending on whether you want to mutate it later or not :
    let objects = [1, 2, 3, 101, 102]

}

class Two : Numbers {

    var objects =  [1, 2, 3, 201]

}

class Three : Numbers {

    var objects  = [1, 2, 3, 301, 302, 303]

}

class Four : Numbers {

    let objects = [401, 402]

}

protocol Numbers {

    var objects:[Int] { get }
    func printMe() ;
}

//Default implementation of the services of Numbers.
extension Numbers {

    func printMe() {
        objects.forEach({ print($0) })
    }
}

Если objects может быть только для чтения, вы все равно можете добавить реализацию по умолчанию для objects геттер, как:

class Four : Numbers {

}

protocol Numbers {

    var objects:[Int] { get }
    func printMe() ;
}

//Default implementation of the services of Numbers.
extension Numbers {

    var objects: [Int] {
        return [401, 402]
    }

    func printMe() {
        objects.forEach({ print($0) })
    }
}
Другие вопросы по тегам