2024
PROGRAMAÇÃO
MODULAR
Curso de Engenharia de
Software
Luiz Otávio Veloso
Luiz Otávio Veloso 5
PROGRAMAÇÃO MODULAR
1. Modularidade
1.1. Definição e importância da modularidade
A modularidade é um conceito fundamental na programação de computadores
que se refere à prática de dividir um programa em partes menores, chamadas
módulos. Cada módulo é uma unidade de código que realiza uma função
específica e pode ser desenvolvido, testado e mantido de forma independente.
Esses módulos podem ser funções, classes ou mesmo arquivos inteiros que
agrupam funcionalidades relacionadas (PRESSMAN, 2019).
Importância da Modularidade:
1. Reutilização de Código: Módulos bem projetados podem ser
reutilizados em diferentes partes de um programa ou mesmo em outros
projetos. Isso economiza tempo e esforço, além de promover a
consistência no desenvolvimento (SOMMERVILLE, 2016).
2. Facilidade de Manutenção: Dividindo o código em módulos menores e
independentes, fica mais fácil identificar e corrigir erros, fazer melhorias
ou adicionar novas funcionalidades sem impactar todo o sistema
(MARTIN, 2009).
3. Colaboração: Em projetos maiores, a modularidade permite que
diferentes equipes ou desenvolvedores trabalhem simultaneamente em
diferentes partes do sistema. Isso facilita a colaboração e a integração
do trabalho realizado por diferentes membros da equipe (STEVENS;
MYERS; CONSTANTINE, 1974).
4. Leitura e Compreensão: Um programa modular é geralmente mais fácil
de ler e entender, pois cada módulo foca em uma parte específica da
funcionalidade, reduzindo a complexidade do código (PRESSMAN,
2019).
5. Testabilidade: Módulos independentes são mais fáceis de testar
isoladamente. Isso permite que os desenvolvedores garantam que cada
parte do sistema funcione corretamente antes de integrá-la ao todo
(FOWLER, 1999).
6. Escalabilidade: Em sistemas grandes, a modularidade ajuda a escalar
o desenvolvimento, pois novos módulos podem ser adicionados sem a
necessidade de reescrever o código existente (MARTIN, 2009).
Em resumo, a modularidade não apenas melhora a organização e a clareza do
código, mas também facilita o desenvolvimento, a manutenção, a
escalabilidade e a colaboração em projetos de programação.
1.2. Princípios de design modular
A modularidade é um dos pilares fundamentais no design de sistemas de
software, permitindo que grandes aplicações sejam estruturadas de forma
eficiente e organizada. O design modular, baseado em princípios como coesão,
Luiz Otávio Veloso 6
PROGRAMAÇÃO MODULAR
acoplamento e encapsulamento, visa criar sistemas compostos por módulos
independentes que se integram de maneira harmoniosa. Esses princípios não
apenas facilitam a compreensão e a manutenção do código, mas também
promovem a reutilização e a escalabilidade, características essenciais para o
desenvolvimento de software moderno e robusto. A seguir, são apresentados
os principais princípios que orientam o design modular.
1. Coesão:
Coesão refere-se ao grau em que os elementos dentro de um módulo
estão relacionados entre si. Um módulo coeso realiza uma única tarefa
ou um grupo de tarefas altamente relacionadas. Alta coesão é desejável,
pois facilita a compreensão, o desenvolvimento e a manutenção do
módulo, além de reduzir a complexidade interna (PRESSMAN, 2019).
2. Acoplamento:
Acoplamento é a medida da interdependência entre módulos. Baixo
acoplamento é ideal, pois minimiza as dependências entre diferentes
partes do sistema, tornando-o mais fácil de modificar e testar. Módulos
com baixo acoplamento podem ser alterados ou substituídos sem afetar
significativamente outras partes do sistema (PRESSMAN, 2019).
3. Encapsulamento:
Encapsulamento é o princípio de esconder a implementação interna de
um módulo, expondo apenas o que é necessário através de interfaces
públicas. Isso protege os detalhes internos do módulo de interferências
externas, permitindo que as mudanças internas não afetem outras
partes do sistema (SOMMERVILLE, 2016).
4. Separação de Preocupações (Separation of Concerns):
Este princípio defende que diferentes funcionalidades ou preocupações
de um sistema devem ser divididas em módulos distintos. Isso ajuda a
organizar o código de forma que cada módulo tenha uma
responsabilidade clara e específica, evitando confusão e sobrecarga de
responsabilidades (SOMMERVILLE, 2016).
5. Substituibilidade:
Substituibilidade refere-se à capacidade de substituir um módulo por
outro que implementa a mesma interface ou funcionalidade, sem
precisar alterar outras partes do sistema. Isso promove flexibilidade e
facilita a atualização e manutenção do sistema (FOWLER, 2004).
6. Reutilização:
Luiz Otávio Veloso 7
PROGRAMAÇÃO MODULAR
Reutilização é a prática de usar módulos existentes em novos contextos
ou sistemas. Um bom design modular facilita a reutilização de código,
pois módulos independentes e bem definidos podem ser aplicados em
diferentes projetos sem a necessidade de modificação (PRESSMAN,
2019).
7. Independência Funcional:
Este princípio destaca que cada módulo deve ser o mais independente
possível, contendo todas as funcionalidades necessárias para executar
suas tarefas sem precisar de apoio significativo de outros módulos. Isso
ajuda a isolar mudanças e falhas, melhorando a robustez do sistema
(SOMMERVILLE, 2016).
8. Princípio da Responsabilidade Única (Single Responsibility Principle
- SRP):
Cada módulo ou classe deve ter uma única responsabilidade ou razão
para mudar. Isso significa que um módulo deve focar em apenas uma
parte do comportamento ou funcionalidade de um sistema (FOWLER,
2004).
1.3. Técnicas de decomposição modular
A decomposição modular é uma técnica essencial no design de sistemas de
software, que permite dividir um problema complexo em partes menores e mais
gerenciáveis. A seguir, são explicadas algumas das principais técnicas de
decomposição modular, com referências a livros em português.
1. Decomposição Funcional:
A decomposição funcional é uma técnica que divide o sistema em
módulos com base nas funções ou operações que o sistema deve
realizar. Cada módulo é responsável por uma tarefa específica, e
o sistema completo é construído integrando esses módulos
funcionais. Essa abordagem facilita a compreensão do sistema,
pois cada módulo pode ser visto como uma “caixa preta” que
realiza uma função claramente definida.
Pressman (2019) destaca que "a decomposição funcional é um
meio eficaz de organizar um sistema em módulos coesos, cada
um responsável por uma função bem definida dentro do sistema
maior".
2. Decomposição por Camadas (Layered Decomposition):
Luiz Otávio Veloso 8
PROGRAMAÇÃO MODULAR
A decomposição por camadas é uma técnica que organiza o
sistema em níveis hierárquicos, onde cada camada é responsável
por uma abstração específica. As camadas superiores dependem
das camadas inferiores, mas as camadas inferiores não devem
depender das superiores. Isso facilita a manutenção e a evolução
do sistema, pois cada camada pode ser modificada
independentemente, desde que as interfaces sejam preservadas.
Sommerville (2016) afirma que "a decomposição em camadas
ajuda a organizar o sistema em níveis de abstração, o que
simplifica a manutenção e o entendimento do sistema"
3. Decomposição Orientada a Dados:
Nesta técnica, a decomposição é feita com base nas estruturas
de dados que o sistema manipula. Cada módulo é responsável
por operar em um conjunto específico de dados, encapsulando as
operações que podem ser realizadas sobre esses dados. Isso
permite que as mudanças nas estruturas de dados sejam isoladas
nos módulos correspondentes, sem impactar o restante do
sistema.
De acordo com Sommerville (2016), "a decomposição orientada a
dados é útil quando as operações sobre os dados são complexas
ou quando se deseja encapsular completamente as mudanças
nas estruturas de dados".
4. Decomposição Baseada em Objeto:
A decomposição baseada em objeto é uma técnica que organiza
o sistema em módulos que representam objetos do mundo real ou
conceitos abstratos do domínio do problema. Cada módulo (ou
classe) encapsula dados e comportamentos relacionados,
promovendo alta coesão e baixo acoplamento. Esta abordagem é
central na programação orientada a objetos.
Como Fowler (2004) aponta, "a decomposição baseada em
objetos permite que o design do sistema espelhe a estrutura do
mundo real, facilitando a modelagem de problemas complexos"
Essas técnicas de decomposição modular ajudam a simplificar o design e a
implementação de sistemas complexos, tornando-os mais fáceis de entender,
Luiz Otávio Veloso 9
PROGRAMAÇÃO MODULAR
manter e escalar. A escolha da técnica apropriada depende do contexto e dos
requisitos específicos do sistema em desenvolvimento.
2. Orientação a Objetos
A Programação Orientada a Objetos (POO) é um paradigma de
desenvolvimento de software que organiza o código em torno de "objetos", que
são instâncias de "classes". Esses objetos representam entidades do mundo
real ou conceitos abstratos, e encapsulam tanto os dados (atributos) quanto os
comportamentos (métodos) que lhes são associados.
De acordo com Deitel & Deitel (2017), "a programação orientada a objetos
permite que programadores criem módulos de software que são independentes
e intercambiáveis, facilitando a reutilização de código, a manutenção de
sistemas e a modelagem de problemas complexos de forma mais intuitiva e
eficiente" (DEITEL; DEITEL, 2017, p. 47).
As principais linguagens de programação orientadas a objetos (POO) são
amplamente utilizadas no desenvolvimento de software e oferecem suporte
total a esse paradigma. Algumas das mais destacadas incluem:
1. Java:
Uma das linguagens mais populares para desenvolvimento de software
empresarial, aplicativos móveis (especialmente Android), e sistemas de
grande escala. Java é conhecida por sua portabilidade e robustez.
2. C++:
Uma extensão da linguagem C, C++ é usada em sistemas operacionais,
drivers, jogos, e software de alto desempenho. Ela suporta tanto
programação orientada a objetos quanto programação procedural.
3. Python:
Uma linguagem de alto nível que é popular por sua simplicidade e
legibilidade. Python é usada em várias áreas, incluindo desenvolvimento
web, ciência de dados, automação, e inteligência artificial.
4. C#:
Desenvolvida pela Microsoft, C# é a principal linguagem para o
desenvolvimento de aplicações na plataforma .NET, incluindo software
empresarial, jogos (usando Unity), e aplicativos web.
5. Ruby:
Luiz Otávio Veloso 10
PROGRAMAÇÃO MODULAR
Conhecida por sua simplicidade e produtividade, Ruby é amplamente
utilizada no desenvolvimento web, especialmente com o framework Ruby on
Rails.
6. Objective-C e Swift:
Objective-C é a linguagem tradicional para desenvolvimento de software
para iOS e macOS, enquanto Swift é a linguagem moderna introduzida pela
Apple para substituir Objective-C, oferecendo uma sintaxe mais segura e
intuitiva.
7. Smalltalk:
Uma das primeiras linguagens a introduzir e popularizar o conceito de
programação orientada a objetos. Embora não seja tão amplamente usada
hoje, Smalltalk influenciou muitas outras linguagens orientadas a objetos.
Essas linguagens não só permitem o desenvolvimento de software modular e
reutilizável, mas também facilitam a modelagem de sistemas complexos de
maneira mais intuitiva, refletindo melhor as relações e interações do mundo
real.
IMPORTANTE!
CamelCase é um estilo de nomenclatura utilizado em programação e design
de software, onde palavras são concatenadas sem espaços, e cada palavra
subsequente começa com uma letra maiúscula. Esse formato melhora a
legibilidade e facilita a distinção entre diferentes partes do nome.
Resumo das Formatações CamelCase e UPPER_SNAKE_CASE:
1. UpperCamelCase (PascalCase): MyClassName, EmployeeRecord
Uso: Classes, tipos de dados.
2. lowerCamelCase: calculateTotalAmount, getUserName
Uso: Variáveis, funções, métodos.
3. UPPER_SNAKE_CASE: MAX_VALUE, DEFAULT_TIMEOUT
Uso: Constantes, variáveis imutáveis.
Utilizaremos este estilo de programação ao codificar em Java,
sempre que necessário recorra a este quadro.
Luiz Otávio Veloso 11
PROGRAMAÇÃO MODULAR
2.1. Classes e objetos
Em Programação Orientada a Objetos (POO), os conceitos de classe e objeto
são fundamentais para entender como o paradigma funciona.
De acordo com Deitel & Deitel (2017), "classes e objetos são conceitos centrais
na programação orientada a objetos, com classes fornecendo a estrutura e os
objetos representando instâncias concretas dessa estrutura" (DEITEL; DEITEL,
2017, p. 78).
Aqui está uma explicação de cada um e suas principais diferenças:
Classe
Definição: Uma classe é uma definição ou um molde que descreve um
tipo de objeto. Ela define a estrutura e o comportamento dos objetos que
serão criados a partir dela. Em outras palavras, uma classe especifica os
atributos (ou propriedades) e os métodos (ou comportamentos) que os
objetos dessa classe terão.
Características:
o Atributos: Variáveis que armazenam o estado do objeto. Por
exemplo, em uma classe Carro, os atributos podem incluir cor,
modelo e ano.
o Métodos: Funções que definem o comportamento dos objetos.
No exemplo da classe Carro, métodos podem incluir acelerar(),
frear() e ligar().
Código: Exemplo de código de uma Classe em Java
public class Carro {
String cor;
String modelo;
int ano;
void acelerar() {
[Link]("O carro está acelerando.");
}
}
Objeto
Definição: Um objeto é uma instância de uma classe. É uma entidade
concreta que possui um estado específico e pode executar
comportamentos definidos pela classe da qual é instanciado. Em termos
Luiz Otávio Veloso 12
PROGRAMAÇÃO MODULAR
simples, se a classe é o molde, o objeto é o produto real criado a partir
desse molde.
Características:
o Estado: Determinado pelos valores dos atributos do objeto. Por
exemplo, um objeto da classe Carro pode ter a cor como
"vermelho", modelo como "Fusca", e ano como 1975.
o Comportamento: Executado através dos métodos definidos na
classe. Um objeto pode usar o método acelerar() para simular
uma aceleração.
Código: Exemplo de código de instanciação de objeto em Java
public class Main {
public static void main(String[] args) {
Carro meuCarro = new Carro(); // Criação de um objeto
[Link] = "vermelho";
[Link] = "Fusca";
[Link] = 1975;
[Link](); // Chamando um método do objeto
}
}
Diferenças Principais
1. Definição vs. Instância:
o Classe: É uma definição abstrata e um modelo que descreve um
tipo de objeto.
o Objeto: É uma instância concreta da classe, criada e manipulada
em tempo de execução.
2. Propósito:
o Classe: Serve como uma receita ou plano para criar objetos.
o Objeto: Representa uma entidade real que possui um estado e
pode executar ações.
3. Exemplo Prático:
o Classe: Imagine uma classe Pessoa que define atributos como
nome e idade, e métodos como falar() e andar().
o Objeto: Um objeto da classe Pessoa pode ser joao, com o nome
"João" e idade 30, e pode usar o método falar() para dizer "Olá!".
Luiz Otávio Veloso 13
PROGRAMAÇÃO MODULAR
Em resumo, as classes são os templates que definem as propriedades e
comportamentos de um conjunto de objetos, enquanto os objetos são
instâncias concretas dessas classes que possuem estados específicos e
podem executar ações.
2.2. Herança e Abstração
A herança permite que classes compartilhem e reutilizem código,
estabelecendo uma relação hierárquica entre classes. Com ela, uma classe
derivada pode herdar atributos e métodos de uma classe base, promovendo a
especialização e a extensão de funcionalidades. Este conceito facilita a
manutenção do código e possibilita a criação de estruturas mais organizadas e
coerentes.
Já a abstração é o princípio que permite esconder a complexidade dos
detalhes de implementação, expondo apenas as interfaces essenciais. Através
da abstração, os desenvolvedores podem criar classes que definem
comportamentos comuns para diferentes objetos, enquanto as subclasses
concretas fornecem as implementações específicas. Isso simplifica o
desenvolvimento e aumenta a flexibilidade do sistema.
2.2.1. Herança
Definição: Herança é um conceito fundamental em POO que permite
que uma classe (classe derivada ou subclasse) herde atributos e
métodos de outra classe (classe base ou superclasse). Esse mecanismo
facilita a reutilização de código e a criação de hierarquias de classes,
refletindo relações "é um" entre diferentes entidades.
Características:
Reutilização de Código: A subclasse herda todos os atributos e
métodos da superclasse, o que evita a duplicação de código e
facilita a manutenção e a expansão do sistema.
Especialização: A subclasse pode adicionar novos atributos e
métodos ou modificar os comportamentos herdados da
superclasse para fornecer funcionalidades mais específicas.
Polimorfismo: A herança permite o uso de métodos polimórficos,
onde um método pode atuar de diferentes maneiras dependendo
do tipo do objeto que o invoca.
Luiz Otávio Veloso 14
PROGRAMAÇÃO MODULAR
Exemplo Prático: Em um sistema de gestão de veículos, você pode ter
uma classe base Veiculo e uma subclasse Carro que herda de Veículo.
Código: Superclasse (Classe pai)
public class Veiculo {
String cor;
String modelo;
void ligar() {
[Link]("O veículo está ligado.");
}
}
Código: Subclasse (Classe Filha)
public class Carro extends Veiculo {
int numeroDePortas;
void abrirPorta() {
[Link]("Porta do carro aberta.");
}
}
No exemplo acima, a classe Carro herda os atributos cor e modelo e o
método ligar() da classe Veiculo, e também adiciona um novo atributo
numeroDePortas e um método abrirPorta().
De acordo com Furgeri (2013), "a herança permite a criação de uma
nova classe baseada em uma classe existente, proporcionando a
reutilização e a extensão de funcionalidades. A classe derivada herda os
atributos e métodos da classe base, o que promove uma estrutura
hierárquica clara e a reutilização de código" (FURGERI, 2013, p. 84).
Além disso, Furgeri (2013) explica que "a herança facilita a
especialização de classes, permitindo que subclasses possam estender
ou modificar o comportamento da superclasse para atender a requisitos
mais específicos" (FURGERI, 2013, p. 85).
2.2.2. Abstração
Definição: Abstração é o princípio que permite ocultar os detalhes
complexos da implementação e expor apenas as funcionalidades
essenciais de um objeto. Em outras palavras, abstração é o processo de
simplificar a complexidade ao focar nos aspectos relevantes e ignorar os
detalhes irrelevantes.
Características:
Luiz Otávio Veloso 15
PROGRAMAÇÃO MODULAR
Encapsulamento de Complexidade: A abstração permite que os
programadores criem modelos simplificados de objetos e
sistemas, sem se preocupar com os detalhes internos de sua
implementação.
Interface Simples: A abstração fornece uma interface clara e
simples para interagir com um objeto, enquanto esconde a
complexidade interna e os detalhes da implementação.
Criação de Classes Abstratas: Em Java, por exemplo, classes
abstratas são usadas para definir uma estrutura comum para
subclasses, mas não podem ser instanciadas diretamente.
Exemplo Prático: Considerando a classe Veiculo mencionada
anteriormente, você pode introduzir abstração definindo a classe Veiculo
como uma classe abstrata, se desejar que ela apenas forneça uma
estrutura para as subclasses.
Código: Exemplo de Classe Abstrata
public abstract class Veiculo {
String cor;
String modelo;
abstract void ligar(); // Método abstrato, deve ser implementado pelas
subclasses
}
Neste exemplo, a classe Veiculo define um método abstrato ligar(), que
deve ser implementado pelas subclasses. A abstração permite que você
defina a interface geral para todos os veículos, enquanto as subclasses
específicas implementam os detalhes concretos.
Segundo Furgeri (2013), "a abstração permite que o programador se
concentre no que um objeto faz, em vez de como ele faz, simplificando o
desenvolvimento e a manutenção do software" (FURGERI, 2013, p. 90).
Os conceitos de abstração, herança e instanciação de objetos podem
ser vistos na figura 1, onde a classe Carro representa uma classe
abstrata, a classe Fusca herda de carro e as classe Kombi, Brasilia e
Vw1600 herdam de Fusca enquanto kombiDaFeira, brasiliaAmarela e
vw1600Taxi são objetos instanciados a partir das classes citadas
anteriormentes.
Luiz Otávio Veloso 16
PROGRAMAÇÃO MODULAR
Figura 1 - Herança, Abstração e Objetos
2.3. Encapsulamento
Encapsulamento é um dos pilares fundamentais da Programação Orientada a
Objetos (POO). Ele se refere ao conceito de agrupar dados (atributos) e
métodos que manipulam esses dados em uma única unidade, conhecida como
classe, e restringir o acesso direto a alguns dos componentes de um objeto,
garantindo assim a integridade e o controle sobre os dados.
Em Java, o encapsulamento é implementado utilizando modificadores de
acesso, como private, protected, e public. A ideia é tornar os atributos de uma
classe privados, ou seja, acessíveis apenas dentro da própria classe, e
fornecer métodos públicos (geralmente chamados de getters e setters) para
acessar e modificar esses atributos de forma controlada. Isso previne que
dados internos sejam alterados de forma inesperada por outras partes do
programa, o que poderia levar a inconsistências e bugs.
Por exemplo, considere a seguinte classe ContaBancaria:
Luiz Otávio Veloso 17
PROGRAMAÇÃO MODULAR
Código: Classe ContaBancaria
public class ContaBancaria {
private double saldo;
public ContaBancaria(double saldoInicial) {
[Link] = saldoInicial;
}
public double getSaldo() {
return saldo;
}
public void depositar(double valor) {
if (valor > 0) {
saldo += valor;
}
}
public void sacar(double valor) {
if (valor > 0 && valor <= saldo) {
saldo -= valor;
}
}
}
Nesse exemplo, o atributo saldo é privado, e só pode ser acessado ou
modificado através dos métodos públicos getSaldo, depositar e sacar. Esse é
um exemplo típico de encapsulamento, onde o estado interno do objeto é
protegido, garantindo que ele só possa ser alterado de maneiras controladas.
Segundo Deitel e Deitel (2017), o encapsulamento é essencial para a criação
de software robusto, pois "esconde os detalhes de implementação dos usuários
da classe, fornecendo uma interface pública que controla como os dados são
acessados e modificados".
2.3.1. Visibilidade
A visibilidade em Programação Orientada a Objetos (POO) refere-se ao
controle de acesso aos membros de uma classe, como atributos e métodos.
Em Java, a visibilidade é controlada por modificadores de acesso, que
determinam o nível de acesso que outros objetos ou classes têm a esses
membros. Os principais modificadores de acesso em Java são public,
protected, private e o modificador de acesso padrão (ou package-private).
Luiz Otávio Veloso 18
PROGRAMAÇÃO MODULAR
Modificadores de Acesso
1. public: Quando um membro de uma classe é declarado como public,
ele pode ser acessado por qualquer outra classe, independentemente de
seu pacote. Isso significa que tanto classes do mesmo pacote quanto de
pacotes diferentes podem acessar esse membro.
Código: Acesso Público
public class Exemplo {
public int valorPublico;
}
No exemplo acima, o atributo valorPublico pode ser acessado de
qualquer lugar no código.
2. private: O modificador private restringe o acesso aos membros de uma
classe apenas à própria classe. Nem mesmo subclasses ou outras
classes do mesmo pacote podem acessar membros privados
diretamente.
Código: Acesso Privado
public class Exemplo {
private int valorPrivado;
}
Nesse caso, valorPrivado só pode ser acessado e modificado por
métodos dentro da própria classe Exemplo.
3. protected: O modificador protected permite que membros de uma
classe sejam acessados por outras classes no mesmo pacote e por
subclasses, mesmo que essas subclasses estejam em pacotes
diferentes. Isso é particularmente útil em herança, quando se deseja
permitir que subclasses acessem membros da classe base.
Código: Acesso Protegido
public class Exemplo {
protected int valorProtegido;
}
Aqui, valorProtegido pode ser acessado por classes no mesmo pacote e
por qualquer classe que herde de Exemplo.
4. Modificador de Acesso Padrão (Package-private): Se nenhum
modificador de acesso for especificado, o acesso padrão (ou package-
private) é aplicado. Membros com acesso package-private podem ser
acessados por qualquer classe no mesmo pacote, mas não por classes
em pacotes diferentes.
Código: Acesso Package-private
class Exemplo {
int valorPadrao;
}
Luiz Otávio Veloso 19
PROGRAMAÇÃO MODULAR
O atributo valorPadrao pode ser acessado por outras classes no mesmo
pacote, mas não por classes de pacotes diferentes.
Importância da Visibilidade
O controle da visibilidade é crucial para a aplicação de boas práticas de
encapsulamento em POO, permitindo que os desenvolvedores protejam
dados sensíveis e controlem como e quando os membros de uma classe
podem ser acessados ou modificados. Isso melhora a modularidade, a
manutenção do código e a segurança dos dados.
Deitel e Deitel (2017) afirmam que "os modificadores de acesso são
essenciais para o encapsulamento, pois permitem que você controle o
nível de exposição dos membros de uma classe para outras classes,
garantindo assim que a estrutura interna da classe esteja protegida de
acessos indesejados".
Fugeri (2013) complementa que "a escolha adequada dos modificadores
de acesso é fundamental para a construção de software robusto, uma
vez que define claramente as fronteiras entre as diferentes partes de um
sistema".
2.3.2. Metodos Getters (get) e Setters(Set)
Os métodos get e set são utilizados em Java para acessar e modificar os
atributos de uma classe que foram declarados como private. Esses
métodos fazem parte do conceito de encapsulamento, permitindo que os
atributos de uma classe sejam manipulados de forma controlada, sem
expor diretamente os dados internos da classe.
Métodos Getters (Get)
Os métodos getters são usados para recuperar o valor de um atributo
privado de uma classe. Eles geralmente começam com a palavra "get"
seguida pelo nome do atributo com a primeira letra em maiúscula. O
método getter não recebe parâmetros e retorna o valor do atributo
correspondente.
Código: Métodos get
public class ContaBancaria {
private double saldo;
public double getSaldo() {
return saldo;
}
}
Luiz Otávio Veloso 20
PROGRAMAÇÃO MODULAR
No exemplo acima, o método getSaldo permite que o valor do atributo
saldo seja acessado de fora da classe, sem que o código externo tenha
acesso direto ao atributo.
Métodos Setters (Set)
Os métodos setters são usados para modificar o valor de um atributo
privado de uma classe. Eles geralmente começam com a palavra "set"
seguida pelo nome do atributo com a primeira letra em maiúscula. O
método setter recebe um parâmetro que será utilizado para atualizar o
valor do atributo correspondente.
Código: Métodos set
public class ContaBancaria {
private double saldo;
public void setSaldo(double novoSaldo) {
if (novoSaldo >= 0) {
[Link] = novoSaldo;
}
}
}
No exemplo acima, o método setSaldo permite que o valor do atributo
saldo seja modificado de fora da classe, mas com uma validação que
impede a atribuição de valores negativos.
Importância dos Getters e Setters
Os getters e setters proporcionam controle sobre como os atributos de
uma classe são acessados e modificados. Eles permitem, por exemplo,
a implementação de validações, cálculos ou outras operações que
devem ser realizadas quando um atributo é modificado ou acessado.
Isso contribui para a integridade dos dados e a manutenção da lógica de
negócio da aplicação.
Deitel e Deitel (2017) destacam que "os métodos get e set fornecem
uma maneira padronizada de manipular atributos de dados privados,
facilitando o controle e a validação dos dados ao mesmo tempo em que
preservam a integridade da estrutura interna da classe"
2.4. Interfaces e implementação
Em Java, uma interface é um tipo especial de contrato que define um
conjunto de métodos que uma classe deve implementar. Interfaces são
Luiz Otávio Veloso 21
PROGRAMAÇÃO MODULAR
fundamentais na Programação Orientada a Objetos (POO), pois permitem a
definição de um comportamento comum que pode ser implementado por
classes diferentes, sem impor uma hierarquia de herança rígida. Isso
promove a flexibilidade e a reutilização de código.
Definindo Interfaces
Uma interface em Java é declarada com a palavra-chave interface. Dentro
de uma interface, você define métodos abstratos, que são métodos sem
corpo, ou seja, sem implementação. As classes que implementam essa
interface são obrigadas a fornecer a implementação desses métodos.
Exemplo de uma interface:
Código: Definição da interface Animal
public interface Animal {
void som(); // método sem implementação
void mover(); // método sem implementação
}
No exemplo acima, a interface Animal define dois métodos abstratos: som() e
mover(). Qualquer classe que implemente essa interface deverá fornecer
implementações para esses métodos.
Implementação de Interfaces
Para que uma classe implemente uma interface, ela deve utilizar a palavra-
chave implements seguida do nome da interface. A classe então deve fornecer
implementações concretas para todos os métodos definidos na interface.
Exemplo de implementação:
Código: Implementação da interface Animal
public class Cachorro implements Animal {
@Override
public void som() {
[Link]("O cachorro late");
}
@Override
public void mover() {
[Link]("O cachorro corre");
}
}
Nesse exemplo, a classe Cachorro implementa a interface Animal e fornece
implementações específicas para os métodos som() e mover().
Luiz Otávio Veloso 22
PROGRAMAÇÃO MODULAR
EXPLICANDO
O @Override é uma anotação em Java usada para indicar que um método em
uma classe está sobrescrevendo (ou substituindo) um método de uma
superclasse ou de uma interface que a classe está implementando. Essa
anotação serve principalmente para melhorar a legibilidade do código e fornecer
verificação em tempo de compilação.
Função do @Override
1. Clareza: A anotação @Override torna claro para os outros desenvolvedores (e para
você mesmo, no futuro) que o método em questão está sobrescrevendo um método
herdado. Isso melhora a compreensão do código, deixando explícito que a intenção do
desenvolvedor é modificar o comportamento de um método definido em uma
superclasse ou interface.
2. Verificação em Tempo de Compilação: Se o método não corresponder exatamente a
um método na superclasse ou na interface, o compilador gerará um erro. Isso ajuda a
evitar erros comuns, como erros de digitação no nome do método ou na lista de
parâmetros. Sem a anotação @Override, esses erros podem passar despercebidos e
levar a comportamentos inesperados.
Importância das Interfaces
Interfaces são essenciais para a criação de código flexível e reutilizável. Elas
permitem que diferentes classes possam ser tratadas de forma polimórfica, ou
seja, diferentes classes que implementam a mesma interface podem ser
manipuladas de forma uniforme, independentemente de suas implementações
concretas.
Deitel e Deitel (2017) explicam que "interfaces permitem que diferentes classes
compartilhem um contrato comum, garantindo que todas as classes que
implementam a interface forneçam as mesmas funcionalidades,
independentemente de como essas funcionalidades são realizadas".
Fugeri (2013) destaca que "as interfaces são uma das ferramentas mais
poderosas em Java para a criação de sistemas modulares, pois permitem que
componentes de software sejam desenvolvidos de maneira independente e
conectados de acordo com as necessidades do projeto".
Uso Comum de Interfaces
Um uso comum de interfaces é em APIs, onde uma interface define um
conjunto de métodos que podem ser implementados por diferentes classes.
Isso permite que diferentes implementações possam ser facilmente
substituídas ou alteradas sem afetar o restante do sistema.