Scala - Orientação a Objetos, Classes e Interfaces
Tags: scala, functional, oop
Published: 2020-04-09 12:44:12

* [Classes Abstratas](#classes-abstratas)
* [Classes](#classes)
* [Objects](#objects)
* [Traits](#traits)
* [Tipos](#tipos)
  - [Nothing e Null](#nothing-null)
* [Polimorfismo](#polimorfismo)

Além de funcional, Scala também tem um pezinho na orientação objeto. Com isso, alguns conceitos já conhecidos pra quem vem de outras linguagens podem muito bem ser aplicados em Scala.

## Classes Abstratas <a name="classes-abstratas"></a>

São classes que podem conter membros cujo a implementação não está definida. Por esse motivo, uma instância de uma **Classe Abstrata** não pode ser criada com o operador *new*.  
As classes abstratas são sempre herdadas por uma subclasse, a qual vai implementar seus métodos.

~~~scala
abstract class IntSet {

    def contains(x: Int): Boolean
    def incl(x: Int): IntSet
    def union(other: IntSet): IntSet
}
~~~  
  
Vamos implementar a classe abstrata `IntSet` como uma *[árvore binária persistente](http://pesquisa.ufabc.edu.br/haskell/cursos/19.q3.eds_funcionais/files/dia01.pdf#page=46&zoom=100,36,-1)* com 2 tipos possíveis: 
1. Uma árvore para IntSet vazio
2. Uma árvore para IntSet contendo 1 número e 2 sub-árvores
  
No nosso exemplo, as sub-árvores da direita serão sempre maiores que as da esquerda.  

>**Estruturas de Dados Persistentes** são tipos de estruturas que sempre perservam sua versão anterior quando é modificada. São efetivamente imutáveis, pois suas operações não atualizam a estrutura no local, mas sempre produzem uma nova, atualizada.
>**Estruturas de dados persistentes são um dos pilares da programação funcional**.

~~~scala
class Empty extends IntSet {
    
    // Como esse é um IntSet vazio, ele nunca vai conter um elemento
    def contains(x: Int): Boolean = false
    // Mesmo sendo um IntSet vazio, podem ser adicionados novos "ramos".
    def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty)
    
    // Esse método retorna uma representação do objeto em forma de String.
    override def toString: String = "."
}

class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
    
    def contains(x: Int): Boolean = {
        /*
            Esse método vai procurar o valor "x" no IntSet. 
            Se for menor que o principal do IntSet, vai procurar no elemento à esquerda. 
            Se for maior, no elemento à direita.
            Se for igual ao elemento em questão, retornará true.
        */
        if (x < elem) left contains x
        else if (x > elem) right contains x
        else true
    }
    
    def incl(x: Int): IntSet = {
        /*
            Esse método vai incluir o valor "x" no IntSet. 
            A lógica é a mesma do método anterior, porém se o valor for 
            igual ao elemento do IntSet, irá retornar o próprio IntSet,
            pois não pode conter elementos repetidos.
        */
        if (x < elem) new NonEmpty(elem, left incl x, right)
        else if (x > elem) new NonEmpty(elem, left, right incl x)
        else this
    }
    
    override def toString: String = s"{$left$elem$right}"
}
~~~

## Classes <a name="classes"></a>

As definições de `contains` e `incl` nas classes Empty e NonEmpty implementam as funções abstratas na classe base IntSet. 
Também é possível redefinir uma função não abstrata, como foi feito com a função `toString`, utilizando `override` antes de declarar a mesma.

Se as classes `Empty` e `NonEmpty` herdam de `IntSet`, isso implica que ambas se conformam à classe IntSet. O que isso quer dizer, de fato, é que tanto Empty quanto NonEmpty podem ser usadas onde IntSet é requirido.

Em Scala, toda classe definida pelo usuário herda de alguma outra classe. Se nenhuma *super classe* é passada, a classe **Object**, do pacote `java.lang` é adotada.
As super classes, diretas ou indiretas, de uma classe X são chamadas *Classes Base* dessa classe X. Logo, as classes base de NonEmpty são IntSet e Object.

## Objects <a name="objects"></a>

Diferente da classe `java.lang.Object`, esse **Object** é um tipo de mescla e abreviação para se definir uma classe de uso único, a qual não pode ser instanciada. Elas também podem ser usadas como *Companion Objects*, que eu pretendo falar no futuro.

Para que `Empty` não precise ser instanciada tantas vezes, a classe pode ser melhor expressada como um object.
~~~scala
object Empty extends IntSet {
    
    def contains(x: Int): Boolean = false
    def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty)
    def union(other: IntSet): IntSet = other
    
    override def toString: String = "."
}
~~~

## Traits <a name="traits"></a>

São exatamente iguais às classes abstratas em sua estrutura, tendo como única diferença a possibilidade de ser herdada com `with`. Isso quer dizer que você pode herdar apenas 1 classe abstrata *(extends C)*, e quantas traits quiser *(with T with T2 with T3)*.
**Traits** são parecidas com as Interfaces em java, mas mais poderosas, pois podem conter campos e métodos concretos. Por outro lado, traits não podem conter variáves `val`.

## Tipos <a name="tipos"></a>

No topo da hierarquia de tipos estão:
* **Any** - É a base de todos os tipos existentes.
* **AnyRef** - É o tipo base de todos os tipos referência (tipos que não são valores). É um pseudônimo da classe `java.lang.Object`.
* **AnyVal** - É o tipo base de todos os tipos primitivos.  

![](https://raw.githubusercontent.com/LuizClaudioSantos/scala-notes/master/img/scala_type_hierarchy.png)
&nbsp;
### Nothing e Null <a name="nothing-null"></a>

**Nothing** está no fundo da hierarquia de tipos de Scala. Ele é um subtipo de todos os outros tipos, e não existe um valor do tipo Nothing.
Ele é útil para sinalizar terminações anormais e também como o tipo de elemento para uma collection vazia
~~~scala
val lista: List[Nothing]
~~~

A forma como Scala lida com exceções é parecida com Java. A expressão `throw new Exception` aborta o programa com a exceção `Exception`.
O tipo dessa expressão é **Nothing**.

**Null** é um subtipo de toda classe do tipo referência, logo, pertence a `AnyRef`.
Por ser um subtipo das classes que herdam de `java.lang.Object`, Null é incompatível com qualquer subtipo de `AnyVal`.
~~~scala
val x: Null = null
val y: Integer = null
val z: Int = null   // error: type mismatch
~~~
## Polimorfismo <a name="polimorfismo"></a>

 **Polimorfismo** significa que o tipo de uma função pode vir de muitas formas. É uma forma de tornar a linguagem mais expressiva, enquanto continua mantendo toda sua tipagem estática segura. 
 Usando o polimorfismo, a função ou tipo de dado pode ser escrita genericamente para que possa suportar valores idênticos sem depender de seu tipo.
 Existem 2 formas principais do polimorfismo:
 * **Subtyping** - As instâncias de uma subclasse podem ser passadas para uma classe base
 * **Generics** - As instâncias de uma função ou classe são criadas pela parametrização do tipo.
&nbsp;

---