2

I have a rather large project structured in this format:

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 has many methods like printMe() which I want any instance of all my subclasses to be able to call.

Numbers also has an allIntegers() function which I want any instance of these subclasses to be able to call. Which would probably be better as a variable right? But I can't override variables in a subclass. So instead I use the same private variable integers in each subclass, which is read and returned by allIntegers().

Also notice an instance of Numbers itself should never call allIntegers(), it should only be called on a subclass.

Last, notice some of the subclasses contain the same objects 1, 2, 3 and then each has some custom integers. But not all the subclasses. If I later decide that all those subclasses need a 4 integer, I have to manually go through each class and punch in a 4 into the array, which is obviously error prone.

I've read up on protocol oriented programming and feel like a solution might lie in there, or I'd appreciate any other suggestions and creative approaches to architecting a better project.

Thanks!

EDIT

All subclasses are different because they also have their own functions to perform. I've updated the code to reflect this.

Imagine, a given class like One is initialized many time throughout the code base, and is always initialized with the exact same integers. Putting:

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

All throughout the code base would be error prone.

Hopefully this resolves some of the concerns of the contrived example I've put forward.

SOLUTION

Thank you everyone for your help. Here is the solution I came up with (please assume that all classes have their own unique methods).

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) })
    }
}
Frankie
  • 11,508
  • 5
  • 53
  • 60

3 Answers3

4

Update

Given the new information you added to your question here's a possible approach

I really could't resist so I slightly refactored the naming :D

Now you have a protocol

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

and a protocol extension which add the printMe function.

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

Finally you have 2 classes (it's 4 classes in your code but the idea doesn't change).

The class A always contain [1, 2, 3, 101, 102] and has it's own set of methods (doSomething())

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

The class B always contain [1, 2, 3, 201] and has a different set of methods (doSomethingElse())

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

Both A and B conform to HasInteger and then automatically receive the printMe() method.


OLD ANSWER

You don't need that

I see lots of stuff in your code:

  • inheritance
  • protocols (well there are no protocols indeed :D)
  • computed properties
  • functions
  • classes (lots of them!)

But there is no apparent reason for using all these things :)

You can simply write

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])
Luca Angeletti
  • 58,465
  • 13
  • 121
  • 148
  • Thanks for taking the time to provide an answer. I've updated my example to make it seem more realistic. Hopefully this helps. – Frankie Jan 20 '17 at 20:33
  • Misunderstood, but to prevent it from getting error prone you can use an enumerator for the types. See my appendix to @appzYourLife below. – Emptyless Jan 20 '17 at 21:21
2

Define a protocol with your common operations, including the objects accessor:

protocol Numbers {

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

    func printMeThatConformersCanOverride()

}

Provide default implementations in an extension:

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]

Because those definitions implement things declared in the protocol, conforming types can override them. You can also define things in an extension that conforming types cannot override:

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: " "))
    }

}

We can then implement a class that conforms to the protocol. We can use a let to override 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.")
    }

}

We can use a stored property to override 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)")
    }

}

We can use a computed property to override 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)")
    }

}

We don't even have to use a class type. We can use a struct type instead:

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

I said above that you can define things in an extension and if they weren't declared in the protocol, then conforming types can't override them. This can be a bit confusing in practice. What happens if you try, like One does, to override a method that was defined in an extension but isn't part of the protocol?

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

For methods declared in the protocol, you run the version belonging to the run-time type of the value. For methods not declared in the protocol, you run the version belonging to the compile-time type of the value.

muescha
  • 1,544
  • 2
  • 12
  • 22
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
1

I assume you have a far complex app, and these simple classes with some dummy functions are just an easy example. So this is a way how it could be refactored using protocols:

First step can be to change the Numbers base class to a protocol with default implementation, like:

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) })
    }
}

Then let's create a variable from objects(), as you said in your question (at this case objects is read-only, you cannot update it in an instance):

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) })
    }
}

If the objects can be read-only, you can still add a default implementation for objects getter, like:

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) })
    }
}
Balazs Nemeth
  • 2,333
  • 19
  • 29
  • 1
    This is very helpful, I didn't understand that a default implementation could be used by extending the protocol. That solves part of my problem. I've updated my question to be more specific on the rest. `One` inherits from `FirstThree` which inherits from `Numbers`. I'm trying to state that in `One` `let integers = super.integers + [101, 102]` which I can't do. – Frankie Jan 20 '17 at 21:11