Pedro Vítor
Apple Developer Academy | UFPE
32 min readApr 11, 2024

--

Guia: Iniciando em Orientação a Objetos em Swift

O objetivo deste texto é ser um guia para introduzir ao tópico de Orientação a Objetos. Alguns conhecimentos sobre Swift e lógica de programação são necessários para compreender bem o texto. Caso você queira aprender ou revisar, os tópicos são:

Declaração de Variáveis e Constantes
Tipos de Variáveis
Estruturas condicionais (IF)
Loops e estruturas de repetição (FOR)
Arrays
Funções e Parâmetros
Classes
Herança
Polimorfismo

Dica: confira este outro artigo do Medium que introduz de forma visual a programação em Swift:

Além disso, utilizarei neste guia o Xcode como ferramenta de desenvolvimento, então algumas prints do código ou de erros serão de lá.

Digamos que você é um funcionário em um zoológico que ficou responsável por catalogar os animais presentes nele. Você precisa anotar o nome, idade, espécie do animal, há quanto tempo vive no zoológico, número de patas, tipo de alimentação etc.

Porém, não basta juntar tudo numa tabela; você vai precisar identificar necessidades desses animais, como calcular quando eles fazem aniversário (os animais deste zoológico adoram festas!), quanto de comida cada animal vai precisar, entre outras informações.

Para isso, você decide então usar programação. Mas como passar todos esses dados? O primeiro pensamento que você pode ter, é tentar criar uma variável para cada informação do animal.

var gatoIdade = 5
var gatoNome = "Cookie"
let gatoEspecie = "Felis catus"
// ...

var leaoIdade = 4
var leaoNome = "Simba"
let leaoEspecie = "Panthera leo"

Note que criamos a espécie como uma constante (usando “let”), pois a espécie de um animal não pode ser alterada. Já a idade e o nome podem ser alterados, então criamos como variável (usando “var”)

Já deu pra perceber que vão ser muitas informações. Para cada animal, precisaremos criar diversas variáveis… E se tivermos mais de um mesmo animal no zoológico? Já teremos uma confusão…

var gatoIdade = 5
var gatoNome = "Cookie"
let gatoEspecie = "Felis catus"

var gato2Idade = 7
var gato2Nome = "Matilda"
let gato2Especie = "Felis catus"

var gato3Idade = 3
var gato3Nome = "Nina"
let gato3Especie = "Felis catus"

//...

Para apenas três gatos, já escrevemos muitas linhas de código, apenas para declarar eles. E nem colocamos todas as informações que queríamos…

E ainda imagine se quisermos, por exemplo, dizer que um ano passou, e incrementar a idade de todos os gatos. Teremos que alterar todas variáveis de idade e aumentar cada uma delas. Fazer tudo isso a mão, com várias variáveis, vai ser complicado.

Podemos pensar de outra maneira. Se você está familiar com o conceito de array (ou lista, vector…), pode ter pensado: vamos criar um array pra cada característica do animal. Podemos ter, por exemplo, um array para idade dos gatos, outra para os nomes dos gatos e outra para as espécies.

var idadeGatos: [Int] = [5,7,3]
var nomeGatos: [String] = ["Cookie", "Matilda", "Nina"]
let especieGatos: [String] = ["Felis catus", "Felis catus", "Felis catus"]

Podemos fazer o mesmo para leões:

var idadeLeoes: [Int] = [5,7]
var nomeLeoes: [String] = ["Simba", "Mufasa"]
let especieLeoes: [String] = ["Panthera leo", "Panthera leo"]

Dessa forma, já fica um pouco melhor. Temos uma forma mais fácil de obter a informação: para o gato 1, podemos obter o seu nome a partir da array nomeGatos, acessando a posição — também chamada de índice — “0” do array. Ou poderemos obter a idade do gato 2, acessando idadeGatos na posição “1”.

var idadeGatos: [Int] = [5,7,3]
var nomeGatos: [String] = ["Cookie", "Matilda", "Nina"]
let especieGatos: [String] = ["Felis catus", "Felis catus", "Felis catus"]

print(nomeGatos[0])
// o nome "Cookie" será exibido no console

print(idadeGatos[1])
// o número 7 será exibido no console

Se quisermos incrementar a idade deles, para indicar que um ano se passou, podemos somar 1 em todos os elementos do array idadeGatos. Para isso, utilizamos um loop FOR:

var idadeGatos: [Int] = [5,7,3]
var nomeGatos: [String] = ["Cookie", "Matilda", "Nina"]
let especieGatos: [String] = ["Felis catus", "Felis catus", "Felis catus"]

print(nomeGatos[0])
// o nome "Cookie" será exibido no console

print(idadeGatos[1])
// o número 7 será exibido no console

for i in 0...2 {
idadeGatos[i] = idadeGatos[i] + 1;
}

Perceba que já está melhor que a tentativa apenas com variáveis.

Ainda assim, se precisarmos criar diversos animais, teremos diversos arrays para cada característica do animal. Perceba que teremos muitas informações repetidas e muitos arrays para lidar.

Será que temos alguma forma melhor de agrupar esses dados?

Orientação a Objetos

