Now, it is time to code another protocol that will be used as a constraint later, when we define another class that takes advantage of generics with two constrained generic types. The following lines show the code for the DeeJayProtocol
protocol. The public
modifier followed by the protocol
keyword and the protocol name, DeeJayProtocol
, composes the protocol declaration, as follows. The code file for the sample is included in the swift_3_oop_chapter_06_10
folder:
public protocol DeeJayProtocol {
var name: String { get }
init(name: String)
func playMusicToDance()
func playMusicToSing()
}
The protocol declares a name: String
read-only stored property and two method requirements: playMusicToDance
and playMusicToSing
. As you
learned in the previous chapter, the protocol includes only the method declaration because the classes that conform to the DeejayProtocol
protocol will be responsible for providing the implementation of the name
stored property and the other two methods.
In addition, the protocol specifies an initializer requirement. The initializer requires a name
argument; therefore, we will make sure that we will be able to create an instance of any class that conforms to this protocol by providing a value to a name
argument during the initialization.
Now, we will declare a class named HorseDeeJay
that conforms to the previously defined DeeJayProtocol
protocol. We can read the class declaration as "The HorseDeeJay
class implements the DeeJayProtocol
protocol." Take a look at the following code. The code file for the sample is included in the swift_3_oop_chapter_06_10
folder:
open class HorseDeeJay: DeeJayProtocol {
open let name: String
public required init(name: String) {
self.name = name
}
open func playMusicToDance() {
print("My name is (name). Let's Dance.")
// Multiple musical notes emoji icon
print(String(UnicodeScalar(0x01F3B6)!))
// Dancer emoji icon
print(String(UnicodeScalar(0x01F483)!))
}
open func playMusicToSing() {
print("Time to sing!")
// Guitar emoji icon
print(String(UnicodeScalar(0x01F3B8)!))
}
}
The HorseDeeJay
class declares an initializer that assigns the value of the required name
argument to the name
read-only stored property. The class declares a name
read-only stored property.
The playMusicToDance
method prints a message that displays the horse DJ name and invites the party members to dance. Then, it prints the multiple musical notes and dancer emoji icons. The playMusicToSing
method prints a message that invites the party members to sing. Then, it prints a guitar emoji icon.
The following lines declare a subclass of the previously created Party<AnimalElement>
class that takes advantage of generics to work with two constrained types. The type constraints declaration is included within angle brackets (< >
). In this case, we have two generic type parameters: AnimalElement
and DeeJayElement
. The generic type parameter named AnimalElement
must conform to the AnimalProtocol
protocol and also the Equatable
protocol, as it happened in the Party<AnimalElement>
superclass. The generic type parameter named DeeJayElement
must conform to the DeeJayProtocol
protocol. The where
keyword allows us to add a second constraint to the generic type parameter named AnimalElement
. This way, the class specifies constraints for both the AnimalElement
and DeeJayElement
generic type parameters.
Don't forget that we are talking about a subclass of Party<AnimalElement>
; therefore, we inherited a required initializer that only receives a leader
argument. We overrode this required initializer with code that calls the fatalErrorfunction
to print a message and stop execution. This way, we make sure that the inherited required initializer cannot be used with this class. The following code highlights the lines that use the DeeJayElement
generic type parameter. The code file for the sample is included in the swift_3_oop_chapter_06_10
folder:
open class PartyWithDeeJay<AnimalElement: AnimalProtocol, DeeJayElement: DeeJayProtocol>: Party<AnimalElement> where AnimalElement: Equatable { public var deeJay: DeeJayElement init(leader: AnimalElement, deeJay: DeeJayElement) { self.deeJay = deeJay super.init(leader: leader) } public required init(leader: AnimalElement) { fatalError("init(leader:) has not been implemented") } open override func dance() { deeJay.playMusicToDance() super.dance() } open override func sing() { deeJay.playMusicToSing() super.sing() } }
Now, we will analyze many code snippets to understand how the code included in the PartyWithDeeJay<AnimalElement, DeeJayElement>
class works. The following line starts the class body and declares a public deeJay
stored property of the type specified by DeeJayElement
:
public var deeJay: DeeJayElement
The following lines declare an initializer that receives two arguments--leader
and deeJay
--whose types are AnimalElement
and DeeJayElement
. The arguments specify the first party leader, the first member of the party, and the DJ that will make the party members dance and sing. Note that the initializer calls the initializer defined in its superclass--that is, the Party<AnimalElement>
init
method--with leader
as an argument:
init(leader: T, deeJay: K) { self.deeJay = deeJay super.init(leader: leader) }
The following lines declare a dance
method, which overrides the method with the same declaration included in the superclass. The code calls the deeJay.playMusicToDance
method and then the super.dance
method, that is, the dance
method defined in the Party<AnimalElement>
superclass:
public override func dance() { deeJay.playMusicToDance() super.dance() }
Finally, the following lines declare a sing
method, which overrides the method with the same declaration included in the superclass. The code calls the deeJay.PlayMusicToSing
method and then calls the super.sing
method, that is, the sing
method defined in the Party<AnimalElement>
superclass:
public override func sing() { deeJay.playMusicToSing() super.sing() }
18.118.24.106