O que é programação estruturada?

No vasto universo do desenvolvimento de software, compreender os diferentes paradigmas de programação é crucial para qualquer profissional da área. Entre eles, a programação estruturada se destaca como um dos pilares que moldaram a forma como construímos sistemas. Mas o que exatamente ela significa e por que é tão relevante até hoje?

Neste texto, vamos entender o que é esse paradigma, como ele surgiu, quais são suas principais características e estruturas, e por que ele ainda é fundamental no aprendizado e, na prática da programação.

1 – O que é programação estruturada?

A programação estruturada é um paradigma de programação que preconiza a criação de programas organizados e de fácil entendimento, utilizando um conjunto limitado de estruturas de controle de fluxo. Ela surgiu como uma resposta aos desafios da programação não estruturada, que resultava em códigos complexos e difíceis de manter, popularmente conhecidos como “código espaguete”.

O objetivo principal da programação estruturada é simplificar o processo de desenvolvimento e depuração, promovendo a clareza e a modularidade do código. Em vez de saltos incondicionais (como o GOTO), ela incentiva o uso de construções bem definidas.

A programação estruturada parte da ideia central de que podemos desenvolver qualquer software, por mais complexo que ele seja, utilizando apenas três estruturas fundamentais de controle de fluxo.

  • Sequência: o sistema executa as instruções em ordem linear, uma após a outra. Essa é a forma mais básica de organizar um código, onde o sistema lê e processa cada comando na sequência em que ele aparece.
  • Seleção (ou Condição): permite que o programa execute diferentes blocos de código com base em uma condição. As estruturas mais comuns são o if/else (se/senão) e o switch/case (escolha/caso). Isso dá ao programa a capacidade de tomar decisões.
  • Iteração (ou Repetição/Loop): permite executar um bloco de código repetidamente enquanto uma condição for verdadeira ou por um número específico de vezes. Exemplos incluem for (para), while (enquanto) e do-while (faça-enquanto). Essas estruturas são essenciais para automatizar tarefas repetitivas.

Linguagens tradicionais como C, Pascal, Fortran, e ALGOL, por exemplo, adotam amplamente o paradigma da programação estruturada. Além delas, atualmente, muitas linguagens modernas como JavaScript, Python, PHP e outras também incorporam os princípios desse paradigma.

Em suma, a programação estruturada defende que a combinação dessas três estruturas básicas é suficiente para construir algoritmos eficientes, claros e fáceis de manter, independentemente da complexidade do problema.

2 – Quando esse paradigma de programação surgiu?

A programação estruturada ganhou força na década de 1960, impulsionada principalmente pelos trabalhos do cientista da computação Edsger W. Dijkstra. Na época, o desenvolvimento de software enfrentava uma crise de produtividade e qualidade. Os programas eram escritos de forma caótica, com muitos saltos GOTO que dificultavam o rastreamento do fluxo de execução e a identificação de erros. Desse modo, a manutenção e a expansão dos sistemas tornava-se um verdadeiro pesadelo.

Dijkstra, em seu famoso artigo “Go To Statement Considered Harmful” (Declaração Go To Considerada Prejudicial) de 1968, criticou severamente o uso indiscriminado do GOTO, argumentando que ele levava a programas “espalhafatosos” e ininteligíveis. Sua proposta era justamente a utilização das três estruturas de controle que mencionamos: sequência, seleção e iteração.

Assim, o surgimento da programação estruturada representou um avanço significativo para a engenharia de software, promovendo a modularidade, legibilidade e manutenibilidade do código. Ao dividir o programa em blocos menores e mais gerenciáveis (como funções e procedimentos), tornou-se mais fácil para os desenvolvedores entenderem, testarem e corrigirem partes específicas do sistema sem afetar o todo.

3 – Exemplo de programação estruturada

Com a finalidade de ilustrar os conceitos da programação estruturada, vamos analisar um exemplo simples de código que verifica se um número é par ou ímpar. Observe que, para fins didáticos, desenvolvemos esse código em três linguagens diferentes: JavaScript, Python e Java.

Ademais, note em cada linguagem utilizada, como a modularização das funções e o uso das estruturas de controle de fluxo acontecem na prática.

JSPythonC++

