fdowsg
Last Updated: June 02, 2016
·
1.686K
· mozharovsky
None

Protocol Oriented Programming and Value Types in Swift

Although Swift is a young language it is extremely diverse. There is no particular way to develop in Swift, it opens countless ways to go. Apple announced that Swift is the first protocol oriented language. I highly recommend you to watch the video regarding this topic from WWDC 2015. So what is a protocol oriented programming in its heart?

Getting Started.

First of all, it's difficult enough to give a precise definition of any programming paradigm whether the talk is about the object oriented paradigm or the protocol oriented one. You need to understand that both are designed basing on simple units and concepts, and both are supposed to solve the certain set of problems.

Anyway it's important to understand what each paradigm stands for and when it is worth applying one. I'll try to demonstrate the difference between OOP (object oriented paradigm) and POP (protocol oriented paradigm)

What is my Object?

Now let's talk about OOP base unit – an object. If you're a major in any OOP based language (such as Java) probably every application you start with a class. Classes are not just templates for objects of their type, they are ultimately the same objects!

But what is an object itself? Well, it's a kind of an entity that operates the more primitive value types. Being an entity basically means uniqueness which in its turn says that every object might behave differently depending on its operated values.

Of course, you know about encapsulation. In OOP we use it designing our Classes in order to provide objects' security. But does it work?

You might encapsulate some of your data from illegitimate access. But your object still has the full and direct access to its any part. And it turns out that your worst enemy is yourself.

I started talking about the security concept to show you the key difference between types we use in two different paradigms. As soon as you have an object even with an encapsulated data, it can be modified and depending on the modification process important data might be damaged. Value types were designed as primitive units. It doesn't mean they are immutable only. But once you modified a value type, it's conceptually a new unit.

From a mathematical point, objects are entities of such importance that they create new probabilistic series or they simply affect the context they were created in.

OOP concepts.

It's a good practice to start any model implementation from its basic design. You research the connections between given values and basing on this research you can create a scheme of relationships between objects.

A relationship might be declared as an inheritance or a protocol implementation. The difference is that the inheritance as its name suggests gives an opportunity to objects to inherit each others class members (properties, methods and initializers) and later to override them. As for protocols, an object implementing a protocol must implement its required members (methods and properties).

Polymorphism is another key feature of OOP. It gives types' flexibility. For instance, if you design a method working with a Vehicle type then this method can work with any its subtype (e.g. Car).

These key concepts give an incredibly flexible way to create models of hierarchy for any kind of relationship. But somewhere it's not flexible enough...

Meet the Zoo!

To show the OOP concepts we'll write a simple program that defines relationships between objects in the Zoo.

First off, the zoo has animals. So we need a hierarchy of animals' types. For simplicity let's have only Dogs, Elephants and Ants in our zoo. Now we need to remember the biology school course to create base classes and their subclasses. A Dog is a Canid which is a Mammal which is an Animal. With this said, an Animal type is the highest one in our model.

To make it simpler, we won't continue to bother ourselves with family clusters and simply set an Elephant as a subclass of Mammal and an Ant as a subclass of Insect which is a subclass of Animal.

Pic. 1.
Picture

OK, now we have our model designed. Let's get started with the code.

Base classes.

// MARK: - Base classes 

class Animal {
    var name: String
    var weight: Double

    init(name: String, weight: Double) {
        self.name = name
        self.weight = weight
    }
}

class Mammal: Animal {
    var minimumNumberOfChildren: Int

    init(name: String, weight: Double, minimumNumberOfChildren: Int) {
        self.minimumNumberOfChildren = minimumNumberOfChildren
        super.init(name: name, weight: weight)
    }
}

class Insect: Animal {
    var feetPair: Int

    init(name: String, weight: Double, feetPair: Int) {
        self.feetPair = feetPair
        super.init(name: name, weight: weight)
    }
}

class Canid: Mammal {
    func bark() {
        println("Bark")
    }

    func play() {
        println("Canid's playing...")
    }
}

Example classes.

// MARK: - Example classes

class Dog: Canid {

}

class Ant: Insect {

}