No dia a dia, interagimos com diversos objetos. Esses objetos podem ter características em comum, e podemos agrupá-los em tipos. Podemos fazer o mesmo em programação: criar objetos que compartilham características em comum.

Isso serve perfeitamente para o nosso exemplo. Temos animais com características em comum: nome, idade, espécie etc. Podemos tentar então definir um animal “genérico”: ele tem nome, idade e espécie. E para cada animal, definimos os valores que quisermos.

Podemos ter os animais como “objetos” e definir nome, idade e espécie como suas características em comum. E como fazer isso no código?

Primeiro, precisamos introduzir o conceito de Classe.

Classes

Classes são o conjunto de características que objetos de mesmo tipo compartilham.

A grande vantagem de utilizar classes é poder reutilizar código. Se sabemos que todos os animais terão nome, idade e espécie, definimos essas características como atributos. Atributos são informações que todos os objetos da classe têm e compartilham.

Podemos comparar classes à “planta” de uma casa: ela não é uma casa em si, mas ela define características gerais de casa: tamanho, número de quartos, banheiros etc. E podemos construir uma casa (objeto) a partir dessa planta (classe). Um objeto criado a partir de uma classe também é chamado de instância. Em resumo, a classe é uma abstração que fazemos de um objeto.

Voltando para o código, vamos criar uma classe Animal que define os atributos (características) nome, idade e especie.

class Animal {
var nome: String
var idade: Int
let especie: String
}

Este código, sozinho, produz erro. Mas calma! Chegaremos lá.

Definimos então duas variáveis: nome e idade, e a constante especie.

Note que não definimos valores para nenhum dos atributos, pois a classe é apenas uma referência do que cada instância de Animal terá. Cada animal terá nomes diferentes, idades diferentes ou espécies diferentes.

Definimos os atributos dessa classe. Mas e se quisermos criar, de fato, uma instância gato1 da classe Animal? Para isso, primeiro, precisamos criar uma função que transforme a “planta” numa “casa”, de fato. Essa função é chamada de inicializador, ou init.

Init

Init é basicamente uma função que tem como parâmetros valores que serão atribuídos aos atributos da Classe. Seu objetivo é definir pela primeira vez os valores de um objeto.

Essa função init recebe como parâmetros nomeAnimal, idadeAnimal e especieAnimal. Ela atribui esses parâmetros diretamente aos atributos da classe Animal: nome, idade e especie. Assim, podemos criar um objeto único com o nome, idade e espécie que quisermos.

Para isso, criamos uma variável nova chamada gato1:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nomeAnimal: String, idadeAnimal: Int, especieAnimal: String) {
nome = nomeAnimal
idade = idadeAnimal
especie = especieAnimal
}
}

var gato1 = Animal(nomeAnimal: "Cookie", idadeAnimal: 5, especieAnimal: "Felis catus")

Note que apesar de especie ser uma constante, o init permite definir um valor para ela, pois ela precisa ser inicializada.

gato1 terá como tipo a classe Animal, pois é um objeto dessa classe. Para indicar isso, atribuímos a essa variável (utilizando o sinal de igualdade) a chamada da função que inicializa um objeto da classe Animal.

Utilizamos o nome da classe Animal da mesma forma que fazemos quando chamamos uma função e passamos como parâmetros os valores que quisermos. Nesse exemplo, eu defini que o animal gato1 terá nome Cookie, idade 5 e será da espécie Felis catus.

Atributos vs Parâmetros, e o uso de self

Perceba que estamos lidando com dois tipos de variáveis até agora: Atributos e Parâmetros. Atributos são as variáveis ou constantes que definimos na classe Animal: nome, idade e especie. Já os Parâmetros são os valores que passamos na função init, quando criamos o objeto gato1: nomeAnimal, idadeAnimal e especieAnimal. Imagine que ao invés de criar nomes diferentes para eles, desejamos usar os mesmos nomes para parâmetros e atributos. Como o compilador vai saber quando estamos nos referindo a parâmetros ou atributos?

Indicamos isso utilizando uma palavra reservada da linguagem: self. O uso de self indica que estamos falando especificamente dos atributos da classe. Podemos declarar nossa função de init dessa maneira:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")

Então, “self.nome” é o atributo nome da classe Animal. Assim, não há confusão: estamos atribuindo a self.nome (atributo da classe Animal) o valor de nome (parâmetro da função init).

O uso de self é essencial para remover ambiguidades e indicar quando estamos lidando com os atributos da classe. Seu uso não é obrigatório, mas pode ser uma boa prática quando quisermos deixar claro que estamos acessando os atributos da classe.

Lembre-se que estamos programando dentro da classe Animal, e por isso acessamos diretamente os seus atributos usando self.

Acessando atributos da classe

Agora voltamos ao exemplo anterior. Criamos um objeto gato1 de nome Cookie, 5 anos de idade e espécie Felis catus.

Caso quiséssemos acessar algum dos atributos de gato1 fora da classe, podemos fazer assim:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
print(gato1.nome)
// será exibido no console o nome "Cookie"
print(gato1.idade)
// será exibido no console o número 5

Escrevemos o nome da variável que criamos para o animal (gato1), colocamos um ponto e escrevemos o nome da característica (nome): gato1.nome.

