- Published on
Entendendo Programação Orientada a Objetos (POO) com Exemplos em TypeScript
- Authors
- Name
- Alysson Rodrigues
- @066aly
1. Classes e Objetos
Uma classe é um modelo para criar objetos. Ela define propriedades e métodos que os objetos criados a partir da classe terão. Um objeto é uma instância de uma classe.
- Exemplo: A classe
Carro
define um modelo para carros com uma propriedademodelo
e um métodoligarMotor
.meuCarro
é um objeto (uma instância) criado a partir da classeCarro
, especificamente um modelo 'Sedan'.
// vehicle.ts
// Classe representando um Carro
class Carro {
// Propriedade privada para armazenar o modelo do carro
private modelo: string;
// Construtor para inicializar o modelo do carro
constructor(modelo: string) {
this.modelo = modelo;
}
// Método público para simular a partida do motor do carro
public ligarMotor(): void {
console.log('Motor ligado');
// Registra uma mensagem indicando que o motor foi ligado
}
}
// Cria uma instância da classe Carro com o modelo 'Sedan'
const meuCarro = new Carro('Sedan');
// Registra a instância do Carro no console
console.log(meuCarro); // Saída: Carro { modelo: 'Sedan' }
2. Encapsulamento
Encapsulamento é o agrupamento de dados (propriedades) e métodos que operam sobre esses dados dentro de uma única unidade (uma classe). Também envolve restringir o acesso direto a alguns dos componentes de um objeto, o que é um aspecto chave do ocultamento de dados (data hiding). Modificadores de acesso como public
, private
e protected
controlam essa visibilidade.
private
: Membros são acessíveis apenas de dentro da classe que os define. Exemplo: EmContaBancaria
,numeroConta
eobterNumeroConta
são privados. Você não pode acessarconta.numeroConta
diretamente de fora da classe. O acesso é controlado através de métodos públicos comoexibirNumeroContaParcial
. Similarmente,modero
emCarro
é privado.public
: Membros são acessíveis de qualquer lugar (este é o padrão se nenhum modificador for especificado). Exemplo:exibirNumeroContaParcial
emContaBancaria
,nome
efazerSom
emAnimal
,raça
elatir
emCachorro
,ligarMotor
emCarro
, edirigir
emCarro
são públicos.protected
: Membros são acessíveis dentro da classe que os define e por instâncias de subclasses (classes derivadas). Exemplo: EmVeículo
(A primeira imagem abaixo),nivelCombustivel
everificarCombustivel
são protegidos. A classeCarro
, que estendeVehicle
, pode acessarverificarCombustivel
diretamente dentro de seu métododirigir
, mas você não poderia chamartheCar.checkFuel()
de fora da classeCarClass
ouVehicle
.
// vehicle.ts
// Classe base representando um Veículo genérico
class Veiculo {
// Propriedade protegida para armazenar o nível de combustível do veículo
protected nivelCombustivel: number;
// Construtor para inicializar o nível de combustível do veículo
constructor(nivelCombustivel: number) {
this.nivelCombustivel = nivelCombustivel;
}
// Método protegido para verificar se o nível de combustível é suficiente
// Retorna verdadeiro se o nível de combustível for maior que 10
protected verificarCombustivel(): boolean {
return this.nivelCombustivel > 10;
}
}
// Classe derivada representando um tipo específico de Veículo: Carro
// Nota: Renomeei 'CarClass' para 'Carro' para seguir o padrão do exemplo anterior
// e para ser mais idiomático em português.
class Carro extends Veiculo {
// Método público para simular a condução do carro
public dirigir(): void {
// Verifica se o nível de combustível é suficiente usando o método herdado verificarCombustivel
if (this.verificarCombustivel()) {
console.log('Dirigindo...'); // Saída se o nível de combustível for suficiente
} else {
console.log('Combustível baixo!'); // Saída se o nível de combustível for insuficiente
}
}
}
// Cria uma instância da classe Carro com um nível inicial de combustível de 11
const meuCarro = new Carro(11); // Usei 'meuCarro' como no exemplo anterior
// Chama o método dirigir na instância do Carro
// Saída: 'Dirigindo...' porque o nível de combustível (11) é maior que 10
meuCarro.dirigir();
// Classe representando uma Conta Bancária
class ContaBancaria {
// Propriedade privada para armazenar o número da conta
// Nota: É convenção usar 'string' (tipo primitivo) em vez de 'String' (objeto) em TypeScript.
private numeroConta: string;
// Construtor para inicializar o número da conta
constructor(numeroConta: string) {
this.numeroConta = numeroConta;
}
// Método privado para obter o número completo da conta
// Este método não é acessível fora da classe
private obterNumeroConta(): string {
return this.numeroConta;
}
// Método público para exibir o número da conta parcialmente mascarado
public exibirNumeroContaParcial(): void {
// Registra o número da conta com os primeiros 4 dígitos mascarados
// Usando 'this.numeroConta' que agora está em português
console.log('Número da Conta: ****' + this.numeroConta.slice(4));
}
}
// Cria uma instância da classe ContaBancaria com um número de conta específico
const conta = new ContaBancaria('456049584456');
// Chama o método exibirNumeroContaParcial para mostrar o número da conta mascarado
// Saída: 'Número da Conta: ****9584456'
conta.exibirNumeroContaParcial();
3. Herança
Herança permite que uma classe (subclasse ou classe derivada) herde propriedades e métodos de outra classe (superclasse ou classe base). Isso promove a reutilização de código. A palavra-chave extends
é usada para estabelecer a herança.
- Exemplo 1: A classe
Cachorro
estende a classeAnimal
.Cachorro
herda a propriedadenome
e o métodofazerSom
deAnimal
. Ela também adiciona sua própria propriedade (raça
) e método (latir
), e sobrescreve o métodofazerSom
. A chamadasuper(nome)
no construtor deCachorro
invoca o construtor deAnimal
. - Exemplo 2: A
Carro
(no exemplo acima) estendeVeiculo
, herdandonivelCombustivel
everificarCombustivel
. - Exemplo 3:
Circulo
eRetangulo
ambos estendemForma
, herdando o método basecalcularArea
(embora eles o sobrescrevam).
// Classe base representando um Animal genérico
class Animal {
// Propriedade pública para armazenar o nome do animal
public nome: string; // Usando 'string'
// Construtor para inicializar o nome do animal
constructor(nome: string) {
this.nome = nome;
}
// Método para simular o animal fazendo um som genérico
fazerSom(): void { // Adicionado tipo de retorno 'void' para clareza
console.log('Fazendo algum som genérico...');
}
}
// Classe derivada representando um tipo específico de Animal: Cachorro
class Cachorro extends Animal {
// Propriedade pública para armazenar a raça do cachorro
public raca: string; // Usando 'string'
// Construtor para inicializar o nome e a raça do cachorro
// Chama o construtor da classe pai (Animal) para definir o nome
constructor(nome: string, raca: string) {
super(nome); // Chama o construtor da classe pai (Animal)
this.raca = raca;
}
// Método sobrescrito (override) para simular o cachorro fazendo um som específico
public fazerSom(): void { // Sobrescrevendo o método da classe pai
console.log('Au au!'); // Som mais específico para cachorro
}
// Método específico da classe Cachorro para simular o latido
latir(): void {
console.log('Latindo...');
}
}
// Cria uma instância da classe Cachorro com nome e raça
const cachorro = new Cachorro('Pimpolho', 'Salsicha');
// Chama o método latir na instância do Cachorro
// Saída: 'Latindo...'
cachorro.latir();
// Exemplo adicional: chamando o método sobrescrito
// Saída: 'Au au!'
// cachorro.fazerSom();
4. Polimorfismo
Polimorfismo ("muitas formas") permite que objetos de diferentes classes sejam tratados como objetos de uma superclasse ou interface comum. Frequentemente se manifesta de duas maneiras: Sobrescrita de Método (Method Overriding) e Interfaces.
- Sobrescrita de Método: Uma subclasse fornece uma implementação específica para um método que já está definido em sua superclasse. Exemplo 1:
Cachorro
sobrescreve o métodofazerSom
herdado deAnimal
para fornecer um som específico de cachorro ('Au au!!') em vez do genérico. Exemplo 2:Circulo
eRetangulo
ambos sobrescrevem o métodocalcularArea
deForma
para realizar cálculos específicos para sua geometria. A funçãoimprimirArea
recebe qualquer objetoShape
, mas chama o métodocalcularArea
sobrescrito correto com base no tipo real do objeto (Circulo
ouRetangulo
) passado para ela. - Interfaces (Polimorfismo Implícito via Duck Typing/Tipagem Estrutural em TypeScript): Uma interface define um contrato para uma certa estrutura ou comportamento. Classes podem implementar interfaces, garantindo que forneçam métodos específicos. Funções podem então operar sobre qualquer objeto que cumpra o contrato da interface, independentemente de sua classe específica.
/**
* Classe base representando uma forma geométrica.
*/
class Forma {
/**
* Calcula a área de uma forma.
* @returns A área calculada como um número. Retorna 0 na classe base,
* pois uma forma genérica não tem área definida.
* Classes derivadas devem sobrescrever este método.
*/
public calcularArea(): number {
return 0;
}
}
/**
* Representa uma forma de círculo, estendendo a classe base `Forma`.
* Fornece funcionalidade para calcular a área do círculo.
*/
class Circulo extends Forma {
// O construtor declara e inicializa a propriedade pública 'raio' diretamente.
constructor(public raio: number) {
super(); // Chama o construtor da classe pai (Forma)
}
/**
* Calcula a área do círculo (π * raio²).
* @returns A área calculada como um número.
*/
public calcularArea(): number {
return Math.PI * this.raio * this.raio;
// Ou: return Math.PI * Math.pow(this.raio, 2);
}
}
/**
* Representa uma forma de retângulo, estendendo a classe base `Forma`.
* Fornece funcionalidade para calcular a área do retângulo.
*/
class Retangulo extends Forma {
// O construtor declara e inicializa as propriedades públicas 'largura' e 'altura'.
constructor(public largura: number, public altura: number) {
super(); // Chama o construtor da classe pai (Forma)
}
/**
* Calcula a área do retângulo (altura * largura).
* @returns A área calculada como um número.
*/
public calcularArea(): number {
return this.altura * this.largura;
}
}
/**
* Registra (imprime) a área de uma forma geométrica dada no console.
* @param forma - Uma instância de uma classe que estende a classe base `Forma` (ex: Circulo, Retangulo).
* Graças ao polimorfismo, o método `calcularArea` correto será chamado.
*/
function imprimirArea(forma: Forma): void { // Especifica o tipo de retorno void
// Chama o método calcularArea do objeto específico (Circulo ou Retangulo)
console.log("Área:", forma.calcularArea());
}
// Cria instâncias das formas específicas
const circulo = new Circulo(5); // Um círculo com raio 5
const retangulo = new Retangulo(10, 4); // Um retângulo 10x4
// Imprime a área de cada forma usando a mesma função
imprimirArea(circulo); // Saída esperada: Área: 78.5398...
imprimirArea(retangulo); // Saída esperada: Área: 40
Estes exemplos demonstram como os princípios da POO ajudam a organizar o código em unidades reutilizáveis, gerenciáveis e extensíveis usando classes, encapsulamento, herança e polimorfismo em TypeScript.
Usei isso para aprender POO a fim de passar no meu exame da faculdade. Se você quiser se aprofundar no entendimento, do começo ao fim, estes guias abrangentes podem te ajudar a começar.
Estou meio nervoso com essa coisa de blog, então deixe seu feedback abaixo, seria muito apreciado!