Building immutable classes

Mutability is very important in object-oriented programming. In fact, whenever we expose mutable properties, we create a class that will generate mutable instances. However, sometimes a mutable object can become a problem and in certain situations, we want to avoid objects changing their state. For example, when we work with concurrent code, an object that cannot change its state solves many concurrency problems and avoids potential bugs.

Tip

An immutable object is also known as a non-mutating object.

For example, we can create an immutable version of the previous MutableVector3D class to represent an immutable 3D vector. The new ImmutableVector3D class has three immutable instance properties declared with the let keyword instead of the previously used var keyword: x, y, and z. We can create a new ImmutableVector3D instance and initialize the immutable instance properties. Then, we can call a summed method with the delta values of x, y, and z as arguments.

Tip

Swift API Design Guidelines suggest us to name functions and methods according to their side-effects. In this case, the sum operation is naturally described by a verb; therefore, we apply the ed suffix (or its past tense) for the nonmutating method: summed. Remember that we used the verb's imperative for the mutating method: sum.

The summed public instance method receives the delta values for x, y, and z (deltaX, deltaY, and deltaZ), and returns a new instance of the same class with the values of x, y, and z initialized with the results of the sum. The following lines show the code of the ImmutableVector3D class. The code file for the sample is included in the swift_3_oop_chapter_03_16 folder:

    public class ImmutableVector3D { 
      public let x: Float 
      public let y: Float 
      public let z: Float 
     
      init(x: Float, y: Float, z: Float) { 
        self.x = x 
        self.y = y 
        self.z = z 
      } 
     
      public func summed(deltaX: Float, deltaY: Float, 
      deltaZ: Float) -> ImmutableVector3D { 
        return ImmutableVector3D(x: x + deltaX, y: y + 
        deltaY, z: z + deltaZ) 
      } 
     
      public func printValues() { 
        print("X: (self.x), Y: (self.y), Z: (self.z))") 
      } 
     
      public class func makeEqualElements(initialValue: 
      Float) -> ImmutableVector3D { 
        return ImmutableVector3D(x: initialValue, 
        y: initialValue, z: initialValue) 
      } 
      public class func makeOrigin() -> ImmutableVector3D 
      { 
        return makeEqualElements(initialValue: 0) 
      } 
    } 

In the new ImmutableVector3D class, the summed method returns a new instance of the ImmutableVector3D class, that is, the current class. In this case, the makeOrigin type method returns the results of calling the makeEqualElements type method with 0 as an argument.

The makeEqualElements type method receives an initialValue argument for all the elements of the 3D vector, creates an instance of the actual class, and initializes all the elements with the received unique value. The makeOrigin type method demonstrates how we can call another type method within a type method. Note that both the type methods specify the returned type with -> followed by the type name (ImmutableVector3D) after the arguments enclosed in parentheses. The following line shows the declaration for the makeEqualElements type method with the specified return type:

    public class func makeEqualElements(initialValue: 
    Float) -> ImmutableVector3D { 

The following lines call the makeOrigin type method, to generate an immutable 3D vector named vector0, and the summed method for the generated instance, and save the returned instance in the new vector1 variable. The call to the summed method generates a new instance and doesn't mutate the existing object. Enter the lines after the code that declares the ImmutableVector3D class. The code file for the sample is included in the swift_3_oop_chapter_03_16 folder:

    var vector0 = ImmutableVector3D.makeOrigin() 
    var vector1 = vector0.summed(deltaX: 5, deltaY: 10, 
    deltaZ: 15) 
    vector1.printValues() 

Tip

The code doesn't allow the users of the ImmutableVector3D class to change the values of the x, y, and z properties declared with the let keyword. The code doesn't compile if you try to assign a new value to any of these properties after they were initialized. Thus, we can say that the ImmutableVector3D class is 100 percent immutable. In other words, it is a non-mutating class.

Finally, the code calls the printValues method for the returned instance (vector1) to check the values of the three elements on the Playground, as shown in the following screenshot:

Building immutable classes

The immutable version adds an overhead, compared with the mutable version, because it is necessary to create a new instance of the class as a result of calling the summed method. The previously analyzed mutable version (MutableVector3D) just changed the values for the attributes, and it wasn't necessary to generate a new instance. Obviously, the immutable version (ImmutableVector3D) has both a memory and performance overhead. However, when we work with concurrent code, it makes sense to pay the extra overhead to avoid potential issues caused by mutable objects. We just have to make sure that we analyze the advantages and tradeoffs in order to decide the most convenient way of coding our specific classes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.67.84