// Função para verificar se o número é par ou ímpar 
function verificarParOuImpar(numero) {

    // Utiliza uma estrutura condicional (if/else) para determinar a paridade
    if (numero % 2 === 0) {
        return "O número é par.";
    } else {
        return "O número é ímpar.";
    }
}

// Função principal para executar o programa 
function main() {
    var entradaValida = false;

    // Loop de repetição (while) para obter uma entrada numérica válida do usuário
    while (!entradaValida) {
        var numero = prompt("Digite um número inteiro: ");
        // Verifica se o valor inserido é de fato um número
        if (!isNaN(numero)) {
            entradaValida = true;
        } else {
            console.log("Por favor, digite um número válido.");
        }
    }

    // Invoca a função que checa a paridade do número (fluxo sequencial)
    console.log(verificarParOuImpar(numero));
}

// Inicia a execução do programa chamando a função principal (fluxo sequencial)
main();

# Função para verificar se o número é par ou ímpar
def verificar_par_ou_impar(numero):
    
    # Aplica uma condição (if/else) para checar se o número é par
    if numero % 2 == 0:
        return "O número é par."
    else:
        return "O número é ímpar."

# Função principal para executar o programa
def main():
    entrada_valida = False

    # Itera (loop while) até que o usuário forneça um número inteiro válido
    while not entrada_valida:
        numero_str = input("Digite um número inteiro: ")
        # Confere se a entrada fornecida é um número
        if numero_str.isdigit() or (numero_str.startswith('-') and numero_str[1:].isdigit()):
            numero = int(numero_str)
            entrada_valida = True
        else:
            print("Por favor, digite um número válido.")

    # Aciona a função que determina se o número é par ou ímpar (execução sequencial)
    print(verificar_par_ou_impar(numero))

# Garante que a função principal seja chamada ao executar o script
if __name__ == "__main__":
    main()

#include <iostream>
#include <string>  
#include <limits>   

// Função para verificar se o número é par ou ímpar
std::string verificarParOuImpar(int numero) {
    // Implementação de uma estrutura condicional (if/else) para validação
    if (numero % 2 == 0) {
        return "O número é par.";
    } else {
        return "O número é ímpar.";
    }
}

// Função principal para executar o programa
int main() {
    bool entradaValida = false;
    int numero = 0; // Inicializa a variável para ser usada no escopo

    // Estrutura de repetição (while) para garantir uma entrada numérica correta
    while (!entradaValida) {
        std::cout << "Digite um número inteiro: ";
        // Tenta ler um número inteiro
        if (std::cin >> numero) {
            entradaValida = true;
        } else {
            std::cout << "Por favor, digite um número válido." << std::endl;
            // Limpa o estado de erro do cin e descarta o restante da linha de entrada
            std::cin.clear();
            std::cin.ignore(std::numeric_limits::max(), '\n');
        }
    }

    // Chama a função que verifica a paridade do número (fluxo de execução)
    std::cout << verificarParOuImpar(numero) << std::endl;

    return 0; // Indica que o programa terminou com sucesso
}

Nestes exemplos, podemos observar claramente os princípios da Programação Estruturada aplicados na prática:

  • Modularidade: nas três linguagens que utilizamos em nosso exemplo, o código é dividido em duas funções, cada uma delas com sua responsabilidade específica.
    • verificarParOuImpar: função para verificar se o número é par ou ímpar.
    • main: função principal para executar o programa.
  • Estruturas de Controle: utilizamos if/else para tomar decisões (verificar par/ímpar e validar a entrada) e um while para repetir a solicitação de entrada até que um número válido seja fornecido.
  • Sequência: observe em todos os códigos que, tanto a organização das instruções dentro de cada função e as suas respectivas chamadas ocorrem em uma ordem lógica e sequencial.

Apesar de novos paradigmas terem surgido, como a programação orientada a objetos, a programação estruturada continua sendo a base para a escrita de código legível, eficiente e fácil de manter. Portanto, entender seus conceitos é fundamental para qualquer desenvolvedor que busca construir sistemas robustos e de alta qualidade.

Conclusão

A programação estruturada representou um divisor de águas na história do desenvolvimento de software, ao propor uma abordagem mais clara, lógica e modular para a construção de programas.

