Classes, Objetos e Traits em Scala
Esse artigo também pode ser chamado “Minha saga aprendendo Scala - Parte 4”
- Parte 1 - Minha saga aprendendo Scala
- Parte 2 - Funções em String
- Parte 3 - Classes em Scala: métodos e definições de atributos
Novas sugestões!
Bom… pelo menos uma pessoa tem lido esses artigos sobre Scala! O Bram Elfrink me mandou um link sobre uma série de aulas de Scala dadas pelo Twitter que parece muito bom! Vai lá conferir! Obrigada, Bram!
Objetos
No último post, eu mostrei como trabalhar com atributos e métodos de classe, especialmente como limitar o uso deles. Mas classes são apenas um tipo de objeto em Scala. Como discutimos na Parte 2, quando você quer rodar algo em Scala, você cria um object
não uma classe. Então qual a diferença entre um e outro?
Quando criamos uma nova classe (com class
), podemos instanciá-la, da seguinte forma:
class Pessoa(val nome: String)
val maria = new Pessoa("Maria")
Se eu criar uma nova pessoa, isso vai criar uma nova instância:
val joao = new Pessoa("Joao")
A instância joao
não é a mesma que a instância maria
, porque cada vez que chamamos new Pessoa
, nós criamos uma instância nova.
Em Scala, um object
sempre vai criar uma única instância, não importa quantas vezes você a instancia. E é por isso que o object
é chamado um singleton.
Na verdade, nem precisamos da palavra new
antes de uma instanciar um objeto, porque não precisamos instanciar um objeto de forma alguma! Quando você cria um object
, ele vai ser instantaneamente instanciado, então você pode simplesmente usá-lo.
Por causa dessa característica, um object
em Scala não pode ter um construtor da mesma forma que uma classe, isto é, não pode receber nenhum parâmetro.
Nos exemplos a seguir você vai ver que ambos os valores maria
e joao
são a mesma instância.
object Pessoa
val maria = Pessoa // mesmo que `new Pessoa`
val joao = Pessoa // mesmo que `new Pessoa`
println(maria == joao) // true
Já que um object
é sempre uma instância - e apenas uma instância - você sempre pode acessar os atributos e métodos diretamente:
object Casa {
val tem_cozinha: Boolean = true
}
println(Casa.tem_cozinha) // true
Uma outra coisa interessante é que você consegue criar, num mesmo arquivo, uma classe com o mesmo nome de um object
.
O arquivo a seguir funciona perfeitamente:
object Pessoa {
numero_de_olhos = 2
}
class Pessoa(nome: String)
val pessoa = Pessoa // object
val maria = new Pessoa("Maria") // class
Overloading
No último artigo, eu mostrei como podemos criar uma classe com múltiplos construtores. Nós podemos simplesmente escrever dois construtores com diferentes parâmetros. Essa funcionalidade se chama overloading e funciona também para qualquer método que você queira:
class Pessoa(nome: String) {
def cumprimentar(nome: String) = println(s"${this.nome} diz: Olá, $nome")
// overloading
def cumprimentar = println(s"Olá! Me chamo $nome")
val joao = new Pessoa("Joao")
println(joao.cumprimentar) // Olá! Me chamo Joao
println(joao.cumprimentar("Maria")) // Joao diz: Olá, Maria
Abstrações!
Agora vemos como classes e object
s funciona, é hora dee entender como usar abstrações de objetos para serem herdadas em uma classe. Uma das coisas que estimula o uso de herança de classes é o conceito de reusabilidade, permitindo que classes reusem atributos e métodos que estão disponíveis em outras classes, evitando duplicação de código!
Classe abstrata
Uma classe abstrata é uma classe como qualquer outra mas escrita com um abstract
na frente da declaração. Ela também pode ter um construtor e receber parâmetros (como qualquer classe) e ter métodos e atributos que podem ser sobrescritos:
abstract class Animal {
val tipo: String
def come: Unit
}
class Cachorro extends Animal {
override val tipo: String = "Cachorro"
override def come: Unit = ???
}
val animal = new Animal // Erro! class Animal is abstract; cannot be instantiated
val cachorro = new Cachorro // Works!
E você também pode colocar um parâmetro obrigatório na classe abstrata, adicionando um construtor dessa forma:
abstract class Animal(val identificacao: String)
class Cachorro(val nome: String) extends Animal(nome)
No entanto, tem um problema! Você só pode estender 1 classe abstrata em uma classe regular. Portanto, nossa classe Cachorro
não pode extender nenhuma outra classe além de Animal
. Essa restrição da linguagem, de apenas permitir que uma classe herde de apenas uma outra classe, se chama herança única (single inheritance). Eu tentei entender por que uma linguagem permitiria apenas uma única herança e eu achei uma explicação para C# (livre tradução):
C# não suporta herança múltipla porque ela adiciona muita complexidade ao C# enquanto provê pouco benefício.
Traits
No livro Programming in Scala Primeira Edição, traits é definido como (livre tradução):
Traits são uma unidade fundamental de reutilização de código em Scala. [1]
Semelhante às classes abstratas, os traits são uma forma de reunir funções e métodos em um único lugar e reutilizá-los em outras classes. Você pode definir um trait da seguinte forma:
trait Carnivoro {
def perseguirPresa: Unit
}
Mas, até onde eu pude ver, existem duas diferenças principais entre traits e classes abstratas:
- Traits não têm um construtor, então eles não podem receber parâmetros
- Uma única classe pode herdar vários traits
Portanto, podemos ter coisas como:
trait Animal {
val estaVivo: Boolean = true
}
trait Carnivoro {
val tipo: String = "Carnivoro"
def come(animal: String): String = s"${this.tipe} come $animal"
}
trait SangueFrio {
val ambiente: String = "Calor"
}
class Crocodilo extends Animal with Carnivoro with SangueFrio {
override val tipo: String = "Crocodilo"
}
class Cachorro extends Animal with Carnivoro {
override val tipo: String = "Cachorro"
}
val cachorro = new Cachorro
val croc = new Crocodilo
println(croc.come(cachorro.tipo)) // Crocodilo come Cachorro
Escolhendo entre uma classe abstrata e trait
Ok, Maravilha! E agora? Qual dos dois eu uso?
Nosso amigo Stack Overflow tem uma longa explicação sobre qual usar. Mas o resumo é: se não tiver certeza, use traits. Em tradução livre:
Se você não tem certeza, mesmo considerando o que foi falado, comece criando um trait. Você sempre pode mudar de ideia depois, e em geral usar traits você deixa mais opções abertas.
Por hoje é só! Demorei um pouco e não sou estou tão conseguindo me concentrar tanto no curso de Scala. Houve um capítulo sobre Generics que acabou com a minha motivação, mas eu não desisti! Com sorte, escreverei sobre isso um dia, mas quem sabe !? Nunca imaginei chegar à Parte 4! 🙂
❤ Abraço! Letícia