Agora podemos criar diversos animais diferentes:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}
var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus")
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus")
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo")
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla")
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus")

Adicionamos diversos animais. Porém, digamos você percebeu que um dos gatos do zoológico acabou de ter um filhote. Vamos criar um objeto para ele também, e dizer que sua idade é 0:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus")
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus")
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo")
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla")
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus")
var gatoFihote = Animal(nome: "Zuri", idade: 0, especie: "Felis catus")

E se nascerem mais? Sabemos que todos os filhotes que nascerem terão a mesma idade: 0. Então será que podemos fazer algo que sempre crie objetos com a idade 0 toda vez que já soubermos que é um filhote?

Criando um init diferente

Podemos criar um init específico pra quando tivermos um filhote. Sim, podemos criar vários init diferentes numa mesma classe. Mas infelizmente não podemos definir um nome diferente para cada um deles, apenas alterar quais parâmetros vamos usar.

Podemos criar um init sem o parâmetro idade, pois se for um filhote já saberemos que sua idade será zero.

class Animal {
var nome: String
var idade: Int
let especie: String

//primeiro init: recebe nome, idade e especie
init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
//segundo init: recebe nome e especie. note que ele não tem a idade como parâmetro
init(nome: String, especie: String) {
self.nome = nome
//definimos que a idade inicial será zero
self.idade = 0
self.especie = especie
}

}

Agora, quando formos criar o objeto da classe Animal, teremos duas opções de init: com ou sem o parâmetro idade.

O próprio Xcode nos indica que podemos criar o objeto gatoFilhote de duas formas:

Podemos criar apenas com os parâmetros nome e espécie, ou com os parâmetros nome, idade e espécie. Vamos criar o gatoFilhote usando o segundo Init (sem idade):

class Animal {
var nome: String
var idade: Int
let especie: String

//primeiro init: recebe nome, idade e especie
init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
//segundo init: recebe nome e especie. note que ele não tem a idade como parâmetro
init(nome: String, especie: String) {
self.nome = nome
self.idade = 0
self.especie = especie
}

}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus")
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus")
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo")
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla")
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus")

//criando o gato filhote. note que nao usamos a idade
var gatoFilhote = Animal(nome: "Zuri", especie: "Felis catus")

print(gatoFilhote.idade)
//Saída: 0

Podemos criar diversos inits para diversos propósitos diferentes.

Classes também são tipos

Digamos que agora queremos agrupar todos os animais que criamos. Quando pensamos em agrupar, podemos pensar num array. Mas será que podemos criar um array de objetos de uma classe?

Sim, podemos, pois classes também são tipos. Para fazer isso, vamos criar uma variável zoologico que terá o tipo array de Animal (utilizando Animal entre colchetes) e definimos quais elementos estarão dentro desse array:

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus")
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus")
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo")
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla")
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus")

//criando a array de animais:
var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]

Agora imagine que queremos printar uma lista com todos os nomes de animais que estão no zoologico. E vem agora uma grande vantagem do uso de classes: todos os objetos têm o mesmo atributo: nome. Ou seja, se conseguirmos obter cada objeto presente no array zoologico, basta acessar seu atributo nome e printá-lo.

Podemos fazer isso utilizando um FOR, em que cada elemento de zoologico é atribuído a variável animal. Então printamos o valor de animal.nome.

class Animal {
var nome: String
var idade: Int
let especie: String

init(nome: String, idade: Int, especie: String) {
self.nome = nome
self.idade = idade
self.especie = especie
}
}
var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus")
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus")
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus")
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo")
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla")
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus")
var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]
for animal in zoologico {
print(animal.nome)
}
/*
Saída:
Cookie
Matilda
Nina
Simba
Fred
Bob
*/

Esse loop FOR acima é chamado também de for in. Usualmente, um FOR define uma variável numérica inicial (digamos: i = 0) e é incrementada para obter cada posição do array. Ex: animais[i] para i = 2 retornaria o gato3 (terceiro elemento da array animais).

Ao invés disso, o for in permite definir uma variável “animal” que obtém cada elemento do array e permite fazer operações diretamente nela. Ele tem o comportamento de passar em cada um do nossos elementos do array. Em Swift você pode fazer iteração de um array de diferentes formas, essa é uma delas.

Modificando atributos

A partir de agora, podemos facilmente incrementar nossa classe Animal com outros atributos. Vamos lembrar o que queríamos no começo:

Você precisa anotar o nome, idade, espécie do animal, há quanto tempo vive no zoológico, número de patas, tipo de alimentação etc.

Então, basta criar mais atributos no interior da classe, que todos os objetos terão esses atributos:

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
}
}

Não se esqueça de atualizar a função init, pois todos os atributos novos devem ser inicializados na função init.

Voltamos ao nosso problema inicial:

Você vai precisar identificar necessidades desses animais, como calcular quando eles fazem aniversário (os animais deste zoológico adoram festas!), quanto de comida cada animal vai precisar, entre outras informações.

Vamos focar no problema do aniversário. Já temos a idade dos animais, porém não temos uma variável para saber quando nasceram. Vamos criar uma também.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

Criamos a variável anoNascimento.