Mesmo que outros paradigmas tenham surgido, como a programação orientada a objetos e programação funcional, por exemplo, os princípios da programação estruturada continuam sendo amplamente utilizados e valorizados.

Por fim, podemos compreender que dominar a programação estruturada é um passo essencial para qualquer desenvolvedor, pois ela fornece a base para escrever códigos mais organizados, legíveis e fáceis de manter — qualidades indispensáveis na criação de sistemas robustos e duradouros.

Espero que este conteúdo seja útil de alguma forma para você. Se gostou do conteúdo, compartilhe com seus amigos e aproveite para conhecer mais sobre programação aqui!

O que é um paradigma de programação?

Os paradigmas de programação representam diferentes estilos ou abordagens para escrever e organizar código. Cada paradigma define um modelo conceitual que influencia diretamente como problemas são analisados, como soluções são projetadas e como o software é implementado e mantido. Compreender esses paradigmas é essencial para todo desenvolvedor que deseja escrever código mais eficiente, legível e sustentável.

Neste artigo, vamos explorar os principais paradigmas de programação, suas características, exemplos e quando aplicá-los. Começamos com uma base conceitual.

1 – O que é um paradigma de programação?

Para entendermos o conceito de paradigma de programação, devemos primeiramente compreender o significado da palavra paradigma.  O termo “paradigma” vem do grego paradeigma, que significa modelo ou exemplo. De acordo com o dicionário Priberam, a palavra paradigma pode ser definida como: “Algo que serve de exemplo geral ou de modelo.” 

No contexto da computação, um paradigma de programação é um modelo conceitual que determina a forma de escrever códigos. Eles fornecem diretrizes sobre como estruturar algoritmos, organizar dados e controlar o fluxo de execução.

Linguagens de programação podem seguir um único paradigma (como Haskell, voltada exclusivamente ao paradigma funcional) ou ser multiparadigma, combinando diferentes abordagens (como Python e JavaScript, que suportam paradigmas imperativo, orientado a objetos e funcional).

2. Principais Paradigmas de Programação

Ao longo da evolução da computação, diversos paradigmas foram desenvolvidos para atender a diferentes demandas e contextos. A seguir, vamos conhecer os principais:

2.1 Programação imperativa

A programação imperativa é uma das formas mais tradicionais de programar. Nesse paradigma, o código é escrito como uma sequência de instruções que alteram o estado do sistema. É como dar ordens ao computador: determinando passo a passo, quais comandos ele deve executar e em qual ordem.

Características:

  • Foco em como resolver o problema.
  • Uso explícito de variáveis, laços e estruturas de controle.
  • Controle detalhado do fluxo de execução.

Exemplos de linguagens: C, C++, Java, Python, JavaScript, PHP.

2.2 Programação declarativa

A programação declarativa é conhecida como o oposto da imperativa. Nesse paradigma, o foco recai sobre o que deve ser feito, e não em como deve ser feito. Assim, o programador descreve ao computador a sequência lógica a executar e o resultado que se espera alcançar, sem determinar o fluxo de controle. O sistema se encarrega de executar as ações necessárias para alcançar esse resultado.

Portanto, na programação declarativa, as instruções possuem uma abordagem mais generalista e não detalham exatamente cada etapa da execução.

Características:

  • Abstrai o controle do fluxo de execução.
  • Minimiza efeitos colaterais.
  • Ideal para tarefas onde regras e resultados são mais importantes que o processo.

Exemplos: SQL, HTML, CSS, XML, Prolog.

2.3 Programação estruturada

Derivada do paradigma imperativo, a programação estruturada introduz boas práticas como a divisão do código em blocos lógicos (modularização do software) e está fundamentada no uso de estruturas básicas de controle de fluxo: sequências, decisões e repetições.

Na programação estruturada, entende-se que para resolver um problema de forma eficiente, ele deve ser quebrado em partes menores (subprogramas ou módulos). Cada uma dessas partes será responsável por resolver uma determinada fração do problema maior. Esse é o conceito de modularização de software, onde todo programa é composto por um conjunto de programas menores interconectados, chamados de módulos ou subprogramas. 

Os softwares construídos com esse paradigma usam as seguintes estruturas de controle de fluxo: 

