Swift: Herança e Polimorfismo do Swift

A herança é um dos três pilares da programação orientada a objetos, permitindo que uma classe se baseie em outra, reutilizando e estendendo o comportamento existente. Esta lição explora o mecanismo de herança do Swift e a aplicação do polimorfismo.

1. O Que Você Vai Aprender


2. Uma História Real de um Desenvolvedor de UI

(1) Problema: Duplicação Massiva de Código entre Componentes de UI

Charlie está construindo uma biblioteca de componentes de UI que requer Button, Label, TextField e mais — todos com comportamento fundamental semelhante:

SWIFT
class Button {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
    func handleTap() { /* Handle tap */ }
}
class Label {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw background */ }
}

frame, corFundo e renderizar() são quase idênticos em Button e Label. Cada novo componente copia e cola as mesmas propriedades; mudar um comportamento base exige editar 10 arquivos.

(2) Solução: Herança

SWIFT
class UIView {
    var frame: CGRect
    var backgroundColor: UIColor
    func render() { /* Draw base background */ }
}
class Button: UIView {
    override func render() {
        super.render()          // First, draw background
        // Then draw button-specific styling
    }
    func handleTap() { }
}
class Label: UIView {
    override func render() {
        super.render()
        // Then draw label-specific styling
    }
}

Propriedades comuns são definidas uma vez e reutilizadas por todas as subclasses. Mudar o comportamento base exige editar apenas um arquivo.

(3) Benefício: Reutilização + Extensibilidade

Dimensão Estilo Copiar-Colar Herança
Reutilização de código 0% — cada componente independente 100% — propriedades e métodos base
Custo de mudança Editar 10 arquivos Editar 1 classe base
Novo componente Copiar arquivo inteiro Herdar + adicionar diferenças
Consistência Fácil de esquecer edições Classe base garante comportamento consistente
Extensibilidade Risco de sobrescrita alto override + super é controlável

3. Fundamentos de Herança

(1) Subclasse

100%
graph TB
    A[Animal] --> B["Propriedades: nome, idade"]
    A --> C["Método: emitirSom()"]
    B --> D[Cachorro]
    C --> D
    D --> E["Propriedade: raca"]
    D --> F["Sobrescrita: emitirSom()"]
    A --> G[Gato]
    G --> H["Propriedade: cor"]
    G --> I["Sobrescrita: emitirSom()"]
Conceito Descrição
Classe base / Superclasse A classe da qual se herda; no Swift todas as classes derivam de AnyObject
Subclasse Uma classe que herda de uma classe base; pode adicionar novas propriedades e métodos
Herança simples Swift só suporta herança simples (uma subclasse, uma superclasse)
Classe raiz Uma classe sem superclasse é chamada de classe raiz

▶ Exemplo: Herança Básica

SWIFT
// ============================================
// Basic inheritance usage
// ============================================
// Base class
class Vehicle {
    var make: String
    var model: String
    var year: Int
    init(make: String, model: String, year: Int) {
        self.make = make
        self.model = model
        self.year = year
    }
    func description() -> String {
        return "\(year) \(make) \(model)"
    }
    func startEngine() {
        print("Engine started")
    }
}
// Subclass — inherits from Vehicle
class Car: Vehicle {
    var numberOfDoors: Int
    init(make: String, model: String, year: Int, numberOfDoors: Int) {
        self.numberOfDoors = numberOfDoors
        super.init(make: make, model: model, year: year)  // Call superclass initializer
    }
    func honk() {
        print("Beep beep!")
    }
}
// Subclass — inherits from Vehicle
class Motorcycle: Vehicle {
    var hasSidecar: Bool
    init(make: String, model: String, year: Int, hasSidecar: Bool = false) {
        self.hasSidecar = hasSidecar
        super.init(make: make, model: model, year: year)
    }
    func wheelie() {
        print("Doing a wheelie!")
    }
}
let car = Car(make: "Toyota", model: "Camry", year: 2022, numberOfDoors: 4)
print(car.description())  // Inherited method
car.honk()                // Car's own method

Saída:

TEXT 📖 Somente leitura
2022 Toyota Camry
Beep beep!

4. Sobrescrita e super

(1) Sobrescrita com override

Uma subclasse pode sobrescrever um método, propriedade computada ou subscrito da superclasse usando override:

Sobrescritível override Obrigatório Observações
Métodos de instância Sim — deve adicionar Fornece um novo corpo de função
Propriedades computadas Sim — deve adicionar Fornece novo getter/setter
Subscritos Sim — deve adicionar Fornece nova implementação de subscrito
Propriedades armazenadas Não — não pode Propriedades armazenadas não podem ser sobrescritas

