Immutability is good. I have discussed this issue, briefly, in this blog in the past, and it won’t be the last time we discuss it.
Today, we are going to look at how not enforcing immutability can have undesired and unexpected side effects.
Let’s consider this class:
class Developer { var baseSalary: Int var yearsOfExperience: Int init(baseSalary: Int, yearsOfExperience: Int) { self.baseSalary = baseSalary self.yearsOfExperience = yearsOfExperience } }
Now, let’s assume that Human Resources calculates monthly payslips like this:
class HRDepartment { func calculatePaySlip(developer: Developer) -> Int { return developer.baseSalary + (developer.yearsOfExperience * 2) } }
So, this could happen:
let hr = HRDepartment() let charles = Developer(baseSalary: 100, yearsOfExperience: 1) let charlesPayslip = hr.calculatePaySlip(charles) class HackedDeveloper: Developer { override var yearsOfExperience: Int { get { return 100000000000 } set (newValue) { } } } let = HackedDeveloper(baseSalary: 100, yearsOfExperience: 1) let hackedCharlesPaySlip = hr.calculatePaySlip()
Boom!
One solution would be making the Developer a struct or a final class. But for the sake of the argument, let’s say that making that class final is not a possibility, because we actually need to subclass it. Which is not a rare use case, by the way. Not everything can always be final, even if it probably should.
In this case, one solution might be declaring those properties as final:
class Developer { final var baseSalary: Int final var yearsOfExperience: Int init(baseSalary: Int, yearsOfExperience: Int) { self.baseSalary = baseSalary self.yearsOfExperience = yearsOfExperience } } class HRDepartment { func calculatePaySlip(developer: Developer) -> Int { return developer.baseSalary + (developer.yearsOfExperience * 2) } } let hr = HRDepartment() let charles = Developer(baseSalary: 100, yearsOfExperience: 1) let charlesPayslip = hr.calculatePaySlip(charles) class HackedDeveloper: Developer { } let = HackedDeveloper(baseSalary: 100, yearsOfExperience: 1) let hackedCharlesPaySlip = hr.calculatePaySlip()
The point of this post is that the defaults (default access modifiers, default read/write access) are not always the best. And that we need to be aware of what might happen when we rely on the defaults.
And, most importantly, unexpected see effects are avoidable. And we should avoid them.
Thanks for sharing your knowledge.
Also the other possibility is to declare the properties like private let.
private let baseSalary: Int
private let yearsOfExperience: Int