Sequências: as instruções são escritas na sequência em que serão executadas.  

Decisões/condições: blocos de código são executados somente quando determinadas condições são cumpridas. Usa-se estruturas como IF – ELSE e SWITCH – CASE. 

Repetições: blocos de código são executados várias vezes até que uma condição seja cumprida. Usa-se estruturas como FOR, WHILE e Recursividade. 

Características:

  • Redução de código espaguete.
  • Organização por funções e blocos.
  • Uso de estruturas como if/else, switch, for, while.

Exemplos: C, C++, C#, Java, Python, PHP.

2.4 Programação procedural

Também derivada do paradigma imperativo, a programação procedural agrupa as instruções em procedimentos (também chamados de funções, métodos ou sub-rotinas). Cada procedimento realiza uma tarefa específica, promovendo modularização e reutilização de código. Esses procedimentos devem ser acionados sequencialmente, durante a execução do software.

Características:

  • Organização baseada em chamadas de funções.
  • Separação de responsabilidades.
  • Facilidade de manutenção e testes.

Exemplos: C, C++, PHP, Python, Go.

2.5 Programação orientada a objetos (POO)

A programação orientada a objetos também deriva da programação imperativa. É o paradigma mais difundido e usado na atualidade. A POO modela o software com base em objetos do mundo real. A ideia central é replicar o mundo real através de códigos que usam classes e objetos para representar tudo aquilo que existe. 

As classes são os modelos que representam coisas reais. Já os objetos são instâncias dessas classes, que encapsulam dados (atributos) e comportamentos (métodos). Esse paradigma favorece a reutilização, escalabilidade e organização do código.

Características:

  • Encapsulamento, herança, polimorfismo e abstração.
  • Interação entre objetos para compor o comportamento do sistema.
  • Facilita a modelagem de domínios complexos.

Exemplos: Java, C++, C#, Python, Ruby, PHP.

2.6 Programação funcional

Baseada em conceitos matemáticos, a programação funcional trata funções como cidadãos de primeira classe. Ela evita estados mutáveis e prioriza funções puras — que sempre produzem o mesmo resultado para os mesmos argumentos.

Características:

  • Imutabilidade e ausência de efeitos colaterais.
  • Uso intensivo de funções e composição.
  • Paradigma adequado para concorrência e paralelismo.

Exemplos: Haskell, Elixir, Scala, JavaScript (parcialmente), Kotlin (parcialmente).

2.7 Programação lógica

A programação lógica também deriva da programação declarativa. Ela está baseada no uso de regras e lógica formal para resolução de problemas. O programador declara fatos e regras, e o motor de inferência da linguagem deduz as conclusões.

Características:

  • Usa lógica de predicados para inferir respostas.
  • Muito usada em inteligência artificial e sistemas especialistas.

Exemplo: Prolog.

2.8 Programação reativa

Também derivada da programação declarativa, a programação reativa possui foco na construção de sistemas orientados a eventos e fluxo de dados assíncronos. É ideal para aplicações que exigem respostas imediatas a mudanças de estado, como interfaces gráficas e aplicações em tempo real.

Características:

  • Fluxos de dados e propagação de mudanças.
  • Combina bem com programação funcional e orientada a eventos.

Exemplos: RxJS, Reactor (Java), Angular (RxJS), Kotlin Flow.

3. Qual o melhor paradigma de programação?

Depois de ler este artigo, talvez você esteja se perguntando qual o melhor paradigma de programação? E a resposta é: depende. Nenhum paradigma é universalmente melhor, tudo depende do contexto. A escolha do paradigma ideal inclui fatores como:

  • A natureza do problema.
  • Requisitos de desempenho e manutenção.
  • Equipe envolvida e conhecimento técnico.
  • Linguagens e tecnologias adotadas pela organização.

Na prática, muitos projetos adotam uma abordagem híbrida, usando múltiplos paradigmas conforme a necessidade. É comum, por exemplo, combinar POO com técnicas funcionais em linguagens modernas como Python, Kotlin ou JavaScript.

Portanto, cabe aos profissionais de desenvolvimento entender os conceitos básicos de cada paradigma e usá-los de acordo com as suas necessidades.

