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
- Criar subclasses através de herança
- Sobrescrever métodos, propriedades e subscritos com
override - Chamar implementações da classe pai com
super - Prevenir herança e sobrescrita com
final - Upcasting e a mecânica de tempo de execução do polimorfismo
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:
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
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
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
// ============================================
// 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 leitura2022 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
// ============================================
// 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 leituraBuddy barks: Woof! Whiskers meows: Meow!
(2) Chamando a Superclasse com super
Use super dentro de uma sobrescrita para chamar a versão da superclasse:
// ============================================
// 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 leituraShape init: Red Drawing a shape in Red It's a circle with radius 5Dica: 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
// ============================================
// 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 leituraAdvanced 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:
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
// ============================================
// 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 leituraAlice 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
// ============================================
// 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 é chamarsuperprimeiro para executar a lógica comum, depois adicionar o comportamento específico da subclasse. Chamar ou nãosuperdepende 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. Usefinal classpara 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 (requeras?ouas!), 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
- O Swift suporta apenas herança simples — uma subclasse pode herdar de exatamente uma superclasse
- A palavra-chave
overridesobrescreve métodos, propriedades computadas e subscritos da superclasse superchama a implementação da superclasse dentro de uma sobrescritafinalimpede que uma classe seja estendida ou que um método/propriedade seja sobrescrito- O polimorfismo permite que uma referência de superclasse chame a implementação sobrescrita da subclasse — "uma interface, muitos comportamentos"
- A inicialização da subclasse acontece em duas fases: primeiro as propriedades armazenadas da subclasse, depois a superclasse
📝 Exercícios
- Básico: Crie uma classe base
Pessoa(propriedades:nome,idade; método:apresentar()), depois crie subclassesEstudanteeProfessorque sobrescrevemapresentar()e adicionam suas próprias propriedades (matricula,disciplina). - Intermediário: Projete uma classe base
ItemMidiacom uma propriedadetituloe um métodoreproduzir(). Crie subclassesFilme(extra:diretor) eMusica(extra:artista) que sobrescrevemreproduzir(). Use um array polimórfico para reproduzir todos. - Desafio: Implemente um "Sistema de Controle de Permissões": classe base
Usuariocom propriedadenivelPermissaoe métodopodeAcessar(_:). Crie subclassesUsuarioAdmin,UsuarioEditor,UsuarioVisualizador.UsuarioAdminusa um métodofinaloverride func podeAcessar(_:)que sempre retorna true. Use um array polimórfico para verificar o acesso de cada usuário uniformemente.