▶ Exemplo: Sobrescrevendo Métodos

SWIFT
// ============================================
// Overriding superclass methods with override
// ============================================
class Animal {
    var name: String
    init(name: String) { self.name = name }
    func makeSound() {
        print("\(name) makes a sound")
    }
}
class Dog: Animal {
    override func makeSound() {
        print("\(name) barks: Woof!")
    }
}
class Cat: Animal {
    override func makeSound() {
        print("\(name) meows: Meow!")
    }
}
let dog = Dog(name: "Buddy")
let cat = Cat(name: "Whiskers")
dog.makeSound()
cat.makeSound()

Saída:

TEXT 📖 Somente leitura
Buddy barks: Woof!
Whiskers meows: Meow!

(2) Chamando a Superclasse com super

Use super dentro de uma sobrescrita para chamar a versão da superclasse:

SWIFT
// ============================================
// Calling superclass with the super keyword
// ============================================
class Shape {
    var color: String
    init(color: String) {
        self.color = color
        print("Shape init: \(color)")
    }
    func draw() {
        print("Drawing a shape in \(color)")
    }
}
class Circle: Shape {
    var radius: Double
    init(color: String, radius: Double) {
        self.radius = radius
        super.init(color: color)  // Complete own init first, then call super
    }
    override func draw() {
        super.draw()  // First, execute superclass drawing
        print("  It's a circle with radius \(radius)")
    }
}
let circle = Circle(color: "Red", radius: 5.0)
circle.draw()

Saída:

TEXT 📖 Somente leitura
Shape init: Red
Drawing a shape in Red
  It's a circle with radius 5

Dica: Durante a inicialização, uma subclasse deve primeiro completar a atribuição de suas próprias propriedades armazenadas antes de chamar super.init. Este é o mecanismo de segurança de inicialização em duas fases do Swift.


5. final e Polimorfismo

(1) final Previne Herança

A palavra-chave final pode impedir que uma classe seja estendida ou que um método/propriedade seja sobrescrito:

Uso Efeito
final class Esta classe não pode ser estendida
final func Este método não pode ser sobrescrito
final var Esta propriedade não pode ser sobrescrita

▶ Exemplo: Usando final

SWIFT
// ============================================
// final prevents inheritance and overriding
// ============================================
class Calculator {
    // Basic operations can be overridden
    func add(_ a: Int, _ b: Int) -> Int { return a + b }
    // Core logic must not be overridden
    final func process(_ a: Int, _ b: Int, operation: String) -> String {
        let result: Int
        switch operation {
        case "add": result = add(a, b)
        default: result = 0
        }
        return "Result: \(result)"
    }
}
// This subclass can override add, but not process
class AdvancedCalculator: Calculator {
    override func add(_ a: Int, _ b: Int) -> Int {
        print("Advanced add: \(a) + \(b)")
        return a + b
    }
    // override func process(...)  // Compile error!
}
let calc = AdvancedCalculator()
print(calc.process(3, 4, operation: "add"))

Saída:

TEXT 📖 Somente leitura
Advanced add: 3 + 4
Result: 7

(2) Polimorfismo

O polimorfismo permite que uma variável de tipo superclasse se refira a uma instância de subclasse, despachando dinamicamente para a implementação sobrescrita da subclasse:

100%
graph TB
    A["let formas: [Forma] = [Circulo(), Retangulo()]"] --> B["for forma in formas"]
    B --> C["forma.desenhar()  ← polimorfismo"]
    C --> D["desenhar() do Circulo"]
    C --> E["desenhar() do Retangulo"]

▶ Exemplo: Polimorfismo na Prática

SWIFT
// ============================================
// Polymorphism — processing different types uniformly
// ============================================
class Employee {
    let name: String
    init(name: String) { self.name = name }
    func work() -> String {
        return "\(name) is working"
    }
    func bonus() -> Double {
        return 0
    }
}
class Developer: Employee {
    override func work() -> String { return "\(name) is writing code" }
    override func bonus() -> Double { return 5000 }
}
class Designer: Employee {
    override func work() -> String { return "\(name) is designing UI" }
    override func bonus() -> Double { return 3000 }
}
class Manager: Employee {
    override func work() -> String { return "\(name) is managing team" }
    override func bonus() -> Double { return 8000 }
}
// Polymorphism: process different subclasses uniformly
let team: [Employee] = [
    Developer(name: "Alice"),
    Designer(name: "Bob"),
    Manager(name: "Charlie")
]
var totalBonus = 0.0
for member in team {
    print(member.work())   // Dynamically calls each implementation
    totalBonus += member.bonus()
}
print("Total bonus: $\(totalBonus)")