Conclusão

Conhecer os paradigmas de programação é essencial para desenvolver software de maneira mais eficaz. Eles moldam a maneira como pensamos e resolvemos problemas computacionais.

Ao dominar diferentes paradigmas, o desenvolvedor ganha flexibilidade, capacidade analítica e autonomia para escolher a melhor abordagem para cada desafio. Lembre-se: aprender novos paradigmas amplia sua visão como desenvolvedor e contribui para um código mais limpo, sustentável e adaptável.

Espero que este conteúdo seja útil de alguma forma para você. Se gostou do conteúdo, compartilhe com seus amigos e aproveite para conhecer mais sobre programação aqui!

Pilares da programação orientada a objetos

A programação orientada a objetos (POO) é um paradigma amplamente usado no desenvolvimento de softwares. Adotado por linguagens de programação populares como Java e C#, por exemplo, o paradigma é aplicável a uma ampla variedade de projetos, desde os mais simples até os mais complexos.

A orientação a objetos busca aproximar o mundo real ao mundo da programação de computadores. A ideia central deste paradigma é que tudo aquilo que existe no mundo real pode ser representado em código através de objetos.

Para exemplificar, pense no sistema de uma universidade. Neste sistema, um aluno é representado como um objeto, com atributos e comportamentos específicos. Os atributos do objeto ‘aluno’ podem incluir matrícula, nome, data de nascimento, endereço, curso, disciplinas, entre outros. Os comportamentos podem englobar a realização de matrículas em disciplinas, cancelamento de matrículas, listagem de disciplinas matriculadas ou concluídas, cálculo de médias semestrais, entre outros. Da mesma forma, um professor, uma disciplina, um curso e qualquer outra entidade do sistema universitário pode ser representada como um objeto, com seus atributos e comportamentos próprios. 

Este artigo não possui a finalidade de detalhar os conceitos da POO, o foco aqui serão os quatro pilares fundamentais deste paradigma. Se você estiver interessado em conhecer mais sobre POO, clique aqui para ler um artigo que escrevi sobre esse assunto.  

1 – Os 4 pilares da programação orientada a objetos 

Os quatro pilares da POO representam quatro características que linguagens de programação e projetos devem atender para que sejam classificados como orientados a objetos. Essas características que formam os pilares da orientação a objetos são: abstração, encapsulamento, herança e polimorfismo.  

Abaixo vamos conhecer cada uma dessas características e na sequência vamos ver um projeto simples de Java, que simula operações bancária, criado para fins didáticos e que com certeza irá lhe ajudar no entendimento dos conceitos aqui apresentados.  

1.1 – Abstração 

A abstração está relacionada a capacidade de representar as coisas que existem no mundo real como objetos em um software. Os objetos devem ser identificados (nomeados) conforme sua função e devem possuir atributos (ou propriedades) e métodos (ou comportamentos) devidamente definidos e configurados.  
Pense, por exemplo, em um sistema bancário. Nesse sistema há um objeto chamado ContaBancaria, no qual temos atributos como número da conta, titular e saldo e temos também métodos como sacar, depositar, transferir e consultar saldo. Esse objeto é uma abstração de algo do mundo real (contas bancárias) representado em um software. 

1.2 – Encapsulamento 

O encapsulamento está ligado, diretamente, a segurança das informações do sistema. Ele pode ser entendido como uma forma de ocultação de dados.  

O encapsulamento restringe os acessos aos atributos e métodos dos objetos, evitando que ocorram acessos desnecessários que possam ocasionar mudanças indesejadas no sistema. A ideia é que cada objeto é responsável por manter a integridade dos seus dados (atributos), permitindo que sejam acessados somente através de métodos públicos específicos para essa finalidade.  

Vamos pensar no objeto ContaBancaria que apresentamos na explicação do conceito de abstração. É prudente que os seus atributos número da conta, titular e saldo sejam encapsulados e, portanto, acessados somente pelos seus métodos, a fim de evitar a exposição e manipulação dessas informações em outras partes do código. 

Nas linguagens de programação orientadas a objetos, o encapsulamento é alcançado através do uso de métodos de acesso (getters e setters) e modificadores de acesso (como public, private e protected). 

1.3 – Herança 

