por Leandro Cissoto
Chegamos, enfim, ao nosso último artigo — já com aquela dor no coração de despedida — sobre Swift na parceria Quaddro + MacMagazine. Vamos fechar esta série de tutoriais com Orientação a Objetos na Swift. Veremos seus principais conceitos e vamos praticar um pouco com um exercício guiado.
Lembramos que, para testar os conceitos aqui apresentados, recomendamos o uso do Xcode 6 ou superior. Se você não possui um Mac ou não quer instalar o Xcode, é possível utilizar uma ferramenta online que permite escrever em Swift diretamente pelo navegador.
Teoria
A Programação Orientada a Objetos foi uma grande mudança de paradigma no desenvolvimento de aplicações, trazendo estruturas semelhantes às do mundo real para dentro da programação. Utilizando esse paradigma, podemos construir aplicações robustas e mais eficientes, gerando menos esforço para manutenção e mais reutilização de códigos.
CLASSES E OBJETOS
O primeiro e um dos mais importantes conceitos a serem entendidos é o de Classes, que podem ser compreendidas como abstrações de objetos que possuem características semelhantes. Um objeto, por sua vez, é quando instanciamos, ou seja, damos uma identidade/especificidade para uma classe.
Objetos podem armazenar estados através das suas propriedades, executar ações através de métodos e se relacionar além de enviar mensagens a outros objetos. Podemos dizer que criar um objeto é dar vida a uma classe.
A sintaxe para a criação de uma classe é a seguinte:
class NomeDaClasse [: NomeDaSuperclasse] { }
Só para explicar, a parte que está entre colchetes é opcional e explicaremos mais sobre superclasse quando falarmos de herança.
A primeira vez que me deparei com esses termos — classes, objetos, propriedades, herança, etc. — admito que fiquei algum tempo fritando os neurônios para achar algum sentido, e na verdade é muito mais simples do que eu imaginava.
Vamos analisar a frase abaixo do ponto de vista da POO:
Gervásio é uma pessoa que possui o RG 12345.
Se formos levar a frase acima para o paradigma da orientação a objetos, podemos dizer que Gervásio é um objeto da classe pessoa, ou seja, estou dando características específicas — o nome Gervásio e o RG — para para algo abstrato (pessoa).
Agora, transportando para Swift, ficaria assim:
class Pessoa{ var nome: String = "" var rg: String = "" }
Essas variáveis dentro da classe são chamadas de propriedades, que veremos adiante. Vamos agora criar um objeto da classe Pessoa:
var gervasio: Pessoa = Pessoa()
Agora a variável gervasio
é um objeto da classe Pessoa
, e por esta razão tem acesso a suas propriedades e métodos.
PROPRIEDADES E MÉTODOS
Agora que temos um entendimento mais claro do que é uma classe, podemos falar sobre o que são propriedades e métodos.
Propriedades, também chamadas de atributos, compõem o conjunto de características que uma classe tem, e métodos constituem o que uma classe faz. Do ponto de vista técnico, ao declararmos variáveis ou constantes dentro do escopo de uma classe, estamos definindo as suas propriedades. Ao declararmos funções dentro do escopo da classe, estamos definindo métodos.
Para acessar propriedades e métodos de uma classe, utilizamos .
depois de termos definido o objeto.
No exemplo abaixo vamos dar uma atualizada na nossa classe Pessoa
, adicionando novas propriedades a ela, e daremos um pouco mais de funcionalidades através dos métodos.
class Pessoa{ var nome: String = "" var rg: String = "" var idade: Int = 0 var peso: Float = 0.0 func andar(){ print("Começou a Andar!") } } var gervasio: Pessoa = Pessoa() //Defino as propriedades gervasio.nome = "Gervásio da Silva" gervasio.rg = "430287039" gervasio.idade = 70 gervasio.peso = 72.0 //Utilizo os métodos gervasio.andar()
ENCAPSULAMENTO
Através do encapsulamento podemos definir diferentes níveis de acesso para as classes, propriedades e métodos.
Utilizamos este conceito quando queremos definir como nossas classes, propriedades e métodos são acessados por outras classes ou objetos dentro da aplicação. Este conceito traz mais segurança e controle durante o desenvolvimento.
Existem três níveis de encapsulamento na Swift:
- public – Permite acesso a qualquer outro elemento.
- internal – Permite acesso apenas dentro da própria classe e nas classes herdeiras.
- private – Permite acesso apenas dentro da classe na qual foi declarada.
Um exemplo de encapsulamento é a variável saldoBancário
de um cliente de banco. Ela não pode ter um acesso público, senão qualquer parte do programa poderia mudar o seu valor. Neste caso, definimos a variável como privada para que seu valor seja alterado usando métodos que irão conter mecanismos de travas dependendo da operação.
Nota: por padrão, o nível de encapsulamento é internal.
class Pessoa { var nome: String = "Leandro" //Criamos a propriedade privada private var idade: Int = 28 //Criamos o método que altera a idade func mudarIdade(novaIdade: Int)->Void{ idade = novaIdade } func imprimeIdade()->Void{ println(idade) } } //Criamos o objeto da classe var humano : Pessoa = Pessoa() humano.idade = 35 //Resultado: erro //Faz a alteração da idade, usando o método da classe humano.mudarIdade(35) humano.imprimeIdade() //Resultado: 35
Nota: o encapsulamento do Swift funciona apenas se a classe e sua instância estiverem em arquivos separados.
MÉTODOS INICIALIZADORES
Podemos utilizar métodos inicializadores para serem executados no momento em que instanciamos nossa classe. Através deles, podemos atribuir valores às propriedades, executar métodos da classe ou da superclasse e realizar qualquer tipo de rotina necessária no momento da criação do objeto.
Esses métodos também são chamados de construtores, e em Swift utilizamos a palavra reservada init
para serem criados no contexto da classe.
//Criamos um classe com o métodos inicializador class Empresa{ var cnpj: String = String() var nomeFantasia: String = String() var faturamentoAnual: Double = Double() init(cnpj: String, nomeFantasia: String, faturamentoAnual: Double){ self.cnpj = cnpj self.nomeFantasia = nomeFantasia self.faturamentoAnual = faturamentoAnual println("Iniciando a classe Empresa: Nome: \(self.nomeFantasia)") } } //Crio um objeto e utilizo seu inicializador var petrobras: Empresa? = Empresa(cnpj: "3299420424-24", nomeFantasia: "BR", faturamentoAnual: 2_000_000)
willSet e didSet
Os observadores didSet
e willSet
provêm uma maneira de responder corretamente quando uma propriedade tem seu valor definido/alterado. O observador willSet
é chamado antes de o valor ser atribuído a uma propriedade, já o observador didSet
é chamado depois de uma propriedade ter recebido um valor.
Você pode estar se perguntando: “Onde vou usar esse negócio estranho?” Eu lhe respondo com um exemplo: vamos supor que toda vez que você usa um Array para preencher uma tableview. Você pode utilizar o didSet
por exemplo para, toda vez que houver alteração no conteúdo do Array, a tableview executar o método reloadData
, responsável por atualizar seu conteúdo.
class Abastecer{ var contador: Int = Int(){ willSet(novaContagem){ print("Abastecer \(novaContagem) litros") } didSet{ var novoValor = oldValue if contador > novoValor{ print("Abasteceu \(contador + novoValor) litros") } } } } let abastecer = Abastecer() abastecer.contador = 30 abastecer.contador = 50 abastecer.contador = 45
HERANÇA E POLIFORMISMO
A capacidade de uma classe de herdar as propriedades e métodos de outra classe é chamada de herança. Essa é uma das ferramentas mais poderosas da orientação a objetos, pois através dela podemos criar hierarquias e aumentamos o nível de abstração.
A herança nos permite muitas possibilidades durante o desenvolvimento através da transferência de propriedades e métodos da superclasse para suas subclasses. Veja o exemplo abaixo:
//Classe Pai / SuperClasse (Humano) class Humano { var nome: String = "" var idade: Int = 0 func andar(){ println("O humano está andando") } } //Classe Filha / SubClasse (Filha) class Atleta : Humano { } //Outra classe que herda as funcionalidade de Humano class Funcionario : Humano{ } //Crio um objeto da classe Humano let pessoa: Humano = Humano() pessoa.nome = "Roberto" println("O Nome da pessoa é \(pessoa.nome)") //Resultado: imprime "O Nome da pessoa é Danilo" //Crio um objeto da classe Atleta let maratonista: Atleta = Atleta() maratonista.nome = "Leandro" println("O Nome do atleta é \(maratonista.nome)") //Resultado: imprime "O Nome do atleta é Leandro" //Crio um objeto do tipo Funcionario let vendedor: Funcionario = Funcionario() vendedor.nome = "Gustavo" println("O nome do funcionário é \(vendedor.nome)") //Resultado: imprime "O Nome do funcionário é Gustavo"
A subclasse pode ter suas próprias propriedades e métodos, e estes não podem ser acessados pela superclasse, já que o fluxo da herança é sempre da superclasse para a subclasse. Veja o exemplo abaixo:
class Animal { var peso: Double = 0.0 var altura: Double = 0.0 func comer(){ println("O Animal está comendo") } } class Cachorro: Animal { var raca: String = "" func latir(){ println("O cachorro está latindo") } } //Crio um objeto da superclasse animal let gato: Animal = Animal() gato.peso = 15.5 gato.altura = 0.30 gato.comer() gato.latir() //Resultado: erro ao tentar executar o método latir, que é da subclasse //Crio um objeto da subclasse cachorro let boxer: Cachorro = Cachorro() boxer.peso = 45.2 boxer.altura = 1.00 boxer.raca = "Boxer" boxer.comer() boxer.latir() //Resultado: executa todos os métodos, tanto da classe "Cachorro" quanto da classe "Animal"
POLIFORMISMO
Outro conceito importante dentro do paradigma de orientação a objetos é o polimorfismo, que é a capacidade de uma subclasse sobrescrever métodos e propriedades de uma superclasse. Em Swift, utilizamos a palavra reservada override
antes do nome do método para dizermos que este está sendo alterando. A palavra override
irá verificar pelo compilador se existe na superclasse o respectivo método. Este processo é altamente importante para definição correta de um método herdado.
Veja o exemplo abaixo:
//Classe Pai / SuperClasse (Humano) class Humano { func andar() { println("Método andar na classe Humano") } } let humano : Humano = Humano() humano.andar() //Resultado: imprime "Método andar na classe Humano" //Classe Filha / SubClasse (Filha) class Atleta : Humano { override func andar() { println("Método andar na classe Atleta") } } let atleta: Atleta = Atleta() atleta.andar() //Resultado: imprime "Método andar da classe Atleta"
Para evitar que a herança substitua a rotina de programação de um método, subscripts ou propriedade, podemos utilizar a palavra reservada final
na declaração do método.
Veja o exemplo abaixo:
//Classe Pai / SuperClasse (Humano) class Humano { final func andar() { println("Método andar na classe Humano") } } //Classe Filha / SubClasse (Filha) class Atleta : Humano { override func andar() { println("Método andar na classe Atleta") } } let atleta: Atleta = Atleta() atleta.andar() //Resultado: erro
Veja que no exemplo acima não conseguiremos alterar o método andar()
, pois ele contém a palavra final
em sua estrutura e o compilador irá apresentar um erro quando tentarmos fazer a reescrita do método.
Exercício guiado
Agora que já vimos os principais conceitos para trabalharmos com Orientação a Objetos em Swift, vamos colocar a mão na massa em um exercício prático.
- Crie um novo arquivo chamado “POO” e deixe-o em branco.
- Aproveitando que no ano que vem teremos as Olimpíadas aqui no Brasil, e como sou um grande fã de artes marciais, vamos criar uma classe
Lutador
e duas classes herdeiras —Karateca
eJudoca
— para vermos melhor o conceito de herança e polimorfismo.
class Lutador{ } class Judoca : Lutador { } class Karateca: Lutador { }
- Vamos criar alguns métodos na classe
Lutador
. Lembrando que, como as classesJudoca
eKarateca
são herdeiras, podem fazer uso das propriedades e métodos de sua superclasse. Começaremos pelas propriedades. Por ser uma classe abstrata, devemos dar aoLutador
propriedades que também façam sentido para suas classes herdeiras. Podemos dizer, com certeza, que um lutador tem um nome, um sexo e que, para justificar ser um lutador, possui uma série de movimentos específicos de luta. Como são varios movimentos, podemos dizer que essa propriedade será um Array de String, para que o nosso lutador possa escolher entre uma coleção de golpes. Um lutador que se preze também cumprimenta seus adversários e luta. Então vamos criar estes métodos.
class Lutador{ var nome:String = String() var sexo: Character = " " var movimentos: Array<String> = Array() func cumprimentar(){ println("Cumprimento geral") } func lutar(){ println("Luta sem regras") } }
- Agora que nossa classe
Lutador
está pronta, vamos nos concentrar nas suas subclasses, começando pela classeJudoca
. Um judoca possui as mesmas características que um lutador, mais alguns diferenciais. Podemos dizer que nem todo tipo de luta possui um sistema de graduação baseado em faixas, mas o judô sim. Vamos criar essa propriedade, então. Outro detalhe relevante é que um judoca também possui um cumprimento específico — na verdade mais de um, mas para o exemplo usaremos só um, mesmo — e o judô tem algumas regras de luta que precisam ser respeitadas. Neste caso, não poderemos utilizar os métodos da superclasse porque se mostram inadequados para a realidade do judoca. Teremos, então, que utilizar o conceito de polimorfismo para alterar esses métodos. Para isso, utilizamos a palavraoverride
do método, e assim podemos sobrescrevê-lo. A classeJudoca
ficou assim:
class Judoca : Lutador { var faixa: String = String() override func cumprimentar(){ println("Tachi-rei") } override func lutar(){ println("Lutando nas regras do Judô") } }
- O mesmo acontece com a classe
Karateca
: ele possui um sistema de graduação específico, um cumprimento diferente e luta com regras diferentes. Como adicional, podemos criar a propriedadeestilos
, pois o caratê possui muitos estilos diferentes, como o Shotokan e o Wado-ryu por exemplo. Vamos criar essa propriedade como um Array de String, pois um carateca pode conhecer vários estilos. Nossa classeKarateca
fica assim:
class Karateca: Lutador { var faixa: String = String() var estilos: Array<String> = Array() override func cumprimentar(){ println("Oss") } override func lutar(){ println("Lutar nas regras do Karate") } }
Tudo junto fica assim:
class Lutador{ var nome:String = String() var sexo: Character = " " var movimentos: Array<String> = Array() func cumprimentar(){ println("Cumprimento geral") } func lutar(){ println("Luta sem regras") } } class Judoca : Lutador { var faixa: String = String() override func cumprimentar(){ println("Tachi-rei") } override func lutar(){ println("Lutando nas regras do Judô") } } class Karateca: Lutador { var faixa: String = String() var estilos: Array<String> = Array() override func cumprimentar(){ println("Oss") } override func lutar(){ println("Lutar nas regras do Karate") } }
- Vamos criar alguns objetos para testarmos as nossas classes.
//Criando um karateca 👊 let oyama: Karateca = Karateca() oyama.nome = "Masutatsu Oyama" oyama.sexo = "M" oyama.faixa = "Preta" oyama.estilos = ["Shotokan", "Goju-ryu", "Kyokushinkai"] oyama.movimentos = ["Ague-zuki", "Haito-uti", "Shuto-uti"] //Hora de o karateca lutar oyama.cumprimentar() oyama.lutar() //Criando um judoca 👊 let koga : Judoca = Judoca() koga.nome = "Toshihiko Koga" koga.sexo = "M" koga.faixa = "Preta" koga.movimentos = ["Nage-waza", "Te-waza", "Mae-sutemi-waza", "Osaekomi-waza"] //Hora de o judoca lutar koga.cumprimentar() koga.lutar() //Criando um lutador 👊 let mummy: Lutador = Lutador() mummy.nome = "Amanda Mummy" mummy.sexo = "F" mummy.movimentos = ["Dedo no olho", "Chute na canela", "Cruzado de direita"] //Hora de o lutador lutar mummy.cumprimentar() mummy.lutar()
Olhando para este exemplo, consigo pensar em um jogo de luta baseado em turnos em que cada movimento poderia receber um valor de ataque, e cada lutador poderia ter um nível de energia.
Deixo esta sugestão para vocês quebrarem a cabeça em cima dessa ideia. 😉
Desafio
Pedra, papel e tesoura
- Criar uma classe chamada de
Jogo
. - Criar uma variável que será uma String contendo a escolha do usuário.
- Criar uma variável que será uma String contendo a escolha do computador.
- Criar uma variável que será uma String contendo o resultado.
- Criar um
willSet
/didSet
na variável que contém a escolha do usuário. - No
willSet
, definir a escolha do computador. - No
didSet
, verificar o vencedor. - Criar um método chamado
regra
, que irá dizer o vencedor, lembrando que:- papel »» pedra
- pedra »» tesoura
- tesoura »» papel
Dica: pesquise por “arc4random_uniform” para realizar a escolha do computador.
É um desafio bem bacana que certamente abrirá a sua mente em relação ao uso de didSet
e willSet
.
· · ·
Lembramos que no nosso GitHub você encontra os exercícios de todos os artigos, bem como as soluções dos desafios. Lá também você encontrará outros exemplos com classes sobre temas que não abordei neste artigo, mas que certamente valem o estudo.
Ainda há muitas coisas sobre a Swift, como protocolos, generics, subscripts e outros temas que certamente valem a pena estudar. Qualquer dificuldade, dúvida ou sugestão, podem enviar um email para mm@quaddro.com.br ou escrever nos comentários abaixo. Terei o maior prazer em ajudá-los nessa jornada.
Gostaria de agradecer ao MacMagazine por permitir que estes artigos fossem realizados e por abrir novamente a oportunidade para que possamos compartilhar nosso conhecimento com seus leitores, fortalecendo ainda mais essa parceria que ainda vai gerar muitos outros frutos. 🙂
Um obrigado especial a vocês, leitores, que acompanharam as matérias e participaram nos comentários. Vocês foram a nossa grande motivação para esta série de artigos!
A Swift ainda vai dar muito o que falar, e as novidades não param por aqui: a Swift 2 está chegado e, com ela, mais um artigo com as principais novidades para vocês. Estude, faça exercícios, quebre a cabeça, mas principalmente, divirta-se com a Swift. Garanto que cada linha de código valerá a pena.
Até breve!