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.
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.
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()
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:
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.
18.224.67.84