Agora vamos tentar incrementar a idade de um animal. Note que o gorila tem 10 anos mas nasceu em 2013, ou seja, não fez aniversário ainda (considerando que estamos em 2024). Vamos retirar os outros animais por enquanto. Printamos essas informações abaixo:

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)

//Printando a idade e ano de nascimento do gorila
print(gorila.idade)
//10
print(gorila.anoNascimento)
//2013

Então, vamos fazer que o gorila Fred faça um aniversário. Podemos alterar o valor de um atributo, desde que ele seja declarado como variável. Vamos alterar o atributo gorila.idade:

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)

print(gorila.idade)
//10
print(gorila.anoNascimento)
//2013

//alterando a idade do gorila:
gorila.idade = 11

print(gorila.idade)
// 11

Se tentarmos alterar o ano de nascimento, que é uma constante, teremos um erro.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
// note que anoNascimento é declarado como let:
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)

print(gorila.idade)
//10
print(gorila.anoNascimento)
//2013

gorila.idade = 11

print(gorila.idade)
// 11

// tentando alterar uma constante:
gorila.anoNascimento = 2011
Teremos o erro acima.

Agora vamos imaginar que o ano novo chegou e queremos atualizar a idade de todos os animais (suponha que todos eles fazem aniversário juntos no dia 1 de janeiro).

Sabemos que toda vez que fazemos aniversário, incrementamos apenas um ano. E faremos isso para todos os animais. Será que podemos criar uma função genérica que atualiza a idade de um animal, independente de qual seja?

Métodos

Podemos criar uma função dentro da classe Animal que faz isso para nós. Funções dentro de classes são chamadas de métodos, e permitem lidar diretamente com os atributos da classe. Além disso, métodos são funções que podem ser utilizadas em todos os objetos de uma classe. Em geral, cada função realizar uma única tarefa. No nosso caso, vamos criar um método fezAniversário. Queremos incrementar a idade de nosso dado animal em 1.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func fezAniversario() {
self.idade = self.idade + 1
}
}

Note que criamos esse método da mesma forma que criamos uma função em Swift: utilizamos func, e definimos parâmetros dentro dos parênteses (nesse caso, não definimos nenhum). Abrimos chave e indicamos o que deve ser feito.

A diferença é essa função está dentro da classe Animal, e por isso é uma função exclusiva dessa classe, e que pode ser utilizada por qualquer objeto.

fezAniversario() incrementa em um o valor de self.idade (lembre-se que self indica que estamos obtendo o atributo da classe).

E como aplicamos esse método em um dos nossos objetos?

Fazemos de maneira similar a quando mexemos com os atributos de uma classe.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func fezAniversario() {
self.idade = self.idade + 1
}
}

var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)

print(gorila.idade)
//10

//chamamos o método fezAniversario, que aumentará a idade do gorila em 1
gorila.fezAniversario()

print(gorila.idade)
//11

Note que quando acessamos um atributo, usamos apenas o nome do atributo ("gorila.idade"), sem usar parênteses ao fim. Ao chamar um método, abrimos e fechamos parênteses ("gorila.fezAniversario()")para indicar que é uma função. Além disso, é dentro dos parênteses que passamos os parâmetros da função, se houver.

Funcionou para o gorila. E se quisermos fazer isso para todos os animais?

Podemos criar um loop for in e chamar o método fezAniversario para cada animal do zoológico.

O código a seguir está bem extenso, mas apenas redeclaramos os animais.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func fezAniversario() {
self.idade = self.idade + 1
}
}

var gato1 = Animal(nome: "Cookie", idade: 5, especie: "Felis catus", chegadaZoo: 2023, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gato2 = Animal(nome: "Matilda", idade: 7, especie: "Felis catus", chegadaZoo: 2020, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2017)
var gato3 = Animal(nome: "Nina", idade: 3, especie: "Felis catus", chegadaZoo: 2024, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2021)
var leao1 = Animal(nome: "Simba", idade: 5, especie: "Panthera leo", chegadaZoo: 2022, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)
var orangotango = Animal(nome: "Bob", idade: 11, especie: "Pongo pygmaeus", chegadaZoo: 2018, numeroPatas: 4, tipoAlimentacao: "onivoro", anoNascimento: 2013)

var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]

print(gorila.idade)
gorila.fezAniversario()

print(gorila.idade)
//11

for animal in zoologico {
print("O animal \(animal.nome) tem \(animal.idade) anos")
animal.fezAniversario()
print("Agora o animal \(animal.nome) fez aniversário e tem \(animal.idade) anos!!")
}

/*
O resultado no console será:
O animal Cookie tem 5 anos
Agora o animal Cookie fez aniversário e tem 6 anos!!
O animal Matilda tem 7 anos
Agora o animal Matilda fez aniversário e tem 8 anos!!
O animal Nina tem 3 anos
Agora o animal Nina fez aniversário e tem 4 anos!!
O animal Simba tem 5 anos
Agora o animal Simba fez aniversário e tem 6 anos!!
O animal Fred tem 11 anos
Agora o animal Fred fez aniversário e tem 12 anos!!
O animal Bob tem 11 anos
Agora o animal Bob fez aniversário e tem 12 anos!!
*/

Podemos pensar nisso de outra forma também. Sabemos o ano de nascimento de cada animal. Se tivermos o ano atual (desconsiderando dia e mês de nascimento) podemos calcular automaticamente a idade de cada animal.