Zoo stuff. For zoo we need a little bit more things than for other implemented classes. First, it's about storing animals. Second, it's about getting animals and filtering them by their types.

// MARK: - Zoo stuff

class Zoo {
    private var animals = [Animal]()

    func addAnimal(animal: Animal) {
        animals += [animal]
    }

    func allAnimals() -> [Animal] {
        return animals
    }

    func allMammals() -> [Mammal] {
        // [1] 
        return animals.filter { $0 is Mammal }.map { $0 as! Mammal }
    }

    func mammals<T: Mammal>() -> [T] {
        // [2]
        return animals.filter { $0 is T }.map { $0 as! T }
    }
}

Pay attention to markers.

  1. We filter objects whose type is Mammal and return the array of this type (i.e. [Mammal]).
  2. We filter objects whose type is a generic T that conforms to a Mammal type and return an array of the appropriate type.

Let's play with the code.

// MARK: - Playground 

let zoo = Zoo()

let dog = Dog(name: "Dog", weight: 15, minimumNumberOfChildren: 1)
let ant = Ant(name: "Ant", weight: 0.003, feetPair: 4)

zoo.addAnimal(dog)
zoo.addAnimal(ant)

This works just fine. OOP is great, isn't it?

Deadly Diamond of Death.

The zoo is plain weird though. We have dogs there although they've been domesticated for thousands of years which makes them our Pets. Congratulations, we just got a new type.

But wait... How do we extend our model? The naïve way is to do something like this:

class Pet: Animal {
    func play() {
        println("Pet's playing...")
    }
}

But it turns out that this class can't be inherited by dog.

/**
Multiple inheritance is not allowed.

How to determine which method is the right one?
*/
class Dog: Canid, Pet {

}

This is so called Deadly Diamond of Death.

Pic. 2.
Picture

The code above is wrong, your compiler won't allow this to happen. Otherwise it would be impossible to know which method to inherit and which to override. Multiple inheritance is not allowed in Swift.

But how do we fix this?

protocol Pet {
    func play() {
        println("Pet's playing...")
    }
}

Actually, this is just a workaround the issue. Now the question is if it would be suitable for our model? My answer is no. We lost the flexibility with this protocol. Imagine you need to operate Pets but now how to work with their base types? We have a relationship around Animal model but not around Pet.

Protocols are our Heroes.

It'd be stupid to continue the previous scheme. It's not flexible anymore, of course if Pet is just a secondary protocol you will be alright but if not, it's better to come up with a flexible model for protocols. Let's talk about protocols and why they play such an important role in Swift.

OOP concepts are good for classes. But in most cases they are not so good for value types such as structs and enums.

Do you remember what we were saying about objects? So it's time to have a talk about value types since in POP the basic unit is a value type.

A value type is a primitive one that is generally operable by high level types such as objects' ones. It's similar to primitives – integers, doubles and floats which are value types as well. So each time you work with a struct (whatever it is) you can imagine that you work with the same value as let's say 10 (which is a primitive). Going forward, value types represent a kind of container for other types. Keep this in mind since this is a basic concept of POP.

But how do protocols relate to value types? Directly! Remember that a struct can't inherit other struct as well as an enum can't inherit other enum. Yes, OOP doesn't work here. What do we do about this? We start with a protocol! Value types can implement protocols, multiple protocols.

The Zoo strikes back!

I hope for this moment you are more familiar with the basics of POP. So let's return to our zoo implementation and get it working with the POP concept.

Base protocols.

// MARK: - Base protocols

protocol Animal {
    var name: String { get set }
    var weight: Double { get set }
}

protocol Mammal: Animal {
    var minimumCountOfChildren: Int { get set }
}

protocol Insect: Animal {
    var feetPair: Int { get set }
}

protocol Canid: Mammal {
    func bark()
    func play()
}

protocol Pet {
    func play()
}

Example value types.

// MARK: - Example value types 

struct Dog: Canid, Pet {
    // MARK: - Animal
    var name: String
    var weight: Double

    // MARK: - Mammal
    var minimumCountOfChildren: Int

    func play() {
        println("Dog's playing...")
    }

    func bark() {
        println("Bark!")
    }
}

