Generics Example - 2020 NFL Season
Link to repo with finished project
This is a tutorial on how to use generics in Swift, using fines that occurred during 2020 NFL season as an example.
Before we jump into understanding generics, let’s add some starting types.
First let’s add a base class Person
and subclass Player
, as well as a class Team
class Person {
let name: String
init(name: String) {
self.name = name
}
}
class Player: Person {
var teamId: UUID
init(name: String, teamId: UUID) {
self.teamId = teamId
super.init(name: name)
}
}
class Team {
let teamId: UUID
var name: String
var location: String
init(teamId: UUID, name: String, location: String) {
self.teamId = teamId
self.name = name
self.location = location
}
}
Next, we can add an Action
type to evaluate for fines. We’ll assign the performer property the Player
type for now.
struct Action {
enum ActionType {
case hitOpposingPlayerInHead
}
let type: ActionType
let performer: Player
}
Lastly, we need a league to implement fines. Let’s add a League
protocol, and a struct NFL
that conforms to our League
protocol.
protocol League {
func handle(action: Action)
}
struct NFL: League {
func handle(action: Action) {
if shouldFine(action: action) {
fine(action: action)
}
}
private func shouldFine(action: Action) -> Bool {
switch action.type {
case .hitOpposingPlayerInHead:
return true
}
}
private func fine(action: Action) {
print("\(action.performer.name) has been fined")
}
}
Now let’s test our code to make sure it works before moving on.
Run the following code and see what the output is.
var nfl = NFL()
let saints = Team(teamId: UUID(), name: "Saints", location: "New Orleans")
let chiefs = Team(teamId: UUID(), name: "Chiefs", location: "Kansas City")
let cam = Player(name: "Cam Jordan", teamId: saints.teamId)
let chris = Player(name: "Chris Jones", teamId: chiefs.teamId)
let camHit = Action(type: .hitOpposingPlayerInHead, performer: cam)
let chrisHit = Action(type: .hitOpposingPlayerInHead, performer: chris)
nfl.handle(action: camHit) // Cam Jordan has been fined
nfl.handle(action: chrisHit) // Chris Jones has been fined
Looks like our model is inaccurate. During the 2020 season Cam Jordan was fined $10,500 for hitting a Chiefs lineman in the head, but Chris Jones was not fined for any of the head punches he performed.
Let’s update our NFL
type to the following
struct NFL: League {
var favoriteTeamIds = Set<UUID>()
func handle(action: Action) {
guard !favoriteTeamIds.contains(action.performer.teamId) else {
return
}
if shouldFine(action: action) {
fine(action: action)
}
}
private func shouldFine(action: Action) -> Bool {
switch action.type {
case .hitOpposingPlayerInHead:
return true
}
}
private func fine(action: Action) {
print("\(action.performer.name) has been fined")
}
}
and update our test code and run it again
var nfl = NFL()
let saints = Team(teamId: UUID(), name: "Saints", location: "New Orleans")
let chiefs = Team(teamId: UUID(), name: "Chiefs", location: "Kansas City")
let cam = Player(name: "Cam Jordan", teamId: saints.teamId)
let chris = Player(name: "Chris Jones", teamId: chiefs.teamId)
let camHit = Action(type: .hitOpposingPlayerInHead, performer: cam)
let chrisHit = Action(type: .hitOpposingPlayerInHead, performer: chris)
nfl.favoriteTeamIds.insert(chiefs.teamId)
nfl.handle(action: camHit) // Cam Jordan has been fined
nfl.handle(action: chrisHit) // (no output)
Perfect, now it looks like our model’s more accurate.
Now, we need to make updates so that leagues can fine teams as well. To do this, we need to be able to be flexible with what types of things we can fine.
First off we need to specify what’s necessary to our league fining something. In order to do this, add protocol Finable
that will make sure whatever we’re fining will have a name and belong to a team.
protocol Finable {
var name: String { get }
var teamId: UUID { get }
}
Now we conform Player
and Team
to our new Finable
protocol
class Player: Person, Finable {
var teamId: UUID
init(name: String, teamId: UUID) {
self.teamId = teamId
super.init(name: name)
}
}
class Team: Finable {
let teamId: UUID
var name: String
var location: String
init(teamId: UUID, name: String, location: String) {
self.teamId = teamId
self.name = name
self.location = location
}
}
Next, let’s update Action
to a generic struct so that the performer
property only needs to conform to the Finable
protocol.
struct Action<PerformerType: Finable> {
enum ActionType {
case hitOpposingPlayerInHead
}
let type: ActionType
let performer: PerformerType
}
Now we need to update our League
models to handle the generic Action
type. To do this, we implement a generic action handler method.
protocol League {
func handle<T>(action: Action<T>)
}
struct NFL: League {
var favoriteTeamIds = Set<UUID>()
func handle<T>(action: Action<T>) {
guard !favoriteTeamIds.contains(action.performer.teamId) else {
return
}
if shouldFine(action: action) {
fine(action: action)
}
}
private func shouldFine<T>(action: Action<T>) -> Bool {
switch action.type {
case .hitOpposingPlayerInHead:
return true
}
}
private func fine<T>(action: Action<T>) {
print("\(action.performer.name) has been fined")
}
}
With our new generic approach to action handling and fines, we can fine teams using the same logic as what’s used to fine players.
Let’s try an example to see how things work
Let’s add breakCovidProtocol
to our ActionType
enum and update switch
statements iterating over the cases.
enum ActionType {
case hitOpposingPlayerInHead
case breakCovidProtocol
}
private func shouldFine<T>(action: Action<T>) -> Bool {
switch action.type {
case .hitOpposingPlayerInHead:
return true
case .breakCovidProtocol:
return true
}
}
Now run the following code to test our new generic implementation
var nfl = NFL()
let saints = Team(teamId: UUID(), name: "Saints", location: "New Orleans")
let ravens = Team(teamId: UUID(), name: "Ravens", location: "Baltimore")
nfl.favoriteTeamIds.insert(ravens.teamId)
let lockerroomCelebration = Action(type: .breakCovidProtocol, performer: saints)
let causeOutbreak = Action(type: .breakCovidProtocol, performer: ravens)
nfl.handle(action: lockerroomCelebration) // Saints have been fined
nfl.handle(action: causeOutbreak) // (no output)
Looks like our model’s still accurate, given the Saints are the only one’s getting fined in each situation.