Saída:

TEXT 📖 Somente leitura
Alice is writing code
Bob is designing UI
Charlie is managing team
Total bonus: $16000.0

6. Exemplo Completo: Sistema de Renderização de Componentes de UI

SWIFT
// ============================================
// Complete example: UI component rendering system
// Features: Inheritance + overriding + super + final + polymorphism combined
// ============================================
import Foundation
// 1. Base class (subclassable)
class UIComponent {
    let id: String
    var x: Int
    var y: Int
    init(id: String, x: Int, y: Int) {
        self.id = id
        self.x = x
        self.y = y
    }
    func render() -> String {
        return "[\(id)] at (\(x), \(y))"
    }
    // Core logic — must not be overridden
    final func display() {
        print(render())
    }
}
// 2. Button subclass
class Button: UIComponent {
    let label: String
    init(id: String, x: Int, y: Int, label: String) {
        self.label = label
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        let base = super.render()
        return "\(base) [Button: \(label)]"
    }
}
// 3. TextField subclass
class TextField: UIComponent {
    var text: String
    init(id: String, x: Int, y: Int, text: String = "") {
        self.text = text
        super.init(id: id, x: x, y: y)
    }
    override func render() -> String {
        return "\(super.render()) [TextField: \"\(text)\"]"
    }
}
// 4. Polymorphic rendering
let components: [UIComponent] = [
    Button(id: "btn1", x: 10, y: 20, label: "Submit"),
    TextField(id: "txt1", x: 10, y: 60, text: "Hello"),
    Button(id: "btn2", x: 10, y: 100, label: "Cancel"),
    TextField(id: "txt2", x: 10, y: 140)
]
for component in components {
    component.display()  // Polymorphic call
}

Saída:

TEXT 📖 Somente leitura
[btn1] at (10, 20) [Button: Submit]
[txt1] at (10, 60) [TextField: "Hello"]
[btn2] at (10, 100) [Button: Cancel]
[txt2] at (10, 140) [TextField: ""]

❓ Perguntas Frequentes

P: O Swift suporta herança múltipla? R: Não. O Swift é uma linguagem de herança simples (uma subclasse, uma superclasse). As necessidades de herança múltipla são atendidas através de protocolos — uma classe pode estar em conformidade com múltiplos protocolos. P: Por que às vezes devo chamar super ao sobrescrever um método? R: Não é obrigatório, mas se você não chamar super, a lógica da superclasse é completamente substituída. O padrão típico é chamar super primeiro para executar a lógica comum, depois adicionar o comportamento específico da subclasse. Chamar ou não super depende da intenção do design. P: final class e struct são iguais em relação à prevenção de herança? R: Comportamentalmente sim — nenhuma das duas pode ser estendida. Mas struct é um tipo por valor e classe é um tipo por referência. Use final class para prevenir herança mantendo a semântica de referência. Se você não precisa de herança nem de semântica de referência, use struct. P: Qual é a diferença entre upcasting e downcasting? R: Upcasting trata um objeto de subclasse como tipo superclasse (automático e implícito, ex.: Funcionario f = Desenvolvedor(...)). Downcasting converte uma referência de superclasse de volta para um tipo de subclasse (requer as? ou as!), e a conversão pode falhar, então o resultado é optional. P: Por que devo inicializar minhas próprias propriedades armazenadas antes de chamar super.init? R: Este é o mecanismo de segurança de inicialização em duas fases do Swift. Fase 1: inicializar propriedades armazenadas da subclasse para a superclasse. Fase 2: personalizar da superclasse para a subclasse. Isso garante que todas as propriedades armazenadas tenham valores definidos antes que o init da superclasse possa acessá-las, prevenindo estado não inicializado.


📖 Resumo


📝 Exercícios

  1. Básico: Crie uma classe base Pessoa (propriedades: nome, idade; método: apresentar()), depois crie subclasses Estudante e Professor que sobrescrevem apresentar() e adicionam suas próprias propriedades (matricula, disciplina).
  2. Intermediário: Projete uma classe base ItemMidia com uma propriedade titulo e um método reproduzir(). Crie subclasses Filme (extra: diretor) e Musica (extra: artista) que sobrescrevem reproduzir(). Use um array polimórfico para reproduzir todos.
  3. Desafio: Implemente um "Sistema de Controle de Permissões": classe base Usuario com propriedade nivelPermissao e método podeAcessar(_:). Crie subclasses UsuarioAdmin, UsuarioEditor, UsuarioVisualizador. UsuarioAdmin usa um método final override func podeAcessar(_:) que sempre retorna true. Use um array polimórfico para verificar o acesso de cada usuário uniformemente.
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%