Herança em Java: Um Estudo Prático com Clube de Fitness
Caros alunos e alunas, hoje vamos desvendar um dos pilares da Programação Orientada a Objetos (POO) em Java: o conceito de herança. Para solidificar nosso aprendizado, mergulharemos em um projeto prático — um sistema de gestão para um clube de fitness. Este clube oferece diferentes tipos de associação, VIP e Normal, e queremos modelar essa realidade em nosso código. Preparados? Vamos iniciar!
Configurando o Ambiente no NetBeans com Maven
Primeiramente, é imprescindível configurar nosso ambiente de desenvolvimento. Estaremos utilizando o NetBeans com Maven, uma escolha excelente para gerenciar nossos projetos Java e suas dependências.
- Abra o NetBeans.
- Vá em
File > New Project
. - Selecione
Java with Maven
. - Escolha
Project from Archetype
e selecione o archetypemaven-archetype-quickstart
. - Nomeie o projeto como
InheritanceDemo
. - Certifique-se de que o GroupId é
com.mycompany
e o ArtifactId éinheritancedemo
.
Com esses passos, teremos nosso projeto pronto para começarmos a programar.
Entendendo a Classe Base: Member.java
No coração do nosso sistema, reside a classe Member
. Ela representa um membro genérico do nosso clube de fitness e contém informações e comportamentos comuns a todos os membros. Vamos dissecar essa classe:
1 2 3 4 5 6 7 8 9 |
//Member.java package com.mycompany.inheritancedemo; import java.util.Scanner; public class Member { // ... código omitido para brevidade ... } |
Aqui, declarou-se a classe Member
no pacote com.mycompany.inheritancedemo
. Note que há uma importação da classe Scanner
do pacote java.util
, pois manipularemos a entrada do usuário.
No corpo da classe Member
, definimos campos que armazenam informações sobre o membro e métodos que determinarão o comportamento do membro.
Construtores
Possuímos dois construtores: um sem parâmetros e outro que recebe nome, ID e o ano de filiação como parâmetros. O construtor é uma “função especial” que é chamada quando um objeto é instanciado.
Métodos
Os métodos getDiscount
e setDiscount
são exemplos de métodos de acesso, também conhecidos como getters e setters. Eles servem para ler e modificar campos privados, mantendo o encapsulamento.
Derivando Classes Especiais: VipMember e NormalMember
Em nosso projeto, temos dois tipos específicos de membros que herdam características da classe Member
. Isso é onde a herança brilha, permitindo-nos estender e especializar a classe base.
VipMember.java
1 2 3 4 5 6 |
package com.mycompany.inheritancedemo; public class VipMember extends Member { // ... código omitido para brevidade ... } |
A palavra-chave extends
é usada para declarar que VIPMember
é uma subclasse de Member
. Assim, VIPMember
herda todos os campos e métodos de Member
.
Observem o uso da anotação @Override
. Ela sinaliza que o método calculateAnnualFee
está sendo sobrescrito, ou seja, VIPMember fornece sua própria implementação deste método.
NormalMember.java
A classe NormalMember
segue uma estrutura semelhante à VIPMember
, também sobrescrevendo o método calculateAnnualFee
. A diferença reside na forma como a taxa anual é calculada.
Integrando as Peças: InheritanceDemo.java
A classe InheritanceDemo
com o método main
é o ponto de entrada do nosso programa. Quando executamos o aplicativo, é esse método que o Java chama.
1 2 3 |
// InheritanceDemo.java // ... código omitido para brevidade ... |
Aqui, instanciamos objetos das classes VIPMember
e NormalMember
, invocamos métodos para calcular suas taxas anuais e exibir suas informações.
Vamos decifrar a classe Member
. Essa classe é a base do nosso sistema e retrata os membros genéricos de um clube de fitness. Todos os membros, independente do tipo, terão as características comuns definidas nesta classe.
1 2 3 4 5 6 7 8 9 10 11 |
//Member.java package com.mycompany.inheritancedemo; import java.util.Scanner; public class Member { // ... declarações de variáveis ... // ... construtores ... // ... métodos ... } |
Essa é a estrutura básica da nossa classe Member
. Ela está dentro do pacote com.mycompany.inheritancedemo
e importa a classe Scanner
do pacote java.util
para capturar a entrada do usuário.
Declaração de Variáveis
1 2 3 4 5 6 7 |
public String welcome = "Bem-vindo à ABC Fitness"; protected double annualFee; private String name; private int memberID; private int memberSince; private int discount; |
Aqui temos seis campos (variáveis) que armazenam informações sobre um membro genérico do clube de fitness. Cada um desses campos tem diferentes níveis de acesso:
public
: Este campo pode ser acessado de qualquer lugar do nosso código. Ele é usado para a mensagem de boas-vindas, que é comum a todos os membros.protected
: Este campo pode ser acessado dentro da mesma classe, em qualquer classe que herde dessa classe e em qualquer classe no mesmo pacote. Usamosprotected
paraannualFee
porque queremos que as subclasses (VIP e Normal) possam acessar e modificar este valor.private
: Estes campos só podem ser acessados dentro da mesma classe. Utilizamosprivate
para os detalhes específicos do membro comoname
,memberID
,memberSince
ediscount
para encapsular essas informações e protegê-las de alterações indesejadas.
Construtores
1 2 3 4 5 6 7 8 9 10 11 |
public Member() { System.out.println("Construtor Pai sem parâmetros"); } public Member(String pName, int pMemberID, int pMemberSince) { System.out.println("Construtor Pai com 3 parâmetros"); this.name = pName; this.memberID = pMemberID; this.memberSince = pMemberSince; } |
Os construtores são métodos especiais em uma classe que são chamados quando um objeto dessa classe é criado. Eles têm o mesmo nome da classe e não têm tipo de retorno. Nossa classe Member
possui dois construtores:
- O primeiro é um construtor sem parâmetros, que imprime uma mensagem no console. Este construtor será chamado quando criamos um membro sem fornecer nenhum detalhe específico.
- O segundo construtor aceita três parâmetros e atribui esses valores aos campos correspondentes da classe. Este construtor será chamado quando quisermos criar um membro fornecendo seu nome, ID e ano de inscrição. O termo
this
é usado para se referir ao objeto atual, diferenciando os campos da classe dos parâmetros do construtor.
Métodos Getter e Setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public double getDiscount() { return discount; } public void setDiscount() { Scanner input = new Scanner(System.in); System.out.print("Por favor, entre com a senha de admin: "); String password = input.nextLine(); if (!password.equals("abcd")) { System.out.println("Senha inválida. Você não tem autoridade para editar o desconto."); } else { System.out.print("Por favor, entre com o desconto: "); this.discount = input.nextInt(); } } |
O método getDiscount()
é um getter que retorna o valor do campo discount
.
O método setDiscount()
é um setter que modifica o valor do campo discount
. Aqui, pedimos ao usuário que insira uma senha de administrador para verificar se eles têm a permissão necessária para alterar o desconto. Se a senha for correta, pedimos que insiram o novo valor de desconto e atualizamos o campo discount
com esse valor.
Métodos Comportamentais
1 2 3 4 5 6 7 8 9 10 11 |
public void displayMemInfo() { System.out.println("Nome do Membro: " + name); System.out.println("ID do Membro: " + memberID); System.out.println("Membro Desde " + memberSince); System.out.println("Taxa Anual: " + annualFee); } public void calculateAnnualFee() { annualFee = 0; } |
O método displayMemInfo()
é responsável por imprimir as informações do membro no console
Aqui está o código Completo da Classe Member
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// Member.java package com.mycompany.inheritancedemo; import java.util.Scanner; public class Member { public String welcome = "Bem-vindo à ABC Fitness"; protected double annualFee; private String name; private int memberID; private int memberSince; private int discount; public Member() { System.out.println("Construtor Pai sem parâmetros"); } public Member(String pName, int pMemberID, int pMemberSince) { System.out.println("Construtor Pai com 3 parâmetros"); this.name = pName; this.memberID = pMemberID; this.memberSince = pMemberSince; } public double getDiscount() { return discount; } public void setDiscount() { Scanner input = new Scanner(System.in); System.out.print("Por favor, entre com a senha de admin: "); String password = input.nextLine(); if (!password.equals("abcd")) { System.out.println("Senha inválida. Você não tem autoridade para editar o desconto."); } else { System.out.print("Por favor, entre com o desconto: "); this.discount = input.nextInt(); } } public void displayMemInfo() { System.out.println("Nome do Membro: " + name); System.out.println("ID do Membro: " + memberID); System.out.println("Membro Desde " + memberSince); System.out.println("Taxa Anual: " + annualFee); } public void calculateAnnualFee() { annualFee = 0; } } |
A Classe VipMember
Agora que já compreendemos a classe base Member
, vamos explorar uma de suas subclasses, a VipMember
. Lembrando, a classe VipMember
representa um membro VIP do clube de fitness, que possui algumas características e comportamentos específicos.
1 2 3 4 5 6 |
package com.mycompany.inheritancedemo; public class VipMember extends Member { // ... código omitido para brevidade ... } |
Observe a declaração da classe. A palavra-chave extends
é usada para indicar que VipMember
é uma subclasse de Member
. Isso significa que VipMember
herda todos os campos e métodos da classe Member
, permitindo também adicionar ou sobrescrever funcionalidades.
Construtor
1 2 3 4 |
public VipMember(String pName, int pMemberID, int pMemberSince) { super(pName, pMemberID, pMemberSince); } |
Aqui temos o construtor de VipMember
, que recebe três parâmetros: nome, ID do membro e o ano de filiação. Dentro do construtor, vemos uma chamada para super(pName, pMemberID, pMemberSince)
. A palavra-chave super
é usada para invocar o construtor da superclasse Member
. Ou seja, quando um novo VipMember
é criado, este construtor chama o construtor da classe Member
que corresponde aos parâmetros fornecidos, o que garante que esses campos sejam devidamente inicializados.
Método Sobrescrito
1 2 3 4 5 |
@Override public void calculateAnnualFee() { annualFee = 1200 - (1200 * getDiscount() / 100); } |
Este é um exemplo de sobrescrita de métodos. A anotação @Override
indica que estamos fornecendo uma nova implementação para o método calculateAnnualFee
que já existe na superclasse Member
.
O cálculo da taxa anual para um membro VIP é diferente do membro genérico. A taxa é inicialmente definida como 1200 e, em seguida, um desconto é aplicado. O método getDiscount()
, que é herdado da superclasse Member
, é usado para obter o valor do desconto.
Portanto, embora VipMember
seja uma subclasse de Member
e herde seus campos e métodos, ele pode personalizar seu comportamento sobrescrevendo métodos conforme necessário. Isso é uma demonstração do poder da herança: permite-nos reutilizar código, mas ainda assim personalizar onde precisamos. É assim que conseguimos modelar uma variedade de membros com características e comportamentos únicos, mas mantendo um design de código elegante e sustentável.
Aqui está o código completo da Classe VipMember
1 2 3 4 5 6 7 8 9 10 11 12 13 |
package com.mycompany.inheritancedemo; public class VipMember extends Member { public VipMember(String pName, int pMemberID, int pMemberSince) { super(pName, pMemberID, pMemberSince); } @Override public void calculateAnnualFee() { annualFee = 1200 - (1200 * getDiscount() / 100); } } |
A Classe NormalMember
Agora vamos para a classe NormalMember
. Assim como a VIPMember
, ela também é uma subclasse de Member
e representa um membro normal no clube de fitness. Embora um membro normal compartilhe muitas características com um membro VIP, ele também possui suas próprias particularidades.
1 2 3 4 5 6 7 |
// NormalMember.java package com.mycompany.inheritancedemo; public class NormalMember extends Member { // ... código omitido para brevidade ... } |
A palavra-chave extends
é utilizada para indicar que NormalMember
é uma subclasse de Member
. Portanto, NormalMember
herda todos os campos e métodos de Member
, mas também permite adicionar ou sobrescrever características e comportamentos próprios dos membros normais.
Construtor
1 2 3 4 |
public NormalMember(String pName, int pMemberID, int pMemberSince) { super(pName, pMemberID, pMemberSince); } |
O construtor de NormalMember
aceita três parâmetros: nome, ID de membro e o ano de filiação. Usamos a palavra-chave super
para invocar o construtor da superclasse Member
com esses parâmetros. Esta chamada ao construtor pai garante que esses campos sejam devidamente inicializados na criação de um novo NormalMember
.
Método Sobrescrito
1 2 3 4 5 |
@Override public void calculateAnnualFee() { annualFee = 100 + (100 * getDiscount() / 100); } |
Este é outro exemplo da sobrescrita de métodos. A anotação @Override
sinaliza que estamos fornecendo uma implementação específica para o método calculateAnnualFee
que foi herdado da classe Member
.
O cálculo da taxa anual para um membro normal é diferente daquela de um membro genérico ou VIP. O método getDiscount()
, herdado de Member
, é usado para obter o valor de desconto. A taxa é definida como 100 acrescida de 100 multiplicado pelo desconto em porcentagem.
Neste ponto, pode-se observar como a herança possibilita a criação de subclasses distintas a partir de uma classe base. Embora NormalMember
e VIPMember
herdem muitas características de Member
, cada um fornece uma implementação única do método calculateAnnualFee
, que reflete as diferenças entre os dois tipos de membros. Com isso, conseguimos representar tanto membros VIP quanto normais de maneira eficaz, preservando as semelhanças e respeitando as diferenças.
Aqui está o Código Completo da Classe NormalMember:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// NormalMember.java package com.mycompany.inheritancedemo; public class NormalMember extends Member { public NormalMember(String pName, int pMemberID, int pMemberSince) { super(pName, pMemberID, pMemberSince); } @Override public void calculateAnnualFee() { annualFee = 100 + (100 * getDiscount() / 100); } } |
A Classe InheritanceDemo e Implementação do Método main()
O método main()
em uma aplicação Java é o ponto de entrada do programa. É onde a execução começa quando você executa a classe contendo o método. Em nosso programa de clube de fitness, o método main()
será responsável por instanciar objetos de membros do clube, calcular suas taxas anuais e exibir suas informações.
Vamos detalhar a implementação do método main()
do nosso programa InheritanceDemo
:
Instanciando Membros
1 2 3 |
VIPMember vip = new VIPMember("Giovanni", 101, 2021); NormalMember normal = new NormalMember("Ana", 102, 2019); |
Neste trecho, criamos dois objetos: um para VIPMember
e outro para NormalMember
. O construtor de cada classe é chamado e recebe três parâmetros: o nome do membro, o ID do membro e o ano desde que ele se tornou um membro. Essas informações são passadas para a superclasse Member
através da palavra-chave super
, que invoca o construtor correspondente na classe Member
.
Calculando a Taxa Anual
1 2 3 |
vip.calculateAnnualFee(); normal.calculateAnnualFee(); |
Depois de instanciar os objetos dos membros, chamamos o método calculateAnnualFee()
de cada um. Este método é uma versão sobrescrita do método presente na classe base Member
. Para VIPMember
, este método calcula a taxa anual com um determinado desconto, enquanto para NormalMember
, ele calcula a taxa anual de uma maneira potencialmente diferente (conforme a implementação de calculateAnnualFee()
para essa subclasse).
Exibindo Informações dos Membros
1 2 3 |
vip.displayMemInfo(); normal.displayMemInfo(); |
Agora que as taxas anuais foram calculadas, exibimos as informações de cada membro chamando o método displayMemInfo()
. Este método imprime detalhes do membro, como o nome, ID do membro, o ano desde que se tornaram membros e a taxa anual. Como este método não foi sobrescrito nas subclasses, o método da superclasse Member
é utilizado aqui.
Resumo do Método main()
Em resumo, o método main()
do nosso programa faz o seguinte:
- Cria objetos para diferentes tipos de membros.
- Calcula as taxas anuais de acordo com o tipo de membro.
- Exibe as informações dos membros.
Através da herança, fomos capazes de estabelecer uma relação entre membros VIP e Normais com a classe Member
, reutilizando código e comportamento. O método main()
demonstra o uso prático dessas classes e métodos numa aplicação Java simples.
Aqui está a implementação completa do método main()
na classe InheritanceDemo
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class InheritanceDemo { public static void main(String[] args) { VIPMember vip = new VIPMember("Giovanni", 101, 2021); NormalMember normal = new NormalMember("Ana", 102, 2019); vip.calculateAnnualFee(); normal.calculateAnnualFee(); vip.displayMemInfo(); normal.displayMemInfo(); } } |
Cada linha do código no main()
executa uma ação específica e juntas formam um fluxo lógico que define a execução do nosso programa. Ao compreender o papel de cada linha no método main()
, você está um passo mais perto de dominar a lógica de programação em Java.
Conclusão: O Poder da Herança
O projeto que acabamos de construir ilustra a elegância e a eficiência da herança. As classes VipMember
e NormalMember
reutilizam a estrutura da classe Member
, cada uma adicionando suas especificidades.
Por meio deste exemplo prático, espero que tenham captado a essência da herança em Java. Ela facilita a expansão e a manutenção de nossos programas ao permitir o compartilhamento de código comum e a criação de uma hierarquia clara de classes. Continuem praticando e explorando, pois cada linha de código escrita fortalece sua compreensão da POO em Java. Lembrem-se, a prática leva à perfeição!