Protocol composition lets our types adopt multiple protocols. This is a major advantage that we get when we use protocols rather than a class hierarchy because classes, in Swift and other single-inheritance languages, can only inherit from one superclass. The syntax for protocol composition is the same as the syntax for protocol inheritance that we just saw. The following example shows how we would use protocol composition:
struct MyStruct: ProtocolOne, ProtocolTwo, Protocolthree { //implementation here }
Protocol composition allows us to break our requirements into many smaller components rather than inheriting all the requirements from a single protocol or single superclass. This allows our type families to grow in width rather than height, which means we avoid creating bloated types that contain requirements that are not needed by all conforming types. Protocol composition may seem like a very simple concept, but it is a concept that is essential to protocol-oriented programming. Let's look at an example of protocol composition so that we can see the advantage we get from using it.
Let's say that we have the class hierarchy that's shown in the following diagram:
In this class hierarchy, we have a base class named Athlete. The Athlete base class then has two subclasses named Amateur and Pro. These classes are used depending on whether the athlete is an amateur athlete or a pro athlete. An amateur athlete may be a collegiate athlete, and we would need to store information such as which school they go to and their GPA. A pro athlete is one that gets paid for playing the game. For the pro athletes, we would need to store information such as what team they play for and their salary.
In this example, things get a little messy under the Amateur and Pro classes. As we can see, we have separate football player classes under both the Amateur and Pro classes (the AmFootballPlayer and ProFootballPlayer classes). We also have separate baseball classes under both the Amateur and Pro classes (the AmBaseballPlayer and ProBaseballPlayer classes). This means we need to have a lot of duplicate code between these classes.
With protocol composition, instead of having a class hierarchy where our subclasses inherit all the functionality from a single superclass, we have a collection of protocols that we can mix and match in our types:
We can then use one or more of these protocols as needed for our types. For example, we can create an AmFootballPlayer structure that conforms to the Athlete, Amateur, and FootballPlayer protocols. We could then create the ProFootballPlayer structure that conforms to the Athlete, Pro, and FootballPlayer protocols. This allows us to be very specific about the requirements for our types and only adopt the requirements that we need.
From a pure protocol point of view, this example may not make a lot of sense right now because protocols only define the requirements; however, in Chapter 3, Extensions, we will look at how protocol extensions can be used to implement these types with minimal duplicate code.
Now, let's look at how a protocol is a full-fledged type in Swift.