Estudo Detalhado de Conceitos de Programação Orientada a Objetos em Scala

1. Pacotes

1.1 Propósitos

  • Resolver conflitos de nomes entre classes
  • Fornecer uma estrutura organizacional eficiente para um grande número de classes
  • Controlar o escopo de visibilidade

1.2 Sintaxe de Declaração

// Forma 1: Similar ao Java, usando '.' para delimitação hierárquica
package nome_do_pacote

// Forma 2: Usando '{}' para delimitação hierárquica
/*
    1. Um único arquivo fonte pode conter múltiplas declarações de pacotes
    2. Classes em subpacotes podem acessar diretamente o conteúdo do pacote pai, sem necessidade de importação
*/
package nivel_pacote1 {
    package nivel_pacote2 {
        ...
    }
}

1.3 Regras de Nomenclatura

  • Pode conter números, letras e sublinhados, mas não pode começar com uma palavra-chave
  • Geralmente segue a estrutura com.nome\_empresa.nome\_projeto.modulo\_negocio
package com {
    import com.corp.Interna // Acesso ao pacote filho requer importação
    object Externa {
        val textoExt: String = "ext"
        def main(args: Array[String]): Unit = {
            println(Interna.textoInt)
        }
    }
    
    package corp {
        object Interna {
            val textoInt: String = "int"
            def main(args: Array[String]): Unit = {
                println(Externa.textoExt) // Acesso ao pacote pai sem importação
            }
        }
    }
}

package com {
    package startup {
        
    }
}

1.4 Objetos de Pacote

Conceito: Em Scala, é possível definir um objeto de pacote com o mesmo nome do pacote. Membros definidos dentro do objeto de pacote são compartilhados por todas as classes e objetos dentro daquele pacote e podem ser acessados diretamente.

/**
    Pacote estilo Java com nome com.analise.demo
    Arquivo do objeto de pacote: package.scala
    Classes e objetos dentro de com.analise.demo podem acessar variáveis e métodos do objeto de pacote
*/
package object demo {
    val valorComum: String = "valor global"
    def funcaoComum(): Unit = {
        println(s"Acessando ${valorComum}")
    }
}

/**
    Pacotes aninhados. O objeto de pacote deve estar no mesmo escopo de declaração do pacote.
*/
package com {
    package analise {
        package demo {
            object TestDemo {
                def main(args: Array[String]): Unit = {
                    println(instituicao)
                }
            }
        }
        
        // Objeto de pacote no mesmo escopo da declaração do pacote
        package object demo {
            val instituicao: String = "UniversidadeFederal"
        }
    }
}

1.5 Importações

Pacotes importados por padrão:

  • import java.lang._
  • import scala._
  • import scala.Predef._

Sintaxe de importação:

// 1. Importação global no início do arquivo
import com.analise.Frutas
package com.analise.demo
object Teste {
    ...
}

// 2. Importação local: apenas no escopo corrente
package com.analise.demo
object Teste {
    def main(args: Array[String]): Unit = {
        import com.analise.Frutas
        ...
    }
    
    def outroMetodo(): Unit = {
        // Frutas não está acessível aqui
    }
}

// 3. Importação com curinga
import java.util._

// 4. Renomear classe na importação
import java.{util => ju}

// 5. Importar múltiplas classes do mesmo pacote
import java.util.{HashSet, ArrayList}

// 6. Excluir uma classe da importação
import java.util.{ArrayList => _, _}

// 7. Importação por caminho absoluto
new _root_.java.util.HashMap
Caso Descrição
import com.analise.Frutas Importa Frutas (classe ou objeto) do pacote com.analise
import com.analise._ Importa todos os membros do pacote com.analise
import com.analise.Frutas._ Importa todos os membros do objeto Frutas
import com.analise.{Frutas, Legumes} Importa Frutas e Legumes de com.analise
import com.analise.{Frutas => Macas} Importa Frutas e a renomeia como Macas
import com.analise.{Frutas => Macas, _} Importa todos e renomeia Frutas para Macas
import com.analise.{Frutas => _, _} Importa todos, exceto a classe Frutas

2. Classes e Objetos

2.1 Conceito

  • Classe: Uma abstração ou modelo para objetos.
  • Objeto: Uma representação de uma entidade concreta.

2.2 Sintaxe

object Programa {
    def main(args: Array[String]): Unit = {
        val aluno = new Aluno()
        println(aluno.idade)
        println(aluno.sexo)
        aluno.idade = 18
        println(aluno.idade)
        
        println(aluno.obterRA)
        aluno.definirRA("2023-XYZ")
        println(aluno.obterRA)
    }
}

