Revisão de Conceitos
Classe vs Objeto
Seção intitulada “Classe vs Objeto”Uma classe é uma definição para criar objetos. Ela define os atributos (variáveis) e comportamentos (métodos) que os objetos criados a partir dela terão.
Ela pode ser vista como um molde ou uma planta que descreve as características e comportamentos de um tipo específico de objeto. Por exemplo, uma classe Pessoa pode ter atributos como nome e idade, e métodos como apresentar(), que exibe uma mensagem de apresentação.
Uma classe pode ser composta de:
- Atributos: Variáveis que armazenam informações sobre o estado do objeto. No exemplo da classe
Pessoa, os atributos sãonomeeidade. - Métodos: Funções que definem o comportamento do objeto. No exemplo da classe
Pessoa, o método éapresentar(), que exibe uma mensagem de apresentação.
public class Pessoa { private String nome; private int idade;
public Pessoa(String nome, int idade) { this.nome = nome; this.idade = idade; }
public void apresentar() { System.out.println("Olá, meu nome é " + nome + " e tenho " + idade + " anos."); }}Já um objeto é uma instância de uma classe. Ele é criado a partir da definição da classe e possui seus próprios valores para os atributos definidos na classe. Por exemplo, se criarmos um objeto pessoa1 a partir da classe Pessoa, ele terá seus próprios valores para nome e idade, e poderá chamar o método apresentar() para exibir sua apresentação.
Pessoa pessoa1 = new Pessoa("Alice", 30);pessoa1.apresentar(); // Saída: Olá, meu nome é Alice tenho 30 anos.Cada objeto criado a partir de uma classe é independente dos outros objetos, mesmo que sejam instâncias da mesma classe. Por exemplo, se criarmos outro objeto pessoa2 a partir da classe Pessoa, ele terá seus próprios valores para nome e idade, e poderá chamar o método apresentar() para exibir sua apresentação, sem afetar o objeto pessoa1.
Interfaces
Seção intitulada “Interfaces”Interfaces em Java definem um contrato: um conjunto de métodos que uma classe deve implementar, sem definir como esses métodos serão executados. Elas ajudam a desacoplar código e permitem múltiplas “tipagens” para o mesmo objeto.
Características principais
Seção intitulada “Características principais”- Não possuem implementação concreta (até o Java 7, ver exceções abaixo).
- Uma classe implementa uma interface usando
implements. - Uma classe pode implementar múltiplas interfaces (diferente de herança de classe única).
- Servem para definir papéis/comportamentos, e não detalhes de implementação.
public interface Notificador { void notificar(String mensagem);}Qualquer classe que implementar Notificador deve fornecer uma implementação para notificar.
public class EmailNotificador implements Notificador { @Override public void notificar(String mensagem) { System.out.println("Enviando e-mail: " + mensagem); }}
public class SmsNotificador implements Notificador { @Override public void notificar(String mensagem) { System.out.println("Enviando SMS: " + mensagem); }}Uso:
public class Pedido { private Notificador notificador;
public Pedido(Notificador notificador) { this.notificador = notificador; }
public void confirmar() { System.out.println("Pedido confirmado."); notificador.notificar("Seu pedido foi confirmado!"); }}
// Em outro ponto do código:Notificador notificador = new EmailNotificador();Pedido pedido = new Pedido(notificador);pedido.confirmar();Repare que Pedido não sabe se a notificação é por e-mail, SMS etc. Ele depende apenas da interface Notificador, reduzindo acoplamento.
A partir do Java 8, interfaces podem ter:
- Métodos
default: implementações padrão que podem ser reutilizadas ou sobrescritas.
public interface Validavel { boolean isValido();
default void imprimirEstadoValidacao() { if (isValido()) { System.out.println("Objeto válido."); } else { System.out.println("Objeto inválido."); } }}- Métodos
static: funções utilitárias relacionadas ao contrato.
public interface Conversor { static double celsiusParaFahrenheit(double c) { return c * 1.8 + 32; }}Papel das interfaces em Design Patterns
Seção intitulada “Papel das interfaces em Design Patterns”Muitos padrões de projeto (Strategy, Observer, Factory Method, Adapter, etc.) fazem uso intenso de interfaces para:
- Definir pontos de extensão.
- Permitir troca de implementações em tempo de execução.
- Reduzir dependência entre módulos (baixo acoplamento).
- Facilitar testes (mocks/stubs implementando interfaces).
Interfaces são, portanto, um dos principais mecanismos para alcançar flexibilidade e boa arquitetura em sistemas orientados a objetos.
Herança vs Composição
Seção intitulada “Herança vs Composição”A herança e a composição são dois conceitos fundamentais na programação orientada a objetos, usados para reutilizar código e estruturar sistemas.
Herança
Seção intitulada “Herança”A herança é um mecanismo que permite que uma classe (chamada de classe filha ou subclasse) herde atributos e métodos de outra classe (chamada de classe pai ou superclasse). Isso promove a reutilização de código e facilita a extensão de funcionalidades.
Por exemplo, considere uma classe Animal que define atributos e métodos comuns a todos os animais. Uma subclasse Cachorro pode herdar esses atributos e métodos, além de adicionar comportamentos específicos.
public class Animal { protected String nome;
public Animal(String nome) { this.nome = nome; }
public void emitirSom() { System.out.println("O animal emite um som."); }}
public class Cachorro extends Animal {
public Cachorro(String nome) { super(nome); }
@Override public void emitirSom() { System.out.println("O cachorro late."); }}No exemplo acima, a classe Cachorro herda o atributo nome e o método emitirSom da classe Animal, mas também pode sobrescrever o método para fornecer um comportamento específico.
Composição
Seção intitulada “Composição”A composição é uma abordagem em que uma classe é composta por instâncias de outras classes, em vez de herdar diretamente de uma superclasse. Isso promove maior flexibilidade e reduz o acoplamento entre as classes.
Por exemplo, considere uma classe Motor que define o comportamento de um motor. Uma classe Carro pode ser composta por uma instância de Motor, em vez de herdar dela.
public class Motor { public void ligar() { System.out.println("O motor está ligado."); }}
public class Carro { private Motor motor;
public Carro() { this.motor = new Motor(); }
public void ligarCarro() { motor.ligar(); System.out.println("O carro está ligado."); }}No exemplo acima, a classe Carro utiliza a composição para incluir um Motor, permitindo maior flexibilidade na reutilização de código.
Comparação
Seção intitulada “Comparação”- Herança: Promove a reutilização de código, mas pode levar a um acoplamento mais forte entre as classes. Deve ser usada quando há uma relação “é um” (ex.: um cachorro é um animal).
- Composição: Promove maior flexibilidade e baixo acoplamento. Deve ser usada quando há uma relação “tem um” (ex.: um carro tem um motor).
Ambas as abordagens têm suas vantagens e desvantagens, e a escolha entre elas depende do contexto e dos requisitos do sistema.
Acoplamento vs Coesão
Seção intitulada “Acoplamento vs Coesão”Acoplamento e coesão são dois conceitos fundamentais na engenharia de software, especialmente no contexto de design de sistemas e programação orientada a objetos.
Acoplamento
Seção intitulada “Acoplamento”Acoplamento refere-se ao grau de dependência entre diferentes módulos ou classes de um sistema. Um sistema com baixo acoplamento é preferível, pois as mudanças em um módulo têm menos impacto nos outros.
- Baixo Acoplamento: Indica que os módulos ou classes têm pouca dependência entre si. Isso facilita a manutenção, testes e reutilização de código.
- Alto Acoplamento: Indica que os módulos ou classes estão fortemente interligados, o que pode dificultar a manutenção e evolução do sistema.
Exemplo de baixo acoplamento:
public interface Notificador { void notificar(String mensagem);}
public class EmailNotificador implements Notificador { @Override public void notificar(String mensagem) { System.out.println("Enviando email: " + mensagem); }}
public class Pedido { private Notificador notificador;
public Pedido(Notificador notificador) { this.notificador = notificador; }
public void confirmar() { System.out.println("Pedido confirmado."); notificador.notificar("Seu pedido foi confirmado!"); }}No exemplo acima, a classe Pedido depende da interface Notificador, e não de uma implementação específica, promovendo baixo acoplamento.
Coesão refere-se ao grau em que as responsabilidades de um módulo ou classe estão relacionadas. Uma classe com alta coesão realiza uma única tarefa ou um conjunto de tarefas intimamente relacionadas.
- Alta Coesão: Indica que uma classe ou módulo tem responsabilidades bem definidas e relacionadas. Isso torna o código mais fácil de entender e manter.
- Baixa Coesão: Indica que uma classe ou módulo realiza muitas tarefas não relacionadas, o que pode dificultar a compreensão e manutenção.
Exemplo de alta coesão:
public class Calculadora { public int somar(int a, int b) { return a + b; }
public int subtrair(int a, int b) { return a - b; }
}No exemplo acima, a classe Calculadora tem alta coesão, pois todas as suas responsabilidades estão relacionadas a operações matemáticas.
Comparação
Seção intitulada “Comparação”- Acoplamento: Focado na relação entre diferentes classes ou módulos.
- Coesão: Focado na organização interna de uma única classe ou módulo.
Um bom design de software busca baixo acoplamento e alta coesão, promovendo sistemas mais robustos, flexíveis e fáceis de manter.