struct Elephant: Mammal {
    // MARK: - Animal
    var name: String
    var weight: Double

    // MARK: - Mammal
    var minimumCountOfChildren: Int
}

struct Ant: Insect {
    // MARK: - Animal
    var name: String
    var weight: Double

    // MARK: - Insect
    var feetPair: Int
}

Pay attention that now we use value types for Dog, Elephant and Ant. In fact they are primitive units and besides we need a more flexible model so we apply the POP concept.

The new zoo stuff. I'll cover its specifications below.

// MARK: - Zoo stuff

// [1]
struct Zoo<T> {
    private var entities = [T]()

    // [2]
    subscript(index: Int) -> T {
        set {
            entities += [newValue]
        }

        get {
            return entities[index]
        }
    }

    // [3]
    mutating func append(object: T)  {
        entities += [object]
    }

    var count: Int {
        return entities.count
    }

    /**
    Returns all animals.
    */
    func all() -> [T] {
        return entities
    }

    /**
    Returns all given types' values.
    */
    func allTypes<U>() -> [U] {
        // [4]
        return entities.filter { $0 is U }.map { $0 as! U }
    }
}
  1. The Zoo has a generic type T. That means that while creating a new Zoo instance we specify which value type it should operate.
  2. If you're unfamiliar with subscripts learn it from The Swift Programming Language.
  3. This is a mutating method that adds a new object to the storage of entities (the same type objects). It's mutating because we mutate our value type.
  4. The filtering is just the same it was. We check out the type and then cast objects.

Let's add more convenience to adding objects to the zoo. For this we will overload += operator.

func +=<T>(inout lhs: Zoo<T>, rhs: T) -> Zoo<T> {
    lhs[lhs.count + 1] = rhs
    return lhs
}

Here we go!

// MARK: - Playground

var zoo = Zoo<Animal>()

let dog = Dog(name: "Dog", weight: 20, minimumCountOfChildren: 1)
let elephant = Elephant(name: "Elephant", weight: 10000, minimumCountOfChildren: 1)
let ant = Ant(name: "Ant", weight: 0.03, feetPair: 4)

zoo += (dog)
zoo += (elephant)
zoo += (ant)

let allDogs = zoo.allTypes() as [Dog]
let all = zoo.allTypes() as [Animal] // But better is to use `zoo.all()`

POP flexibility.

Remember the issue we got with the Pet protocol for OOP model. Imagine we need a Robotic Zoo with Robots. But a RoboticDog is still a Pet!

All we have to do is to add new value types.

struct RoboticDog: Robot, Pet {
    // MARK: - Robot
    var name: String
    var weight: Double
    var batteryLife: Double

    func play() {
        println("Dog's playing...")
    }
}

struct RoboticAnt: Robot {
    // MARK: - Robot
    var name: String
    var weight: Double
    var batteryLife: Double
}

struct RoboticElephant: Robot {
    // MARK: - Robot
    var name: String
    var weight: Double
    var batteryLife: Double
}

The Zoo is flexible. So we just need to create a Robotic Zoo!

/// Robotic zoo
var roboticZoo = Zoo<Robot>()

let roboticDog = RoboticDog(name: "Robotic Dog", weight: 30, batteryLife: 100)
let roboticElephant = RoboticElephant(name: "Robotic Elephant", weight: 100000, batteryLife: 1000)
let roboticAnt = RoboticAnt(name: "Robotic Ant", weight: 0.3, batteryLife: 0.1)

roboticZoo += roboticDog
roboticZoo += roboticElephant
roboticZoo += roboticAnt

let allRoboticDogs = roboticZoo.allTypes() as [RoboticDog]
let allRobots = roboticZoo.all()

Conclusion.

We've walked through the concepts of OOP and POP and differences between them. And still the covered part is very tiny since POP is much more than what we were talking about. Besides Swift 2 brings a new feature of protocol extensions so it extends the possibilities even more.

I strongly recommend to read Erik Kerber's article on RayWenderlich where he covers new features in Swift 2 regarding POP and shows how to use it in practice.

I'll continue to investigate POP and will try to keep you updated!

P.S. Special thanks to my friend Nino for editing this article. :]

Say Thanks
Respond