Herança é a característica de um objeto ou classe receber (herdar) atributos e métodos de um objeto ou classe superior. Assim, surge o conceito de classe mãe e classes filhas, as quais herdam atributos e métodos da classe mãe.  

Pensando novamente no exemplo do sistema bancário, nele temos uma classe chamada ContaCorrente  que é uma classe filha de ContaBancaria. Desta forma, a classe ContaCorrente herda os atributos (número da conta, titular e saldo) e os métodos (sacar, depositar, transferir e consultar saldo) da sua classe mãe ContaBancaria. Essa mesma regra valeria para outras classes filhas de ContaBancaria como, por exemplo, ContaDigital e ContaSalario.  

Note que, apesar de conta corrente, conta poupança e conta salário serem tipos diferentes de contas bancárias, todas compartilham os mesmos atributos e métodos básicos da classe ContaBancaria. Essa relação entre as classes, permitida pela herança, possibilita a reutilização de código, deixando o projeto mais organizado, legível e performático. 

1.4 – Polimorfismo 

O polimorfismo pode ser entendido como a característica da POO em que um mesmo método pode ter comportamentos diferentes conforme o objeto em que foi herdado.  

Para que possamos entender esse conceito, vamos imaginar a seguinte situação: o método consultarSaldo da classe ContaBancaria é herdado pelos objetos de conta corrente, conta digital e conta salário. A princípio, o método consultarSaldo deveria ter o mesmo comportamento em todos os tipos de contas, porém, nas contas correntes ele deve apresentar além do saldo disponível, o saldo de cheque especial daquela conta. Para contas poupança e salário deve ser apresentado somente o saldo da conta, pois, não há oferta de produtos de crédito nessas contas. Veja que se trata do mesmo método apresentando comportamentos diferentes conforme o objeto em que ele é usado.

2 – Entendendo os pilares da programação orientadas a objetos 

Após conhecermos a definição dos 4 pilares da programação orientada a objetos, vamos ver abaixo uma sequência de códigos escritos em Java, que simulam algumas transações bancárias. Esses códigos nos ajudarão a visualizar os pilares de programação orientada a objetos de forma prática, melhorando nossa compreensão a respeito do assunto.

2.1 – A estrutura do projeto 

Para executar os códigos em sua máquina você vai precisar instalar: a JDK 17 ou superior e uma IDE compatível com Java. Eu recomendo utilizar o IntelliJ ou o Visual Studio Code (se optar por esta opção não esqueca de instalar a extensão da linguagem Java). 

Depois de instaladas as ferramentas necessárias, você pode criar um projeto com a seguinte estrutura ou fazer o download do projeto disponível aqui:

Estrutura do projeto do sistema do banco.

Como podemos ver na imagem acima, a estrutura é bem simples e fácil de entender:

– Contas: é a pasta onde estão os arquivos que representam os tipos de contas que o banco fornece; 

– Enums: como o próprio nome sugere, é a pasta onde fica o arquivo de Enums da aplicação; 

– Models: pasta onde está a classe principal da aplicação; 

– Service: pasta onde fica a função que executa as ações da aplicação; 

– BancoApplicaton: arquivo onde está a função main() responsável por executar a aplicação. 

2.2- Entendendo os pilares da programação orientada a objetos no código

Abaixo, vou compartilhar alguns trechos dos códigos do projeto onde poderemos visualizar a aplicação de cada um dos pilares da programação orientada a objetos. Vamos lá!

public class ContaBancaria { 

    // Atributos da classe 
    private int numeroConta; 
    private String titularConta; 
    private double saldo; 


    // Construtor da classe 
    public ContaBancaria(int numeroConta, String titularConta,
                        double saldoInicial, TipoConta tipoConta) { 
        this.numeroConta = numeroConta; 
        this.titularConta = titularConta; 
        this.saldo = saldoInicial; 
    } 
  

