[PROIBIDO PARA SÊNIORS] Usando POO para tirar um sistema de transportadora do zero
POO na teoria dá pano pra manga. Uma busca básica aqui no Tabnews e você encontrará boas discussões sobre a base desse paradigma. Mas como esse post é PROIBIDO PARA SÊNIORS, vou me abster das discussões filosóficas regadas a bom vinho e bom jazz 😄. O objetivo aqui é mostrar como você, iniciante, pode usar POO para sair do zero.
Isso não significa que, em algum momento, você não deveria se preocupar com essas discussões. Afinal, o consumo delas é que te faz "sair do Júnior". Mas, first things first: não dá pra entrar na discussão sobre o basico de POO se você não consegue sequer usar o básico de POO. Então, tenha foco! , força e fé!
O Problema
Li em algum lugar aqui mesmo no tabnews sobre a dificuldade de algum novato em conseguir tirar um problema do papel e transformar em código. As aulinhas básicas de POO me ajudaram bastante com isso, porque graças a elas que consegui compreender o que é abstração, que em parcas palavras, significa pegar algo do mundo real e modelar apenas com as características necessárias para solução do problema. E pensar nos aspectos de um programa como se eles fosse objetos ajuda bastante. Não é a bala de prata que pode ser utilizada em qualquer situação, mas ajuda bastante.
Vamos começar: nosso objetivo é modelar um programa que faz a gestão de transportes de uma dessas transportadoras que coletam cargas nesses e-commerces famosos e entregam em nossas casas. No mercado, esses sistemas são classificados como TMS (Transportation Management System), e vamos pedir ao ChatGPT para criar um briefing simples, como se ele fosse um cliente solicitando a nós um desses programas.
Para isso, o ChatGPT pode ser ótimo! Ele agiria como um cliente da nossa fábrica de software e nós podemos ir conversando com ele. Você até pode dizer que o ChatGPT alucina às vezes, mas quem disse que o nosso cliente não costuma fazer o mesmo? 😄
Caso se interesse, segue o link do prompt que usei.
O Projeto
Depois de algumas conversas com o ChatGPT para refinar objetivos (e essa rotina é muito comum no trato com os clientes), conseguimos definir um ponto de partida:
Principais Funcionalidades:
- Cadastro de Viagens: Permitir a inserção manual ou automática de viagens, especificando o galpão de origem, os destinos e as informações dos veículos envolvidos.
- Roteirização Automática: Implementar um algoritmo de roteirização automática que determine a melhor sequência de entregas, considerando distâncias, trânsito e prioridades.
- Visualização de Rotas: Fornecer uma interface intuitiva para visualização das rotas planejadas, incluindo detalhes como pontos de parada, distâncias estimadas e horários previstos.
- Atualização em Tempo Real: Manter as informações das viagens atualizadas em tempo real, permitindo ajustes dinâmicos com base em mudanças nas condições de tráfego ou outros imprevistos.
- Atribuição de Veículos: Possibilitar a associação eficiente de veículos disponíveis às viagens, levando em consideração capacidade de carga e requisitos específicos de cada entrega.
Cada tópico desse pode muito bem ser uma sprint, ou seja, vamos fazer entregas cíclicas para o cliente, onde cada sprint é um cíclo, e em cada cíclo desses, vamos entregar uma desses funcionalidades. Vamos começar com a primeira delas?
Abstraíndo o problema
Uma dica: transforme o seu objetivo em uma frase, e tente mapear os substantivos e verbos desta frase. Aliás, a nossa primeira sprint é uma frase, certo? Vamos dar uma olhada:
- Cadastro de Viagens: Permitir a inserção manual ou automática de viagens, especificando o galpão de origem, os destinos e as informações dos veículos envolvidos.
Atenção nos substantivos "viagens", "galpão", "destinos", "veículos".
E atenção aos verbos relacionados aos substantivos: "inserir viagens", "especificar galpões", "especificar destinos", "especificar informações dos veículos".
Se você já estudou o mínimo de OO, então você sabe que objetos sempre possuem algo, fazem algo e estão de alguma forma.
Vamos supor, então, que cada um dos nossos subsantivos acima será um objeto. Essa suposição, na verdade, está errada. Mas depois eu explico o porquê. Mas, vamos errado mesmo no início.
Vamos começar com "viagem". O que uma viagem possui?
Objeto: viagem
- Possui:
- Partida,
- Destino,
- hora de partida,
- hora de chegada,
- carga (no contexto de uma transportadora),
- local de partida,
- local de chegada,
- indicador de sucesso (precisamos marcar se a viagem atingiu o seu objetivo, ou seja, se a carga foi entregue),
- e ocorrências (observações do tipo por que a viagem atrasou? a carga foi roubada? o veículo bateu? a carga chegou avariada?, etc...)
E galpão? O que possui um galpão?
Objeto: galpão
- Possui:
- Nome (tipo "CD1", ou "CD Jundiaí", etc..)
- Endereço
- indicador de atividade (será que o galpão está funcionando? será que foi desativado? interditado? etc...)
Destino, no caso, está relacionado ao local indicado para entrega pelo cliente.
Objeto: destino
- Possui:
- Nome do cliente
- Endereço
E, por fim, veículo
Objeto: veículo
- Possui:
- Tipo (caminhão, bicicleta, carro, etc...)
- Placa
- Marca
- Modelo
- Peso Máximo Permitido
- Cubagem máxima permitida (a medida do bau, da carroceria, porta malas, etc.)
- Estado (está em conformidade para uso? pode ser que não esteja, pode ser que esteja quebrado, sem documento, etc...)
Olha só quanta coisa já temos pra codar! Bora lá?
Codando
Atributos
Para os exemplo, vou usar Java, "a linguagem oficial do POO". Mas isso pode ser feito em Python, JS ou qualquer outra linguagem que tenha suporte a POO.
Mas, antes, vamos falar sobre "classes".
Se você já estudou POO, sabe bem que "veículo" tá mais pra classe do que pra objeto (não vou me ater a explicar os porquês, pesquise!). Vamos criar a classe veículo?
//Veiculo.java
public class Veiculo{
private Long id;
private Enum tipo;
private String placa;
private Enum marca;
private Enum modelo;
private int pesoMaximo
private int cubagemMaxima;
private boolean ok;
}
Vamos criar logo viagem, porque tenho comentários a fazer sobre "destino" e "galpão".
//Viagem.java
public class Viagem{
//Timestamp é um tipo contido na lib 'java.sql.Timestamp' que registra data e hora
//vamos controlar o agendamento planejado e realizado, para podermos comparar a eficiencia das entregas
private Long id;
private Timestamp hrPlanejadaPartida;
private Timestamp hrPlanejadaChegada;
private Timestamp hrRealizadaPartida;
private Timestamp hrRealizadaChegada;
private String numCarga; //poderiamos criar a classe 'Carga', que conteria info das cargas, como nr de NF, por exemplo
private String nomeGalpao;
private Endereco enderecoDestino; //poderiamos criar a classe endereço tbem?
private String ocorrencias;
}
Vamos tratar sobre "destino" e "galpão". Concorda que tanto um destino de um cliente quanto um galpão são locais? E que eles possuem os mesmos atributos, como endereço, por exemplo?
Mas, ao mesmo tempo, não podemos dizer que um cliente foi interditado pela prefeitura, certo?
Neste caso, podemos definir uma classe abstrata "Local" e herdar dela "Cliente" (nosso destino) e "Galpão".
//Local.java
abstract class Local{
protected Endereco endereco //pq "protected"? Pesquise! É importante!
protected String nome;
//vamos logo definir construtor e métodos
public Local(Endereco e){
this.endereco = e;
}
public abstract void setEndereco(Endereco e){
this.endereco = e;
}
public abstract Endereco getEndereco(){
return this.endereco;
}
public abstract void setNome(String nome){
this.nome = nome;
}
public abstract String getNome(){
return nome;
}
}
Essa classe abstrata, como você já deve ter estudado, serve apenas como um ponto comum para as classes que virão. Se, a respeito do seu projeto, você pode afirmar que "varias coisas são uma coisa, porém essas várias coisas não são iguais entre si", pode ser que você esteja lidando com um caso de classe abstrata.
Cliente e Galpão são locais, mas Cliente não é Galpão e Galpão não é cliente
Bora criar, então, as classes Galpão e CLiente:
//Galpao.java
public class Galpao extends Local{
private Long id;
private boolean ativo;
//lembrando que não precisamos implementar nome e endereço aqui pq já virão 'herdados' de Local
}
//Cliente.java
public class Cliente extends Local{
//como no nosso projeto original, cliente só tem mesmo nome e endereço,
//então não precisamos implementar nenhum atributo alem do id.
//Endereço e nome virão herdados de Local.
private Long id;
}
Métodos
Assim como atributos estão substantivos, métodos estão para verbos. Métodos, normalmente, estão relacionados a alguma ação do objeto. E precisamos tratar dessa ação no nosso código também.
Vamos tomar, por exemplo, a classe Galpão. O que Galpão tem? Registro de atividade e endereço. Essas informações podem ser alteradas com o tempo (set). Também pode ocorrer de precisarmos exibir essas informações para quem solicitá-las (get).
//Galpao.java
public class Galpao extends Local{
private boolean ativo;
private Long id;
}
public Galpao(){ //construtor
super(); //pq "super"? Pesquise! É importante!
this.ativo = true;
}
public void setAtivo(){
if this.ativo == false ativo = true;
else ativo == false;
}
public boolean getAtivo(){
return ativo;
}
public Long getId(){
return id;
}
//lembrando que o get e set de nome e endereço já foram implementados em "Local"
Esses controles de alteração e exibição devem ser colocados sempre que for prevista essas atividades, em todas as classes. Por exemplo, não estamos prevendo alterar o registro de ID do Galpão, logo não há necessidade de implementar um setID()
por aqui.
Vamos implementar os métodos das demais classes?
//Veiculo.java
public class Veiculo {
private Long id;
private Enum tipo;
private String placa;
private Enum marca;
private Enum modelo;
private int pesoMaximo;
private int cubagemMaxima;
private boolean ok;
// Construtor padrão
public Veiculo() {
}
// Construtor com todos os campos
public Veiculo(Long id, Enum tipo, String placa, Enum marca, Enum modelo, int pesoMaximo, int cubagemMaxima, boolean ok) {
this.id = id;
this.tipo = tipo;
this.placa = placa;
this.marca = marca;
this.modelo = modelo;
this.pesoMaximo = pesoMaximo;
this.cubagemMaxima = cubagemMaxima;
this.ok = ok;
}
// Métodos getters e setters para todos os campos
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Enum getTipo() {
return tipo;
}
public void setTipo(Enum tipo) {
this.tipo = tipo;
}
public String getPlaca() {
return placa;
}
public void setPlaca(String placa) {
this.placa = placa;
}
public Enum getMarca() {
return marca;
}
public void setMarca(Enum marca) {
this.marca = marca;
}
public Enum getModelo() {
return modelo;
}
public void setModelo(Enum modelo) {
this.modelo = modelo;
}
public int getPesoMaximo() {
return pesoMaximo;
}
public void setPesoMaximo(int pesoMaximo) {
this.pesoMaximo = pesoMaximo;
}
public int getCubagemMaxima() {
return cubagemMaxima;
}
public void setCubagemMaxima(int cubagemMaxima) {
this.cubagemMaxima = cubagemMaxima;
}
public boolean isOk() {
return ok;
}
public void setOk(boolean ok) {
this.ok = ok;
}
}
//Viagem.java
import java.sql.Timestamp;
public class Viagem {
private Long id;
private Timestamp hrPlanejadaPartida;
private Timestamp hrPlanejadaChegada;
private Timestamp hrRealizadaPartida;
private Timestamp hrRealizadaChegada;
private String numCarga;
private String nomeGalpao;
private Endereco enderecoDestino;
private String ocorrencias;
// Construtor padrão
public Viagem() {
}
// Construtor com todos os campos
public Viagem(Long id, Timestamp hrPlanejadaPartida, Timestamp hrPlanejadaChegada, Timestamp hrRealizadaPartida,
Timestamp hrRealizadaChegada, String numCarga, String nomeGalpao, Endereco enderecoDestino,
String ocorrencias) {
this.id = id;
this.hrPlanejadaPartida = hrPlanejadaPartida;
this.hrPlanejadaChegada = hrPlanejadaChegada;
this.hrRealizadaPartida = hrRealizadaPartida;
this.hrRealizadaChegada = hrRealizadaChegada;
this.numCarga = numCarga;
this.nomeGalpao = nomeGalpao;
this.enderecoDestino = enderecoDestino;
this.ocorrencias = ocorrencias;
}
// Métodos getters e setters para todos os campos
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Timestamp getHrPlanejadaPartida() {
return hrPlanejadaPartida;
}
public void setHrPlanejadaPartida(Timestamp hrPlanejadaPartida) {
this.hrPlanejadaPartida = hrPlanejadaPartida;
}
public Timestamp getHrPlanejadaChegada() {
return hrPlanejadaChegada;
}
public void setHrPlanejadaChegada(Timestamp hrPlanejadaChegada) {
this.hrPlanejadaChegada = hrPlanejadaChegada;
}
public Timestamp getHrRealizadaPartida() {
return hrRealizadaPartida;
}
public void setHrRealizadaPartida(Timestamp hrRealizadaPartida) {
this.hrRealizadaPartida = hrRealizadaPartida;
}
public Timestamp getHrRealizadaChegada() {
return hrRealizadaChegada;
}
public void setHrRealizadaChegada(Timestamp hrRealizadaChegada) {
this.hrRealizadaChegada = hrRealizadaChegada;
}
public String getNumCarga() {
return numCarga;
}
public void setNumCarga(String numCarga) {
this.numCarga = numCarga;
}
public String getNomeGalpao() {
return nomeGalpao;
}
public void setNomeGalpao(String nomeGalpao) {
this.nomeGalpao = nomeGalpao;
}
public Endereco getEnderecoDestino() {
return enderecoDestino;
}
public void setEnderecoDestino(Endereco enderecoDestino) {
this.enderecoDestino = enderecoDestino;
}
public String getOcorrencias() {
return ocorrencias;
}
public void setOcorrencias(String ocorrencias) {
this.ocorrencias = ocorrencias;
}
}
//Cliente.java
public class Cliente extends Local {
private Long id;
// Construtor padrão
public Cliente() {
}
// Construtor com o campo id
public Cliente(Long id) {
this.id = id;
}
// Métodos getters e setters para o campo id
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Concluíndo
Aqui foi um exemplo simples de como usar abstração e o básico de POO para sair do zero e preencher a tela preta que amedronta muitos novatos. Há outras formas de construir isso, outras abordagens de ensino e tudo isso pode ser colocado em discussão, porém creio que isto aqui já ajuda muito quem quer sair do zero e não sabe como.
Propositalmente não expliquei tudo aqui. Não falei, por exemplo, por que colocamos private
em algumas coisas e public
em outras. Se você já estudou OO, então já deve saber o porquê disso. Se você não sabe, então é hora de abrir outra aba e prosseguir com a pesquisa.
Lembre-se que aprendizado é ativo, e não passivo. Você aprende muito mais quando vai atrás da informação do que quando eu fico aqui vomitando código pronto pra você.
Sugestão para estudos:
Que tal tentar implementar as outras sprints do projeto?
Talvez você tenha que pesquisar mais sobre coisas como:
- ORM e Migrations para conectar com banco de dados;
- Padrões de arquitetura como MVC, DDD ou outros, para saber como organizar o projeto
- Libs para criar APIs RESTful, como Spring (Java), Django (Python), Express (JS) e outras
Dicas de pesquisa - POO
- Encapsulamento: protected, private e public
- JS, Python e C# dão suporte a encapsulamento?
- Polimorfismo
- Herança e o uso de super()
- Interfaces
- Classes abstratas