Object oriented principles: getter and setter methods

Hi folks,

I am currently learning Swift coming from Java. In Java and other OO languages it is a good style to declare class properties private and create public getters and setters in order to modify them from outside.

In swift there are built in getters and setters for computed properties only. But what is if I want to access a stored property? Should I write a custom getter method or is there another more elegant way in Swift?

Thanks

2 Likes

Java does not have computed properties, so the practice of defining public getter and setter methods is a way of providing API resilience so that, if logic needs to be added at a later date during access to a property, it can be done without breaking clients of the library. Swift doesn't have this problem. You can make a stored property public and later replace it with a computed property without impacting your clients.

6 Likes

And what is about some checks? E.g. if a property may not become negative, a setter method would first check the new value and then assign it.
What construct should I use for this? willSet/didSet function?

1 Like

willSet is perfect if you want to trap (i.e. crash) when your precondition is violated. In Swift, this is the preferred way to handle programmer errors—that is, errors you don’t anticipate should ever happen, so any occurrence could only be caused by a bug.

If you want to throw an error or return a failure value when your precondition is violated, it’s not really a precondition and you should use a method of some sort to validate the new value and set it if appropriate. But consider giving it a name that indicates why it can fail, like setFooIfValid(_:) or fillInEmptyFoo(_:) instead of just setFoo(_:).

3 Likes

thank you. It is really a change of mindset... The biggest advantage what I see is the reduced workload to type the code :slight_smile:

1 Like

If you come from a Java background you might have used Kotlin or Scala; Swift is very similar to these languages in how it handles properties.

1 Like

I reasoned more time about the stored and computed properties and still have some problems with the concept.
If I have e.g. a stored property which is initialized with a default value and then it just should be read-only? What is the best way to code it with swift?

The following solution isn't good, is it?

struct car {
     var brand:String
    //brand should be a read-only property
    

   public var publicReadOnlyBrand:String {
        get {
            return brand
        }
    }
       
}

var myCar = car (brand:"Some Brand")

print (myCar.publicReadOnlyBrand)
1 Like

Something like this:

struct Car {
  private(set) var brand: String
}

And if it will never ever change, it's even easier:

struct Car {
  let brand: String
}
2 Likes

If you write let instead of var, the property can only be set during initialization. If that's the semantic you want, it's the perfect solution.

If you want something a little more flexible, adding private(set) (or fileprivate(set), etc.) to the property will give the setter tighter access control than the getter.

3 Likes

So there's a bit more going on here.

In Swift, there's a distinction between objects that have value semantics (structs) and those that have reference semantics (classes). If you don't already know the distinction between those, you should checkout the Swift book, but I'll explain a bit in passing below:

For classes, it's more likely that you will explicitly use let or public private(set) or public internal(set). Because of the reference semantics, if you pass an object of a class type to a method, that method is free change the properties of that object, and have those changes viewed outside the method, so explicit let is needed.

For structs it's a bit different. Because structs have value semantics, passing an instance of one copies the value. So if the method wants to change properties of that object, it will need to do something like:

func something(_ x: MyStruct) {
  var s = x
  s.foo = "hello"
}

Which means you have more flexibility over choosing var vs let. It really depends on the property. What you do not want to do is the Java getter/setter circus.

3 Likes

The thing with getters and setter is actually what I want to understand. So how to ensure the encapsulation of the properties within a type (e.g. class)

I just turned my former struct in a class and added one more stored property "speed". This property must not be set below 0 and above 200. Is there any other more elegant way to do this?

class car {
    //brand should be a read-only property
    private (set) var brand:String
    
    //speed should be a property in an interval between 0 and 200
    var speed: Int {
        didSet {
            if (speed<0 || speed > 200) {
                speed = oldValue
            }
        }
    }
    
    init( _ initialBrand:String, _ initialSpeed:Int) {
        speed = initialSpeed
        brand = initialBrand
    }
    
}

var myCar = car ("Some brand",0)

myCar.speed = 210 //Speed becomes 0 again

Yeah, I think this is a valid use case for didSet. It's really up to you what happens if the constraints on that property are violated. Do you set the old value, trap, or set the nearest valid one.

And just to throw it out: my general rule for deciding between classes and structs is that if the type will have properties whose changes need to be viewed by many people, use a class. But you should always be defaulting to structs. The value semantics they bring are immensely valuable. And don't use classes for inheriting behavior. Most cases where you would use inheritance in Java can be better expressed in Swift as protocol hierarchies.

3 Likes

There is a chapter called Choosing Between Structures and Classes in the Swift documentation.

Here's how I'd write it:

class Car {
    private static let permittedSpeeds = 0...200

    private(set) var brand: String
    private(set) var speed: Int

    func accelerate(to newSpeed: Int) -> Bool {
        guard Car.permittedSpeeds.contains(newSpeed) else {
            return false
        }

        speed = newSpeed
        return true
    }

    init?(brand: String, speed: Int) {
        // Note that you need to duplicate the check here; you needed 
        // this in your example too, since inits bypass setters on self.
        // You could make this a precondition if you trust yourself not 
        // to initialize with an illegal speed; then this init wouldn't have
        // to be an "init?" (which allows it to return nil).
        guard Car.permittedSpeeds.contains(newSpeed) else {
            return nil
        }

        self.brand = brand
        self.speed = speed
    }
}

The reason I'd use a method is that people expect a setter to actually set the value (or trap if it violates a precondition). A setter which silently fails to do anything is mysterious and frustrating. But people understand that a method which takes a value could apply arbitrary logic to that value, so it's not surprising when a method doesn't do anything with a value—especially if it has a return value which indicates a failure state, like this one does.

6 Likes

Car.permittedSpeeds ~= newSpeed is a bit shorter than contains().

3 Likes