    // Métodos da classe 
    public void depositar(double valor) { 
        saldo += valor; 
        System.out.println("Conta "+numeroConta+" - Depósito de R$" + valor + " realizado com sucesso!"); 
    } 

  
    public void sacar(double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            System.out.println("Conta "+numeroConta+" - Saque de R$" + valor + " realizado com sucesso!"); 
        } else { 
            System.out.println("Saldo insuficiente para saque de R$" + valor); 
        } 
    } 


    public void consultarSaldo() { 
        System.out.println("Conta: "+numeroConta+" / "+"Titular: "+titularConta+" / "+"Saldo atual: R$" + saldo); 
    } 

  
    public void transferir(ContaBancaria destino, double valor) { 
        if (valor <= saldo) { 
            saldo -= valor; 
            destino.saldo += valor; 
            System.out.println("Conta "+numeroConta+" - Transferência de R$" + valor + " realizada para a conta " + destino.numeroConta); 
        } else { 
            System.out.println("Saldo insuficiente para transferência de R$" + valor); 
        } 
    } 

  
    public double getSaldo() { 
        return saldo; 
    } 

    public int getNumeroConta() { 
        return numeroConta; 
    } 

    public String getTitularConta() { 
        return titularConta; 
    } 
} 

A abstração pode ser vista no código da classe ContaBancaria que replica algo que existe no mundo real (contas bancárias) através de um código de programação que será usado em um sistema computacional.  

Observe que a classe ContaBancaria foi construída com três atributos (número da conta, titular da conta e saldo) e quatro métodos (depositar, sacar, consulta saldo e transferir) que compõem a estrutura de uma conta bancária. A conta bancária é algo que existe no mundo real e utilizamos em nosso dia a dia e está sendo abstraída em um código computacional. 

Nesta mesma classe, também conseguimos ver o encapsulamento. Observe que os atributos numeroConta, titularConta e saldo foram definidos como private e, portanto, não podem ser acessados fora da classe ContaBancaria. A única forma de serem manipulados por códigos externos a essa classe é através dos getters definidos no código: getNumeroConta(), getTitularConta() e getSaldo(). 

public class ContaCorrente extends ContaBancaria { 
    
    private double chequeEspecial; 

    public ContaCorrente(int numeroConta, String titularConta, double saldoInicial, double chequeEspecial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.CORRENTE); 
        this.chequeEspecial = chequeEspecial; 
    } 

    public double getChequeEspecial() { 
        return chequeEspecial; 
    } 

  
    @Override 
    public void consultarSaldo() { 
        double saldoTotal = getSaldo() + chequeEspecial; 
        System.out.println("Conta: " + getNumeroConta() + " / " + "Titular: " + getTitularConta() + " / " +  "Saldo atual: R$" + getSaldo() + " / Cheque Especial: R$" + chequeEspecial + " / Saldo Total: R$" + saldoTotal); 
    } 
}
public class ContaDigital extends ContaBancaria { 
    public ContaDigital(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.DIGITAL); 
    } 
} 
public class ContaSalario extends ContaBancaria { 
    public ContaSalario(int numeroConta, String titularConta, double saldoInicial) { 
        super(numeroConta, titularConta, saldoInicial, TipoConta.SALARIO); 
    } 
} 

A herança pode ser vista nas classes ContaCorrente, ContaDigital e ContaSalario. Observe que, em todas elas, possuímos o comando extends ContaBancaria. Quando usamos a palavra reservada extends, indicamos ao Java que aquela classe irá herdar os recursos de uma outra classe, que, em nosso exemplo, é a classe ContaBancaria.  

Já o polimorfismo pode ser visto na classe ContaCorrente. Nesta classe é usada a anotação @Override que indica o polimorfismo na linguagem Java. Note que abaixo desta anotação, o comportamento do método consultarSaldo da classe ContaBancária está sendo alterado para apresentar uma informação exclusiva das contas correntes, que é o saldo de cheque especial do cliente.  

Conclusão 

De forma simples e resumida, neste artigo conhecemos os quatro pilares da programação orientada a objetos, os quais representam as características básicas das linguagens de programação orientadas a objetos.   

Recomendo que você analise, estude e edite os códigos para conseguir uma melhor compreensão dos pilares da POO. Procure entender estes conceitos e aprofundar seus conhecimentos neste paradigma de programação que é um dos mais utilizados no desenvolvimento de softwares.

Espero que o conteúdo aqui apresentado seja útil de alguma forma para você. Em caso de dúvidas, sugestões ou reclamações fique à vontade para entrar em contato.