Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects of different classes to be treated as objects of a common superclass. It enables a single interface to represent different underlying forms (data types). In Swift, polymorphism is achieved through inheritance and protocols.

Key Concepts

  1. Definition: Polymorphism means "many shapes" and allows methods to do different things based on the object it is acting upon.
  2. Types of Polymorphism:
    • Compile-time Polymorphism: Achieved through method overloading and operator overloading.
    • Runtime Polymorphism: Achieved through method overriding.

Method Overloading

Method overloading allows multiple methods in the same scope to have the same name but different parameters.

Example

class MathOperations {
    func add(a: Int, b: Int) -> Int {
        return a + b
    }
    
    func add(a: Double, b: Double) -> Double {
        return a + b
    }
}

let math = MathOperations()
print(math.add(a: 5, b: 3)) // Output: 8
print(math.add(a: 5.5, b: 3.3)) // Output: 8.8

In this example, the add method is overloaded to handle both Int and Double types.

Method Overriding

Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass.

Example

class Animal {
    func makeSound() {
        print("Some generic animal sound")
    }
}

class Dog: Animal {
    override func makeSound() {
        print("Bark")
    }
}

class Cat: Animal {
    override func makeSound() {
        print("Meow")
    }
}

let animals: [Animal] = [Dog(), Cat()]

for animal in animals {
    animal.makeSound()
}

In this example, the makeSound method is overridden in the Dog and Cat subclasses. When iterating through the animals array, the correct method is called based on the actual object type.

Protocols and Polymorphism

Protocols in Swift define a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. Classes, structs, and enums can conform to protocols to provide actual implementations of those requirements.

Example

protocol Shape {
    func area() -> Double
}

class Circle: Shape {
    var radius: Double
    
    init(radius: Double) {
        self.radius = radius
    }
    
    func area() -> Double {
        return Double.pi * radius * radius
    }
}

class Rectangle: Shape {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
    
    func area() -> Double {
        return width * height
    }
}

let shapes: [Shape] = [Circle(radius: 5), Rectangle(width: 4, height: 6)]

for shape in shapes {
    print("Area: \(shape.area())")
}

In this example, both Circle and Rectangle conform to the Shape protocol. The area method is implemented differently in each class, demonstrating polymorphism through protocols.

Practical Exercise

Exercise

Create a base class Vehicle with a method move(). Then, create two subclasses Car and Bicycle that override the move() method. Instantiate objects of Car and Bicycle and call the move() method on each.

Solution

class Vehicle {
    func move() {
        print("The vehicle is moving")
    }
}

class Car: Vehicle {
    override func move() {
        print("The car is driving")
    }
}

class Bicycle: Vehicle {
    override func move() {
        print("The bicycle is pedaling")
    }
}

let myCar = Car()
let myBicycle = Bicycle()

myCar.move() // Output: The car is driving
myBicycle.move() // Output: The bicycle is pedaling

Common Mistakes and Tips

  • Forgetting to use override keyword: When overriding a method in a subclass, always use the override keyword to avoid compilation errors.
  • Not calling the superclass method: If you need to call the superclass method within the overridden method, use super.methodName().
  • Misunderstanding protocol conformance: Ensure that all required methods and properties are implemented when conforming to a protocol.

Conclusion

Polymorphism is a powerful feature in Swift that allows for flexible and reusable code. By understanding and utilizing method overloading, method overriding, and protocols, you can create more dynamic and scalable applications. In the next section, we will explore another key concept in object-oriented programming: Protocols.

© Copyright 2024. All rights reserved