Object-Oriented Programming: Beyond the Basics

Oct 17 2023 · Swift 5.9, iOS 17, Xcode 15

Lesson 01: Composition & Aggregation

Demo 2

Episode complete

Play next episode

Next
Transcript

In the previous demo, you created a contact struct that used Composition. Now, you’ll update the struct to use aggregation.

Open the playground in progress. First, add a field to represent an identifier for each contact. This identifier MUST be unique for each contact:

struct ContactCard {
  var firstName: String
  var lastName: String
  var phoneNumber: String
  var relatedContacts: [ContactCard] = []
  let contactID = UUID() // new code
}

Notice that this value is a constant because identifiers should never change.

Next, change the type of relatedContacts to an array of UUID:

struct ContactCard {
  var firstName: String
  var lastName: String
  var phoneNumber: String
  var relatedContacts: [UUID] = [] // new code
  let contactID = UUID() 
}

If you’re familiar with value types and reference types, you can change ContactCard‘s type from a structure to a class and use references directly. This will work the same and deliver the exact same result as the code above. However, you’ll end up with a situation where you can’t change to reference types. For the purpose of explaining the lesson, you need an obvious and explicit indirect reference to the related contacts.

File shortcuts work by linking to another unique file on the hard disk. In the same way, the UUID is the unique value for the contacts. You can create shortcuts to that contact using that value. So relatedContacts is now an array of shortcuts.

Change the lines where you added the relationship between the two contacts to use the new contactID property:

ehabContact.relatedContacts.append(timContact.contactID)
timContact.relatedContacts.append(ehabContact.contactID)

The relationship between two people almost always works in both directions. It doesn’t make much sense to have one contact related to the other without the other being related back. So you want to make sure that both contacts have each other as part of their related contacts list.

There are a few drawbacks to this. For example, if someone else on your team is working with you on the app and creating contacts, they must go through two steps to properly implement the relationship. They might forget the second step, and they must also understand how the relationship is done.

Also, If you decide to change how the identifiers work later, like the property name or its type, or even change your implementation to use reference types, they will need to change their code. This is not a good object-oriented practice.

To avoid these problems, you’ll abstract how this part works from the rest of your system. Add this new function inside the ContactCard type:

struct ContactCard {

  ...
  
  mutating func addRelatedContact(_ contact: inout ContactCard) {
    relatedContacts.append(contact.contactID)
    contact.relatedContacts.append(contactID)
  }
}

Now, replace the two lines that create the relationship between the two cards with this single line:

ehabContact.addRelatedContact(&timContact)

This way, the creation of the relationship doesn’t expose any details about how it works, keeping your code secure from future updates. Security is what the next three lessons will focus on.

Now, to test this out. Add the following code:

let containsTim = ehabContact.relatedContacts.contains(timContact.contactID)
let containsEhab = timContact.relatedContacts.contains(ehabContact.contactID)

Here, you define two Boolean variables that check to see if Ehab contains the Tim contact and that the Tim contact contains Ehab. Now, print out the values:

print("Ehab contact contains Tim contact: \(containsTim)")
print("Tim contact contains Ehab contact: \(containsEhab)")

Execute the code. Both print out true. Great job.

See forum comments
Cinema mode Download course materials from Github
Previous: Introduction 2 Next: Conclusion