/*
    Sintaxe de declaração de classe:
    [modificador] class nome_da_classe {
        propriedades
        métodos
    }
*/
class Aluno { // Modificador de acesso padrão é público
    private val nome: String = "João" // Propriedades são públicas por padrão
    var idade: Int = _ // '_' define um valor padrão (0 para Int, null para String, false para Boolean)
    var sexo: String = _ // '_' só pode ser usado com 'var'
    
    @BeanProperty
    var ra: String = "001" // Gera automaticamente métodos getter/setter compatíveis com Java
}

// Um único arquivo pode conter múltiplas classes
class Professor {
    ...
}

3. Encapsulamento

3.1 Conceito

Encapsular é combinar dados e as operações que atuam sobre eles em uma unidade, protegendo os dados internos. Outras partes do programa só podem acessar os dados através de métodos autoirzados.

3.2 Comparação com Java e Permissões de Acesso

  • Público (padrão em Scala): Não há palavra-chave 'public'. Acesso universal.
  • Private: Visível apenas dentro da classe e seu objeto companheiro.
  • Protected: Mais restrito que em Java. Visível na classe e subclasses, mas não em classes no mesmo pacote.
  • private[nomepacote]: Expande o acesso para classes dentro do pacote especificado.
package com.empresa.projeto

object Pessoa {
    def main(args: Array[String]): Unit = {
        val pessoa = new Pessoa()
        pessoa.falar()
        println(pessoa.nome)
        println(pessoa.idade)
    }
}

class Pessoa {
    private var nome: String = "Carlos"
    protected var idade: Int = 25
    var endereco: String = "Rua Principal"
    private[projeto] var genero: String = "M"
    
    def falar(): Unit = {
        println(s"$nome $idade $endereco $genero")
    }
}

class Funcionario extends Pessoa {
    def teste(): Unit = {
        // this.nome // Erro
        this.idade // Permitido (subclasse)
        this.genero // Permitido (acesso via pacote)
    }
    
    override def falar(): Unit = {
        println(s"$idade $endereco $genero")
    }
}

class Animal {
    def verificar: Unit = {
        // new Pessoa().idade // Erro (protected, mesma linha de herança)
        new Pessoa().genero // Permitido (acesso via pacote)
    }
}

package com.empresa.outro_projeto
class Gerente extends Pessoa {
    def teste(): Unit = {
        // this.nome // Erro
        this.idade // Permitido (subclasse)
        // this.genero // Erro (fora do pacote 'projeto')
    }
}

3.3 Construtores

3.3.1 Sintaxe Básica
/*
    A classe Scala inclui: construtor primário e construtores auxiliares
*/
class NomeClasse(param1: Tipo1,...) {    // Construtor Primário
    
    def this(param1: Tipo1,...) {   // Construtor Auxiliar
    
    }
}

Regras:

  • Construtores auxiliares são nomeados 'this' e sobrecarregados por assinatura.
  • Devem chamar, direta ou indiretamente, o construtor primário.
  • Um construtor auxiliar deve ser definido antes de ser chamado.
3.3.2 Parâmetros do Construtor

Os parâmetros do construtor primário podem ter três modificações:

  • Sem modificador: Variável local dentro do construtor.
  • var: Torna-se uma propriedade mutável da classe.
  • val: Torna-se uma propriedade imutável (somente leitura) da classe.
// Parâmetros como variáveis locais (não recomendado)
class Aluno1(_nome: String, _idade: Int) {
    def mostrar(): Unit = {
        // As variáveis _nome e _idade só existem aqui
    }
}

// Parâmetros como propriedades mutáveis (recomendado)
class Aluno2(var nome: String, var idade: Int) 

// Parâmetros como propriedades imutáveis
class Aluno3(val nome: String, val idade: Int) 

// Construtor auxiliar com propriedade adicional
class Aluno4(var nome: String, var idade: Int) {
    var escola: String = _
    
    def this(nome: String, idade: Int, escola: String) {
        this(nome, idade)
        this.escola = escola
    }
}

4. Herança e Polimorfismo

4.1 Herança

// Scala suporta apenas herança simples
class ClasseFilha extends ClassePai {
    // Herda atributos e métodos
}

4.2 Polimorfismo (Ligação Dinâmica)

object TestePolimorfismo {
    def main(args: Array[String]): Unit = {
        def processar(entidade: Entidade): Unit = {
            entidade.descricao()
        }
        
        val ent = new Entidade
        val func = new Funcionario
        val gerente = new Gerente
        
        // O método correto é chamado em tempo de execução, baseado no tipo real do objeto
        processar(ent)    // "sou uma entidade"
        processar(func)   // "sou um funcionário"
        processar(gerente)// "sou um gerente"
    }
}

class Entidade {
    def descricao(): Unit = {
        println("sou uma entidade")
    }
}

