O padrão de projeto Abstract Factory e eficiência em Python e Java: qual a melhor linguagem?
- #Java
- #Python
Introdução
Você já ouviu falar na “Gang of Four”? A expressão é utilizada para se referir aos quatro autores do livro “Design patterns – elements of reusable object-oriented software” ou “Padrões de projeto - soluções reutilizáveis de software orientado a objetos”. Ele foi publicado em 1995 por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides.
Eles definem os padrões de projeto como “descrições de objetos e classes comunicantes que precisam ser personalizadas para resolver um problema geral de projeto num contexto particular”. Em outras palavras, são guias que oferecem alternativas de implementações focadas em dinamicidade, flexibilidade e eficiência.
Imagine, por exemplo, um aplicativo de navegação GPS. Há incontáveis maneiras de chegar a um destino, mas uma rota pode fazer com que você chegue mais rápido, outra gastando menos combustível e outra evitando os semáforos. A ideia é a mesma, só que para engenharia de software.
Um programa pode utilizar dois, três ou até todos os padrões, a depender de sua complexidade e tamanho. Inicialmente concebidos para funcionar diretamente em relação ao paradigma orientado a objetos (como na linguagem Java), com o tempo a ferramenta também se mostrou poderosa para arquiteturas dinâmicas, como o Python.
Eles são divididos em três categorias: criacionais, comportamentais e estruturais. Aqui, vamos focar apenas em um padrão criacional. Mas espero que a leitura ajude você a querer aprender mais sobre essas soluções, que podem fazer toda a diferença no seu código.
Abstract Factory
Esse padrão tem o objetivo de criar famílias de objetos relacionados ou dependentes sem a necessidade de especificar suas classes concretas. Ela fornece uma interface para criar objetos que são da mesma família para que sejam compatíveis entre si.
Especialmente útil quando:
- o sistema deve ser independente de como os produtos são criados ou representados;
- o sistema deve ser montado com um produto de uma família de múltiplos produtos;
- existe a necessidade de uma biblioteca de classes e produtos que revele somente as interfaces, não suas implementações.
Componentes:
- AbstractFactory: interface para operações de criação dos produtos abstratos, declarando os métodos necessários;
- ConcreteFactory: implementa a AbstractFactory, criando uma família específica de produtos;
- AbstractProduct: interface para um tipo de objeto de produto;
- ConcreteProduct: implementa a AbstractProduct definindo um objeto de produto a ser criado pela fábrica concreta correspondente.
Exemplo de uso em Python:
from abc import ABC, abstractmethod
# interface para os produtos:
class Camisa(ABC):
@abstractmethod
def vestir(self):
pass
class Sapato(ABC):
@abstractmethod
def calcar(self):
pass
# classe para os produtos concretos masculinos:
class CamisaMasculina(Camisa):
def vestir(self):
print("Vestindo camisa masculina.")
class SapatoMasculino(Sapato):
def calcar(self):
print("Calçando sapato masculino.")
# classe para os produtos concretos femininos:
class CamisaFeminina(Camisa):
def vestir(self):
print("Vestindo camisa feminina.")
class SapatoFeminino(Sapato):
def calcar(self):
print("Calçando sapato feminino.")
# interface para fabrica abstrata:
class FabricaModa(ABC):
@abstractmethod
def produzir_camisa(self) -> Camisa:
pass
@abstractmethod
def produzir_sapato(self) -> Sapato:
pass
# classe concreta para as fábricas:
class FabricaMasculina(FabricaModa):
def produzir_camisa(self) -> Camisa:
return CamisaMasculina()
def produzir_sapato(self) -> Sapato:
return SapatoMasculino()
class FabricaFeminina(FabricaModa):
def produzir_camisa(self) -> Camisa:
return CamisaFeminina()
def produzir_sapato(self) -> Sapato:
return SapatoFeminino()
# Cliente:
def vestir_e_calcar(fabrica: FabricaModa):
camisa = fabrica.produzir_camisa()
sapato = fabrica.produzir_sapato()
camisa.vestir()
sapato.calcar()
# Exemplo de uso:
if __name__ == "__main__":
print("Moda Masculina:")
fabrica_masculina = FabricaMasculina()
vestir_e_calcar(fabrica_masculina)
print("\nModa Feminina:")
fabrica_feminina = FabricaFeminina()
vestir_e_calcar(fabrica_feminina)
Implementação em Java:
Implementação das interfaces relativas aos produtos abstratos, no nosso caso, os tipos de roupas:
Camisa.java
package roupas;
public interface Camisa {
void vestir();
}
Sapato.java
package roupas;
public interface Sapato {
void calcar();
}
Agora as classes irão herdar das interfaces criadas anteriormente com os métodos definidos:
CamisaFeminina.java
package roupas.feminino;
import roupas.Camisa;
public class CamisaFeminina implements Camisa {
@Override
public void vestir() {
System.out.println("Vestindo a camisa feminina.");
}
}
SapatoFeminino.java
package roupas.feminino;
import roupas.Sapato;
public class SapatoFeminino implements Sapato {
@Override
public void calcar() {
System.out.println("Calçando sapato feminino.");
}
}
CamisaMasculina.java
package roupas.masculino;
import roupas.Camisa;
public class CamisaMasculina implements Camisa {
@Override
public void vestir() {
System.out.println("Vestindo uma camisa masculina");
}
}
SapatoMasculino.java
package roupas.masculino;
import roupas.Sapato;
public class SapatoMasculino implements Sapato {
@Override
public void calcar() {
System.out.println("Calçando sapato masculino.");
}
}
Agora precisamos da interface para criação de métodos relativos à família de roupas.
FabricaModa.java
package fabricas;
import roupas.Camisa;
import roupas.Sapato;
public interface FabricaModa {
Camisa criarCamisa();
Sapato criarSapato();
}
Finalmente, temos as fábricas concretas, que irão criar as roupas concretas:
FabricaFeminina.java
package fabricas;
import roupas.*;
import roupas.feminino.CamisaFeminina;
import roupas.feminino.SapatoFeminino;
public class FabricaFeminina implements FabricaModa {
@Override
public Camisa criarCamisa() {
return new CamisaFeminina();
}
@Override
public Sapato criarSapato() {
return new SapatoFeminino();
}
}
FabricaMasculina.java
package fabricas;
import roupas.*;
import roupas.masculino.CamisaMasculina;
import roupas.masculino.SapatoMasculino;
public class FabricaMasculina implements FabricaModa {
@Override
public Camisa criarCamisa() {
return new CamisaMasculina();
}
@Override
public Sapato criarSapato() {
return new SapatoMasculino();
}
}
Na classe Main, vamos ter um método para receber uma fábrica específica e criar as roupas de cada sexo e o método main, para criar de fato as roupas e calçados masculinos e femininos:
import roupas.*;
import fabricas.*;
public class Main {
public static void novoLook(FabricaModa fabrica){
Camisa camisa = fabrica.criarCamisa();
Sapato sapato = fabrica.criarSapato();
System.out.println("Novo look concluído.");
camisa.vestir();
sapato.calcar();
}
public static void main(String[] args) {
System.out.println("Novo look masculino sendo montado agora...");
novoLook(new FabricaMasculina());
System.out.println("\n");
System.out.println("Novo look feminino sendo montado agora...");
novoLook(new FabricaFeminina());
}
}
Tente replicar os códigos na sua IDE para ver os resultados!
Veredito
Com uma implementação segura, o Java oferece estabilidade e clareza, apesar da necessidade já conhecida de escrever mais linhas de código. Essa robustez pode ser essencial em projetos grandes, que envolvam a participação de equipes em diferentes níveis de complexidade.
A linguagem pode ser mais verbosa, mas o contrato é claro. Ao utilizar as interfaces de fábrica, o cliente sabe exatamente qual tipo de roupa ou sapato o método deve retornar. Além disso, a utilização dos pacotes junto com o compilador ajuda o desenvolvedor a tratar erros de forma rápida.
Já o Python dispõe de velocidade e flexibilidade. A característica dinâmica intrínseca da linguagem permite fazer alterações de forma ágil. Isso significa que a prototipagem aqui também sai ganhando por conta da alta adaptabilidade. No entanto, a baixa segurança de tipos pode fazer com que um erro seja descoberto somente quando o código for executado.
Aqui temos claramente um empate: se o seu trabalho envolver prioritariamente programas estáveis, detecção precoce de erros e previsibilidade, vá de Java. Se quer concisão, segurança em protótipos e flexibilidade, corra para o Python.