Podemos criar outro método em que passamos o ano atual como parâmetro e atualizamos a idade dele baseado no seu ano de nascimento.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func fezAniversario() {
self.idade = self.idade + 1
}

// criamos aqui outro método: atualizarIdade, que recebe um Int como parâmetro
func atualizarIdade(anoAtual: Int) {
idade = anoAtual - anoNascimento
}
}

Note que definimos o método atualizarIdade com o parâmetro anoAtual como um Int, igual como fazemos com uma função normal.

Também note que apesar de usarmos os atributos da classe no método atualizarIdade, não precisamos usar o “self” sempre, já que não há ambiguidade no nome das variáveis. Mas lembrando: pode ser uma boa prática usar self para indicar que estamos lidando com os atributos.

Agora vamos aplicar o método atualizarIdade() para o gorila.

class Animal {
var nome: String
var idade: Int
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func fezAniversario() {
self.idade = self.idade + 1
}

func atualizarIdade(anoAtual: Int) {
idade = anoAtual - anoNascimento
}
}

var gorila = Animal(nome: "Fred", idade: 10, especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)


print(gorila.idade)
//10
print(gorila.anoNascimento)
//2013

// dizemos que o ano atual é 2030 e o gorila terá sua idade atualizada
gorila.atualizarIdade(anoAtual: 2030)

print(gorila.idade)
//17

Chamamos o método animal.atualizarIdade() e colocamos o anoAtual como 2030. Assim, calculamos a idade do animal.

Também podemos pensar de outra maneira. E se não precisarmos toda vez incrementar a idade de cada animal mas sim calculá-la automaticamente?

Variáveis Computáveis

Variáveis computáveis são atributos de uma classe que dependem de outros atributos da classe para se obter seu valor. No nosso caso, podemos definir idade como uma variável computável: ela depende do ano atual e do ano de nascimento do animal.

Para isso, definimos o tipo de idade e abrimos chaves. Semelhante a uma função, dizemos que idade vai retornar o valor de anoAtualanoNascimento.

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, idade: Int, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.idade = idade
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

O código acima resultará num erro. Cuidado! Agora que idade é uma variável computável, ela não precisa (e não pode!) ser inicializada na função de init. Além disso, seu valor não pode ser alterado diretamente ao longo do código (exceto com a utilização de gets e sets, mas não vamos entrar nisso neste guia).

Vamos corrigir o erro, retirando idade da função init:

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}
}

var gato1 = Animal(nome: "Cookie", especie: "Felis catus", chegadaZoo: 2023, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gato2 = Animal(nome: "Matilda", especie: "Felis catus", chegadaZoo: 2020, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2017)
var gato3 = Animal(nome: "Nina", especie: "Felis catus", chegadaZoo: 2024, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2021)
var leao1 = Animal(nome: "Simba", especie: "Panthera leo", chegadaZoo: 2022, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gorila = Animal(nome: "Fred", especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)
var orangotango = Animal(nome: "Bob", especie: "Pongo pygmaeus", chegadaZoo: 2018, numeroPatas: 4, tipoAlimentacao: "onivoro", anoNascimento: 2013)
var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]
print(anoAtual)
//2026
print(gorila.anoNascimento)
//2013
print(gorila.idade)
//13
anoAtual = 2030
print(gorila.idade)
//17

Lembre-se de retirar também a idade dos parâmetros dos objetos.

Agora podemos apenas alterar a variável anoAtual diretamente e todos os animais terão suas idades modificadas.

Imagine agora que queremos alimentar esses animais. Para isso, precisamos saber o tipo de alimentação de cada um, para saber se damos carne, folhas, frutas, ou um pouco de tudo. Podemos pensar em criar um método alimentar() para cada tipo de alimentação: Herbívoro, Carnívoro ou Onívoro. Precisaremos saber o tipo de alimentação daquele animal e depois exibimos uma mensagem do que cada animal gosta.

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func alimentar() {
if (tipoAlimentacao == "carnivoro") {
print("Este animal adora um bife")
} else if (tipoAlimentacao == "herbivoro") {
print("Este animal adora alface")
} else if (tipoAlimentacao == "onivoro") {
print("Este animal come qualquer coisa")
}
}
}

Criamos o método de alimentar. Vamos aplicá-lo nos objetos que criamos:

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func alimentar() {
if (tipoAlimentacao == "carnivoro") {
print("Este animal adora um bife")
} else if (tipoAlimentacao == "herbivoro") {
print("Este animal adora alface")
} else if (tipoAlimentacao == "onivoro") {
print("Este animal come qualquer coisa")
}
}
}
var gato1 = Animal(nome: "Cookie", especie: "Felis catus", chegadaZoo: 2023, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gato2 = Animal(nome: "Matilda", especie: "Felis catus", chegadaZoo: 2020, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2017)
var gato3 = Animal(nome: "Nina", especie: "Felis catus", chegadaZoo: 2024, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2021)
var leao1 = Animal(nome: "Simba", especie: "Panthera leo", chegadaZoo: 2022, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2019)
var gorila = Animal(nome: "Fred", especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, tipoAlimentacao: "herbivoro", anoNascimento: 2013)
var orangotango = Animal(nome: "Bob", especie: "Pongo pygmaeus", chegadaZoo: 2018, numeroPatas: 4, tipoAlimentacao: "onivoro", anoNascimento: 2013)

var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]