class Funcionario extends Entidade {
    override def descricao(): Unit = {
        println("sou um funcionário")
    }
}

class Gerente extends Entidade {
    override def descricao(): Unit = {
        println("sou um gerente")
    }
}

5. Classes Abstratas

5.1 Definição

Uma classe abstrata (modificada com abstract) serve como um modelo. Pode conter atributos e métodos tanto abstratos (sem implementação) quanto concretos. Deve ser estendida.

object TesteAbstrato {
    def main(args: Array[String]): Unit = {
        val aluno: Aluno = new Aluno
        println(aluno.nome)
        println(aluno.curso)
        aluno.estudar()
        aluno.dormir()
    }
}

abstract class Pessoa {
    val nome: String = "Ana" // Atributo concreto
    var idade: Int = 20      // Atributo concreto
    
    var curso: String        // Atributo abstrato (sem valor inicial)
    
    def estudar(): Unit = {  // Método concreto
        println("pessoa estuda")
    }
    
    def dormir(): Unit       // Método abstrato (sem corpo)
}

class Aluno extends Pessoa {
    override val nome: String = "Bruno"
    idade = 21 // Modifica a propriedade herdada, não a pode sobrescrever com override
    
    var curso: String = "Engenharia" // Implementa o atributo abstrato
    
    override def estudar(): Unit = { // Sobrescreve o método concreto
        super.estudar()
        println("aluno estuda")
    }
    
    def dormir(): Unit = { // Implementa o método abstrato, 'override' é opcional
        println("aluno dorme")
    }
}

5.2 Classes Anônimas

object TesteAnonimo {
    def main(args: Array[String]): Unit = {
        val individuo: Pessoa = new Pessoa {
            var nome: String = "Pedro"
            var curso: String = "Filosofia"
            def estudar(): Unit = println("Pedro estuda")
            def dormir(): Unit = println("Pedro dorme")
        }
        
        println(individuo.nome)
        individuo.estudar()
    }
}

abstract class Pessoa {
    var nome: String
    var curso: String
    def estudar(): Unit
    def dormir(): Unit
}

6. Objetos Companheiros e Singleton

6.1 Conceito

Scala não possui membros estáticos. Em vez disso, usa objetos companheiros: um objeto (declarado com object) com o mesmo nome de uma classe. Os membros do objeto companheiro atuam como membros "estáticos" da classe.

// Classe
class Aluno(val nome: String, val idade: Int) {
    def exibir(): Unit = {
        println(s"Nome: $nome, Idade: $idade, Escola: ${Aluno.escola}")
    }
}

// Objeto Companheiro
object Aluno {
    val escola: String = "Escola Estadual" // Membro "estático" compartilhado
}

object ProgramaPrincipal {
    def main(args: Array[String]): Unit = {
        val a1 = new Aluno("Lucas", 15)
        val a2 = new Aluno("Maria", 16)
        a1.exibir() // Ambos mostram a mesma escola
        a2.exibir()
    }
}

6.2 Método Apply

O método apply no objeto companheiro permite criar instâncias da classe associada sem usar new.

class Aluno private(val nome: String, val idade: Int) { // Construtor privado
    def exibir(): Unit = {
        println(s"Nome: $nome, Idade: $idade")
    }
}

object Aluno {
    val escola: String = "Escola Municipal"
    
    // Método apply para criação de instâncias
    def apply(nome: String, idade: Int): Aluno = new Aluno(nome, idade)
    
    // Sobrecarga do apply
    def apply(): Aluno = new Aluno("Padrão", 0)
}

object ProgramaPrincipal {
    def main(args: Array[String]): Unit = {
        val a1 = Aluno("Ana", 17) // Equivalente a Aluno.apply("Ana", 17)
        val a2 = Aluno()
        a1.exibir()
        a2.exibir()
    }
}

6.3 Implementação de Singleton

object TesteSingleton {
    def main(args: Array[String]): Unit = {
        val obj1 = Config.getInstancia()
        val obj2 = Config.getInstancia()
        println(obj1 == obj2) // true
    }
}

class Config private(param: String)

object Config {
    // Implementação "Eager"
    // private val instancia: Config = new Config("padrão")
    // def getInstancia(): Config = instancia
    
    // Implementação "Lazy"
    private var instancia: Config = _
    def getInstancia(): Config = {
        if (instancia == null) {
            instancia = new Config("padrão")
        }
        instancia
    }
}

7. Traits (Características)

7.1 Definição

Traits são a alternativa Scala para interfaces. Podem conter implementações concretas e abstratas de atributos e métodos. Uma classe pode misturar (mixin) múltiplos traits, complementando o mecanismo de herança simples.

7.2 Sintaxe Básica

