Как использовать протоколно-ориентированное программирование для улучшения моего кода 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) })
}
}