gato1.alimentar()
//Este animal adora um bife
gorila.alimentar()
//Este animal adora alface
orangotango.alimentar()
//Este animal come qualquer coisa

Essa é uma forma boa de criar um método que varia de acordo com o tipo de animal. Mas e se quisermos criar mais métodos e atributos específicos para carnívoros, herbívoros e onívoros?

Podemos chegar num ponto em que vale mais a pena criar Classes específicas para cada um deles, cada uma com seus métodos e atributos próprios. Porém, se quisermos que eles ainda sejam animais e tenham os métodos e atributos que já criamos? Teremos que repetir todos os atributos e métodos criados para a classe Animal, em cada uma delas? Isso gastaria tempo e teríamos muito código repetido.

Será que tem alguma forma de criarmos uma classe que também compartilha atributos com outra classe “superior”?

É daí que vem o conceito de Herança.

Herança

Herança é um princípio da Orientação a Objetos que permite que Classes mãe “transmitam” suas características para as Classes "filha". Essas características seriam os atributos e os métodos. Assim, diferentes classes podem compartilhar métodos e atributos, desde que sejam filhas da mesma classe "mãe".

Usamos o termo “mãe” e “filha”, porém classes “mãe” são chamadas de superclasses (ou classe base), enquanto classes filhas são “subclasses” (ou classes especializadas).

O objetivo da herança é justamente poder transmitir e reutilizar métodos e atributos de outras classes para criar uma classe nova, porém mais específica, como dito, mais especializada.

Vamos fazer isso primeiro criando classes específicas para os tipos de alimentação: Onívoro, Carnívoro e Herbívoro.

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func alimentar() {
if (tipoAlimentacao == "carnivoro") {
print("Este animal adora um bife")
} else if (tipoAlimentacao == "herbivoro") {
print("Este animal adora alface")
} else if (tipoAlimentacao == "onivoro") {
print("Este animal come qualquer coisa")
}
}


}

class Carnivoro {
var quantidadeCarne: Int
}
class Onivoro {
var alimentoPreferido: String
}
class Herbivoro {
var quantidadeFolha: Int
}

Este código, no formato que está, ainda está com erro pois não criamos inicializadores (inits) para as novas classes, mas chegaremos lá.

Temos classes para cada tipo de animal. Porém, um objeto da classe Carnívoro também pode ser um Animal. Podemos então dizer que Carnívoro HERDA da classe Animal, ou seja, Carnívoro é subclasse de Animal. Indicamos isso no código utilizando dois pontos (:) e colocando após eles o nome da superclasse.

var anoAtual = 2026

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func alimentar() {
if (tipoAlimentacao == "carnivoro") {
print("Este animal adora um bife")
} else if (tipoAlimentacao == "herbivoro") {
print("Este animal adora alface")
} else if (tipoAlimentacao == "onivoro") {
print("Este animal come qualquer coisa")
}
}


}
// definindo as classes:
class Carnivoro: Animal {
var quantidadeCarne: Int
}
class Onivoro: Animal {
var alimentoPreferido: String
}
class Herbivoro: Animal {
var quantidadeFolha: Int
}

O código terá erro, pois, novamente, ainda não criamos inicializadores. Mas agora pense: se Carnívoro, Onívoro e Herbívoro são classes filhas de Animal, então elas terão os mesmos atributos que a classe Animal tem. Além disso, cada classe ainda define atributos novos: quantidadeCarne, alimentoPreferido, quantidadeFolha. Esses atributos também terão que ser inicializados quando criarmos os inits de cada classe. E como fazemos isso?

Vamos começar com a classe Carnívoro e depois fazemos para as outras.

Primeiro, criaremos uma função init que terá como parâmetros todos os atributos da classe mãe Animal e o seu próprio atributo quantidadeCarne.

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, tipoAlimentacao: String, anoNascimento: Int, quantidadeCarne: Int) {

}
}

Ainda teremos erro pois não passamos os parâmetros aos atributos da classe.

Primeiro, vamos passar o valor do parâmetro quantidadeCarne para o atributo quantidadeCarne da classe Carnívoro:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, tipoAlimentacao: String, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

}

Se você tentar fazer o mesmo para os atributos da classe mãe, terá um erro. Por exemplo, vamos tentar passar o parâmetro nome para self.nome:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, tipoAlimentacao: String, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne
self.nome = nome


}
}

Precisamos achar outra forma de passar os parâmetros dessa classe para a classe mãe. Para isso, usaremos o termo super para indicar que estamos lidando com a uma filha da superclasse, ou seja, ela também tem as propriedades que sua mãe tem. Assim, vamos transmitir os valores da função init de Carnívoro chamando o init da superclasse Animal e passando esses valores como parâmetros.

Atenção, o inicializador da nossa super sempre deve vir depois das novas propriedades que estamos especializando com a nossa subclasse.

Chamaremos o init da superclasse Animal usando super.init:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, tipoAlimentacao: String, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: tipoAlimentacao, anoNascimento: anoNascimento)
}
}