trait NomeTrait {
    val atributoConcreto: String = "valor"
    var atributoAbstrato: String // Atributo abstrato
    
    def metodoConcreto(): Unit = {
        println("implementação no trait")
    }
    
    def metodoAbstrato(): Unit // Método abstrato
}

7.3 Mistura de Traits (Mixins)

// Com herança existente
class ClasseFilha extends ClassePai with Trait1 with Trait2 {}

// Sem herança
class ClasseFilha extends Trait1 with Trait2 with Trait3 {}

object TesteMixin {
    def main(args: Array[String]): Unit = {
        // Mistura estática na definição da classe
        val aluno = new Estudante
        aluno.comer()
        aluno.aprender()
        
        // Mistura dinâmica na instanciação
        val alunoTalentoso = new Estudante with Talento {
            override def cantar(): Unit = println("aluno canta bem")
            override def dançar(): Unit = println("aluno dança bem")
        }
        alunoTalentoso.cantar()
    }
}

class Pessoa {
    def comer(): Unit = println("pessoa come")
}

trait Estudioso {
    var conhecimento: Int = 0
    def aprender(): Unit
}

trait Talento {
    def cantar(): Unit
    def dançar(): Unit
}

class Estudante extends Pessoa with Estudioso {
    def aprender(): Unit = {
        conhecimento += 1
        println("estudante aprende")
    }
}

7.4 Resolução de Conflitos (Linearização)

Quando múltiplos traits misturados têm membros com o mesmo nome, Scala usa um processo chamado linearização para resolver a ordem de chamada. Um conflito comum ocorre no padrão diamante.

object TesteConflito {
    def main(args: Array[String]): Unit = {
        val bola = new MinhaBola
        println(bola.descrever()) // "minha bola é uma bola-vermelha-de-futebol"
    }
}

trait Bola {
    def descrever(): String = "bola"
}

trait BolaColorida {
    val cor: String = "vermelha"
    def descrever(): String = cor + "-" + super.descrever()
}

trait BolaDeFutebol {
    val categoria: String = "futebol"
    def descrever(): String = categoria + "-" + super.descrever()
}

// A ordem dos traits após 'extends' define a linearização
class MinhaBola extends BolaDeFutebol with BolaColorida {
    override def descrever(): String = {
        // super.descrever() segue a linearização: BolaColorida -> BolaDeFutebol -> Bola
        "minha bola é uma " + super.descrever()
        // Para chamar um trait específico: super[BolaDeFutebol].descrever()
    }
}

7.5 Tipos Próprios do Trait (Self-type)

Um trait pode exigir que seja misturado em uma classe que estende ou contém um tipo específico, similar à injeção de dependência.

object TesteSelfType {
    def main(args: Array[String]): Unit = {
        val usuario = new UsuarioRegistrado("maria", "senha123")
        usuario.salvar()
    }
}

class Usuario(val nome: String, val senha: String)

trait UsuarioDAO {
    // Declaração de tipo próprio: exige ser misturado com uma classe que seja um Usuario
    _: Usuario =>
    
    def salvar(): Unit = {
        // 'this' acessa os membros de Usuario
        println(s"Salvando usuário: ${this.nome} no banco")
    }
}

// A classe deve herdar de Usuario (ou ser Usuario) e misturar o trait
class UsuarioRegistrado(nome: String, senha: String) extends Usuario(nome, senha) with UsuarioDAO

8. Tópicos Adicionais

8.1 Verificação e Conversão de Tipos

class Animal
class Cachorro extends Animal

object TesteTipos {
    def main(args: Array[String]): Unit = {
        val animal: Animal = new Animal
        val cachorro: Animal = new Cachorro
        val rex: Cachorro = new Cachorro
        
        // Verificação de tipo
        val teste1: Boolean = animal.isInstanceOf[Animal]    // true
        val teste2: Boolean = animal.isInstanceOf[Cachorro]  // false
        val teste3: Boolean = cachorro.isInstanceOf[Cachorro]// true
        
        // Conversão de tipo (com verificação de segurança)
        if (cachorro.isInstanceOf[Cachorro]) {
            val c: Cachorro = cachorro.asInstanceOf[Cachorro]
        }
        
        // Obter a classe
        val classeAnimal: Class[Animal] = classOf[Animal]
    }
}

8.2 Tipos Personalizados

object TesteType {
    def main(args: Array[String]): Unit = {
        // 'type' cria um apelido para um tipo existente
        type Texto = String
        type ListaDeInteiros = List[Int]
        
        val mensagem: Texto = "Olá"
        val numeros: ListaDeInteiros = List(1, 2, 3)
    }
}

Tags: Scala programacao-orientada-a-objetos traits companion-objects Herança

Publicado em 7-2 00:58