Swift as a protocol-oriented programming language

With object-oriented programming, we usually begin our design by thinking about the objects and the class hierarchy. Protocol-oriented programming is a little different. Here, we begin our design by thinking about the protocols. However, as we stated at the beginning of this chapter, protocol-orientated programming is about so much more than just the protocol.

As we go through this section, we will briefly discuss the different items that make up protocol-oriented programming with regards to our current example. We will then discuss these items in depth over the next couple of chapters to give you a better understanding of how to use protocol-oriented programming as a whole in our applications.

In the previous section, when we looked at Swift as an object-oriented programming language, we designed our solution with a class hierarchy, as shown in the following diagram:

Swift as a protocol-oriented programming language

To redesign this solution with protocol-oriented programming, we would need to rethink a couple areas of this design. The first area that we would want to rethink is the Drink class. Protocol-oriented programming states that we should begin with a protocol rather than a superclass. This means that our Drink class would become a Drink protocol. We would then use protocol extensions to add common code for our drink types that will conform to this protocol. We will go over the protocols in Chapter 4, All about the Protocol, and we will cover the protocol extensions in Chapter 5, Let's Extend Some Types.

The second area that we would want to rethink is the use of reference (class) types. With Swift, Apple has stated that it is preferable to use value types over reference types where appropriate. There is a lot to consider when we decide whether to use reference or value types, and we will go over this in depth in Chapter 2, Our Type Choices. In this example, we will use value (structure) types for our drink types (Jolt and CaffeineFreeDietCoke) and a reference (class) type for our Cooler type.

The decision to use value types for our drink types and a reference type for our Cooler type, in this example, is based on how we would use the instances of these types. The instance of our drink types will only have one owner. For example, when a drink is in the cooler, the cooler owns it. But then, when a person takes the drink out, the drink is removed from the cooler and given to a person who would then own it.

The Cooler type is a little different from the drink types. While the drink types will have only one owner interacting with it at a time, instances of the Cooler type may have several parts of our code interacting with it. For example, we may have one part of our code adding drinks to the cooler while we have instances of several people taking drinks from the cooler.

To summarize it, we use a value type (structure) to model our drink types because only one part of our code should be interacting with an instance of the drinks type at any one time. However, we use a reference type (class) to model our cooler because multiple parts of our code will be interacting with the same instance of the Cooler type.

We are going to stress this many times in this book: one of the main differences between reference and values types is how we pass the instances of the type. When we pass an instance of a reference type, we are passing a reference to the original instance. This means that the changes made are reflexed in both the references. When we pass an instance of a value type, we are passing a new copy of the original instance. This means that the changes made in one instance are not reflexed in the other.

Before we examine protocol-oriented programming further, let's take a look at how we would rewrite our example in a protocol-oriented programming manner. We will start by creating our Drink Protocol:

protocol Drink {
    var volume: Double {get set}
    var caffeine: Double {get set}
    var temperature: Double {get set}
    var drinkSize: DrinkSize {get set}
    var description: String {get set}
}

Within our Drink protocol, we defined the five properties every type that conforms to this protocol must provide. The DrinkSize type is the same DrinkSize type that we defined in the object-oriented section of this chapter.

Before we add any types that conform to our Drink protocol, we want to extend the protocol. Protocol extensions were added to the Swift language in version 2, and they allow us to provide functionality to conforming types. This lets us define the behavior for all types that conform to a protocol rather than adding the behavior to each individual conforming type. Within the extension for our Drink protocol, we will define two methods: drinking() and temperaturChange(). These are the same two methods that were in our Drink superclass in the object-oriented programming section of this chapter. Following is the code for our Drink extension:

extension Drink {
    mutating func drinking(amount: Double) {
        volume -= amount
    }
    mutating func temperatureChange(change: Double) {
        temperature += change
    }
}

Now, any type that conforms to the Drink protocol will automatically receive the drinking() and the temperaturChange() methods. Protocol extensions are perfect for adding common functionality to all the types that conform to a protocol. This is similar to adding functionality to a superclass where all subclasses receive the functionally from the superclass. The individual types that conform to a protocol can also shadow any functionality provided by an extension similar to overriding functionality from a superclass.

Now let's create our Jolt and CaffeineFreeDietCoke types:

struct Jolt: Drink {
    var volume: Double
    var caffeine: Double
    var temperature: Double
    var drinkSize: DrinkSize
    var description: String
    
    init(temperature: Double) {
        self.volume = 23.5
        self.caffeine = 280
        self.temperature = temperature
        self.description = "Jolt Energy Drink"
        self.drinkSize = DrinkSize.Can24
    }
    
}

struct CaffeineFreeDietCoke: Drink {
    var volume: Double
    var caffeine: Double
    var temperature: Double
    var drinkSize: DrinkSize
    var description: String
    
    init(volume: Double, temperature: Double,
        drinkSize: DrinkSize) {
        
            self.volume = volume
            self.caffeine = 0
            self.temperature = temperature
            self.description = "Caffiene Free Diet Coke"
            self.drinkSize = drinkSize
    }
}

As we can see, both the Jolt and CaffeineFreeDietCoke types are structures rather than classes. This means that they are both value types rather than reference types, as they were in the object-oriented design. Both of the types implement the five properties that are defined in the Drink protocol as well as an initializer that will be used to initialize the instances of the types.

There is more code needed in these types as compared to the drink classes in the object-oriented example. However, it is easier to understand what is going on in these drink types because everything is being initialized within the type itself rather than in a superclass.

Finally, let's look at the cooler type:

class Cooler {
    var temperature: Double
    var cansOfDrinks = [Drink]()
    var maxCans: Int
    
    init(temperature: Double, maxCans: Int) {
        self.temperature = temperature
        self.maxCans = maxCans
    }
    
   func addDrink(drink: Drink) -> Bool {
        if cansOfDrinks.count < maxCans {
            cansOfDrinks.append(drink)
            return true
        } else {
            return false
        }
    }
    
   func removeDrink() -> Drink? {
        if cansOfDrinks.count > 0 {
            return cansOfDrinks.removeFirst()
        } else {
            return nil
        }
    }
}

As we can see, the Cooler class is the same class that we created in the Object-oriented programming section of this chapter. There could be a very viable argument for creating the Cooler type as a structure rather than a class, but it really depends on how we plan to use it in our code. Earlier, we stated that various parts of our code will need to interact with a single instance of our cooler. Therefore, in our example, it is better to implement our cooler as a reference type rather than a value type.

The following diagram shows how the new design looks:

Swift as a protocol-oriented programming language

Now that we have finished redesigning, let's summarize what protocol-oriented programming is and how it is different from object-oriented programming.