Perceba que chamamos o parâmetro tipoAlimentacao mesmo já sabendo que a classe é de um Carnívoro. Podemos então remover esse parâmetro e passar diretamente no super.init a string carnívoro para indicar o tipoAlimentacao. Podemos fazer isso já que sabemos que todo objeto de Carnívoro será um animal carnívoro.

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}
}

Vamos fazer o mesmo para as outras classes:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}
}

class Onivoro : Animal {
var alimentoPreferido: String

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, alimentoPreferido: String) {
self.alimentoPreferido = alimentoPreferido

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "onivoro", anoNascimento: anoNascimento)
}
}

class Herbivoro : Animal {
var quantidadeFolha: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeFolha: Int) {
self.quantidadeFolha = quantidadeFolha

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "herbivoro", anoNascimento: anoNascimento)
}
}

Agora podemos criar objetos dessas classes, que serão tanto Animais quanto Carnívoros ou Onívoros ou Herbívoros.

var gato1 = Carnivoro(nome: "Cookie", especie: "Felis catus", chegadaZoo: 2023, numeroPatas: 4, anoNascimento: 2019, quantidadeCarne: 200)
var gato2 = Animal(nome: "Matilda", especie: "Felis catus", chegadaZoo: 2020, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2017)
var gato3 = Animal(nome: "Nina", especie: "Felis catus", chegadaZoo: 2024, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2021)
var leao1 = Carnivoro(nome: "Simba", especie: "Panthera leo", chegadaZoo: 2022, numeroPatas: 4, anoNascimento: 2019, quantidadeCarne: 2000)
var gorila = Herbivoro(nome: "Fred", especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, anoNascimento: 2013, quantidadeFolha: 10)
var orangotango = Onivoro(nome: "Bob", especie: "Pongo pygmaeus", chegadaZoo: 2018, numeroPatas: 4, anoNascimento: 2013, alimentoPreferido: "frutas")

var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]

Note que não colocamos o tipo de alimentação como argumento na chamada função de init quando estamos criando um tipo Carnivoro, Herbivoro ou Onívoro.

Podemos criar tanto animais através da classe Animal (como gato2 e gato3) quanto animais específicos de cada tipo de alimento. Porém, eles têm diferentes inits.

Vamos voltar agora ao método de alimentar() que criamos na classe Animal e testá-lo nos novos objetos que criamos:

class Animal {
var nome: String
let especie: String
let chegadaZoo: Int
let numeroPatas: Int
let tipoAlimentacao: String
let anoNascimento: Int

var idade: Int {
return anoAtual - self.anoNascimento
}

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int,
tipoAlimentacao: String, anoNascimento: Int) {
self.nome = nome
self.especie = especie
self.chegadaZoo = chegadaZoo
self.numeroPatas = numeroPatas
self.tipoAlimentacao = tipoAlimentacao
self.anoNascimento = anoNascimento
}

func alimentar() {
if (tipoAlimentacao == "carnivoro") {
print("Este animal adora um bife")
} else if (tipoAlimentacao == "herbivoro") {
print("Este animal adora alface")
} else if (tipoAlimentacao == "onivoro") {
print("Este animal come qualquer coisa")
}
}


}

//... tem mais código aqui ...
gato1.alimentar()
//Este animal adora um bife
gorila.alimentar()
//Este animal adora alface
orangotango.alimentar()
//Este animal come qualquer coisa

A função alimentar() que declaramos em Animal é transmitida para as classes filha por Herança, por isso gato1, gorila e orangotango podem utilizar esse mesmo método, mesmo sendo das classes Carnívoro, Herbívoro e Onívoro.

Note que o método alimentar() verifica qual tipo de alimentação aquele animal tem, e cria uma mensagem própria para ele. Mas agora temos classes específicas para cada tipo de alimentação e já sabemos de cara qual é a alimentação daquele animal. Então, será que conseguimos criar um método dentro das próprias classes Carnívoro, Herbívoro e Onívoro, único para cada classe mas que ainda tenham o mesmo nome alimentar()?

Polimorfismo

Polimorfismo vem do grego e significa poli = muitas e morfismo = formas, muitas formas. Ou seja, temos uma mesma coisa com diversas formas diferentes. Pense no nosso zoológico: alimentar é uma mesma ação, mas varia de acordo com o animal e seu tipo de alimentação. Então, podemos fazer um polimorfismo no método alimentar() para que ele esteja presente de forma diferente em cada classe Carnívoro, Herbívoro e Onívoro.

Por mais que tenham o mesmo nome, cada classe terá sua própria implementação do método alimentar().

Lembre-se que através da herança, cada uma dessas classes já contém o método alimentar(). Porém ela está declarada de forma única na classe mãe Animal. Vamos agora criar métodos alimentar() específicos para cada classe, usando o atributo novo que criamos em cada uma delas. Vamos começar pela classe Carnívoro.

Podemos tentar fazer assim:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

func alimentar() {
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}

Porém, teremos um erro:

O método alimentar() já existe na classe Animal, e estamos criando-o novamente na classe filha Carnívoro, mudando sua forma. Temos duas implementações diferentes e nosso compilador não sabe qual utilizar.

Para indicar que queremos utilizar o método novo, precisamos sobrescrever ou sobrepor o novo método alimentar() da classe Carnívoro no método alimentar() da classe Animal.

Mas lembre-se: A partir do código acima, fica evidente que essa sobreposição acontece apenas para os objetos que são da classe carnívoro. Qualquer outro animal ainda executará o mesmo método padrão presente na classe Animal.

Usaremos uma palavra chamada “override”, já indicada pelo erro acima. Essa palavra mostra ao compilador que ao chamarmos a função alimentar() num Animal da classe Carnívoro, ele vai utilizar a versão de alimentar() escrita na classe Carnívoro, ou seja, exibirá a mensagem com a quantidade de carne que ele come:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}

Podemos fazer o mesmo com as outras classes:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}

class Onivoro : Animal {
var alimentoPreferido: String

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, alimentoPreferido: String) {
self.alimentoPreferido = alimentoPreferido

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "onivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
print("Este animal adora \(alimentoPreferido)!")
}
}
class Herbivoro : Animal {
var quantidadeFolha: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeFolha: Int) {
self.quantidadeFolha = quantidadeFolha

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "herbivoro", anoNascimento: anoNascimento)


}
override func alimentar() {
print("Este animal come \(quantidadeFolha) folhas de alface!")
}
}

Vamos testar esse novo método alimentar() nos animais que criamos anteriormente:

var gato1 = Carnivoro(nome: "Cookie", especie: "Felis catus", chegadaZoo: 2023, numeroPatas: 4, anoNascimento: 2019, quantidadeCarne: 200)
var gato2 = Animal(nome: "Matilda", especie: "Felis catus", chegadaZoo: 2020, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2017)
var gato3 = Animal(nome: "Nina", especie: "Felis catus", chegadaZoo: 2024, numeroPatas: 4, tipoAlimentacao: "carnivoro", anoNascimento: 2021)
var leao1 = Carnivoro(nome: "Simba", especie: "Panthera leo", chegadaZoo: 2022, numeroPatas: 4, anoNascimento: 2019, quantidadeCarne: 2000)
var gorila = Herbivoro(nome: "Fred", especie: "Gorilla gorilla", chegadaZoo: 2019, numeroPatas: 4, anoNascimento: 2013, quantidadeFolha: 10)
var orangotango = Onivoro(nome: "Bob", especie: "Pongo pygmaeus", chegadaZoo: 2018, numeroPatas: 4, anoNascimento: 2013, alimentoPreferido: "frutas")


var zoologico: [Animal] = [gato1, gato2, gato3, leao1, gorila, orangotango]
gato1.alimentar()
//Este animal come 200 gramas de carne!
gorila.alimentar()
//Este animal come 10 folhas de alface!
orangotango.alimentar()
//Este animal adora frutas!
gato2.alimentar()
//Este animal adora um bife

Perceba que agora gato1, gorila e orangotango exibem resultados diferentes, mesmo sendo a mesma função alimentar(). Porém, gato2 foi declarado como Animal, por isso retorna a mensagem padrão de alimentar() dos carnívoros criada na classe Animal.

E se quisermos que os métodos de cada classe também exibam a mensagem padrão de alimentar() que criamos para a classe Animal? Podemos usar novamente o termo “super”.

O termo super é uma forma de acessar os métodos da superclasse.

Chamamos o método super.alimentar(). Esse método vai exibir a mensagem criada na superclasse, e depois vai exibir as linhas de código específicas da classe Carnívoro.

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
super.alimentar()
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}

Se chamarmos gato1.alimentar(), ele vai retornar a mensagem padrão para animais carnívoros da superclasse e a mensagem específica da classe Carnívoro:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
super.alimentar()
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}
//...

gato1.alimentar()
//Este animal adora um bife
//Este animal come 200 gramas de carne!

Podemos fazer o mesmo para as outras classes:

class Carnivoro : Animal {
var quantidadeCarne: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeCarne: Int) {

self.quantidadeCarne = quantidadeCarne

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "carnivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
super.alimentar()
print("Este animal come \(quantidadeCarne) gramas de carne!")
}
}

class Onivoro : Animal {
var alimentoPreferido: String

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, alimentoPreferido: String) {
self.alimentoPreferido = alimentoPreferido

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "onivoro", anoNascimento: anoNascimento)
}

override func alimentar() {
super.alimentar()
print("Este animal adora \(alimentoPreferido)!")
}
}
class Herbivoro : Animal {
var quantidadeFolha: Int

init(nome: String, especie: String, chegadaZoo: Int, numeroPatas: Int, anoNascimento: Int, quantidadeFolha: Int) {
self.quantidadeFolha = quantidadeFolha

super.init(nome: nome, especie: especie, chegadaZoo: chegadaZoo, numeroPatas: numeroPatas, tipoAlimentacao: "herbivoro", anoNascimento: anoNascimento)


}
override func alimentar() {
super.alimentar()
print("Este animal come \(quantidadeFolha) folhas de alface!")
}
}

Essa é uma visão geral dos principais tópicos de Orientação a Objetos em Swift.
Espero que tenha contribuído para seus estudos.
Obrigado se chegou ate aqui!

--

--