Padrões Criacionais do GoF: Uma Abordagem Essencial para o Desenvolvimento de Software

Os padrões de design criacionais são parte integrante do catálogo de padrões do GoF (Gang of Four), um grupo de autores que popularizou esses conceitos no clássico livro “Design Patterns: Elements of Reusable Object-Oriented Software”. Esses padrões abordam problemas comuns relacionados à criação de objetos em sistemas orientados a objetos, promovendo flexibilidade e reutilização do código. Neste artigo, exploraremos os padrões criacionais e suas características principais.
O que são padrões criacionais?
Padrões criacionais fornecem soluções para problemas recorrentes no processo de criação de objetos. Eles ajudam a abstrair o processo de instanciar classes, separando a lógica de criação do código principal. Essa abordagem facilita a manutenção, promove o desacoplamento e torna os sistemas mais escaláveis.
Principais Padrões Criacionais do GoF
- Factory Method
Problema: Como criar objetos sem especificar a classe exata a ser instanciada?
Solução: Define um método abstrato em uma classe base, permitindo que subclasses decidam qual objeto concreto será criado.- Vantagens: Flexibilidade e substituição fácil de implementações concretas.
- Exemplo: Um sistema de notificações que decide dinamicamente se envia SMS, e-mail ou push notification
-
abstract class Notification { abstract void notifyUser(); } class EmailNotification extends Notification { void notifyUser() { System.out.println("Enviando e-mail..."); } } class NotificationFactory { public static Notification createNotification(String type) { if (type.equals("email")) { return new EmailNotification(); } // Adicione outras notificações conforme necessário return null; } }
- Abstract Factory
Problema: Como criar famílias de objetos relacionados sem depender de suas implementações concretas?
Solução: Fornece uma interface para criar famílias de objetos relacionados, mas sem especificar suas classes concretas.- Vantagens: Garante a consistência entre os objetos criados.
- Exemplo: Criar componentes gráficos para diferentes sistemas operacionais (Windows, macOS, Linux).
-
// Abstract Product A interface Button { void render(); } // Concrete Product A1 class WindowsButton implements Button { public void render() { System.out.println("Rendering a Windows Button."); } } // Concrete Product A2 class MacOSButton implements Button { public void render() { System.out.println("Rendering a MacOS Button."); } } // Abstract Product B interface Checkbox { void render(); } // Concrete Product B1 class WindowsCheckbox implements Checkbox { public void render() { System.out.println("Rendering a Windows Checkbox."); } } // Concrete Product B2 class MacOSCheckbox implements Checkbox { public void render() { System.out.println("Rendering a MacOS Checkbox."); } } // Abstract Factory interface GUIFactory { Button createButton(); Checkbox createCheckbox(); } // Concrete Factory 1 class WindowsFactory implements GUIFactory { public Button createButton() { return new WindowsButton(); } public Checkbox createCheckbox() { return new WindowsCheckbox(); } } // Concrete Factory 2 class MacOSFactory implements GUIFactory { public Button createButton() { return new MacOSButton(); } public Checkbox createCheckbox() { return new MacOSCheckbox(); } } // Client class Application { private Button button; private Checkbox checkbox; public Application(GUIFactory factory) { button = factory.createButton(); checkbox = factory.createCheckbox(); } public void render() { button.render(); checkbox.render(); } } // Main public class AbstractFactoryExample { public static void main(String[] args) { GUIFactory factory = new WindowsFactory(); Application app = new Application(factory); app.render(); factory = new MacOSFactory(); app = new Application(factory); app.render(); } }
- Singleton
Problema: Como garantir que uma classe tenha apenas uma instância global acessível em todo o sistema?
Solução: Força a criação de uma única instância e fornece um ponto global de acesso a ela.-
- Vantagens: Ideal para gerenciar recursos compartilhados, como conexões de banco de dados.
- Cuidado: Pode ser considerado um anti-padrão se mal utilizado, pois aumenta o acoplamento.
-
class DatabaseConnection { private static DatabaseConnection instance; private DatabaseConnection() { } public static DatabaseConnection getInstance() { if (instance == null) { instance = new DatabaseConnection(); } return instance; } }
-
- Builder
Problema: Como criar objetos complexos com muitos parâmetros opcionais ou configurações?
Solução: Separa a construção de um objeto da sua representação, permitindo criar diferentes configurações passo a passo.- Vantagens: Torna o código mais legível e modular.
- Exemplo: Construção de um pedido de fast food com opções como hambúrguer, refrigerante e sobremesa.
-
// Product class Computer { private String CPU; private String GPU; private int RAM; private int storage; // Setters public void setCPU(String CPU) { this.CPU = CPU; } public void setGPU(String GPU) { this.GPU = GPU; } public void setRAM(int RAM) { this.RAM = RAM; } public void setStorage(int storage) { this.storage = storage; } @Override public String toString() { return "Computer [CPU=" + CPU + ", GPU=" + GPU + ", RAM=" + RAM + "GB, Storage=" + storage + "GB]"; } } // Abstract Builder interface ComputerBuilder { void buildCPU(); void buildGPU(); void buildRAM(); void buildStorage(); Computer getComputer(); } // Concrete Builder class GamingComputerBuilder implements ComputerBuilder { private Computer computer; public GamingComputerBuilder() { this.computer = new Computer(); } public void buildCPU() { computer.setCPU("Intel i9"); } public void buildGPU() { computer.setGPU("NVIDIA RTX 3080"); } public void buildRAM() { computer.setRAM(32); } public void buildStorage() { computer.setStorage(2000); } public Computer getComputer() { return computer; } } // Concrete Builder 2 class OfficeComputerBuilder implements ComputerBuilder { private Computer computer; public OfficeComputerBuilder() { this.computer = new Computer(); } public void buildCPU() { computer.setCPU("Intel i5"); } public void buildGPU() { computer.setGPU("Integrated Graphics"); } public void buildRAM() { computer.setRAM(16); } public void buildStorage() { computer.setStorage(500); } public Computer getComputer() { return computer; } } // Director class ComputerDirector { private ComputerBuilder builder; public ComputerDirector(ComputerBuilder builder) { this.builder = builder; } public Computer constructComputer() { builder.buildCPU(); builder.buildGPU(); builder.buildRAM(); builder.buildStorage(); return builder.getComputer(); } } // Main public class BuilderExample { public static void main(String[] args) { ComputerBuilder gamingBuilder = new GamingComputerBuilder(); ComputerDirector director = new ComputerDirector(gamingBuilder); Computer gamingComputer = director.constructComputer(); System.out.println("Gaming Computer: " + gamingComputer); ComputerBuilder officeBuilder = new OfficeComputerBuilder(); director = new ComputerDirector(officeBuilder); Computer officeComputer = director.constructComputer(); System.out.println("Office Computer: " + officeComputer); } }
- Prototype
Problema: Como criar cópias de objetos existentes sem depender diretamente de suas classes?
Solução: Cria novos objetos clonando instâncias já existentes.- Vantagens: Útil quando a criação de objetos é custosa ou complexa.
- Exemplo: Sistema de gráficos que duplica formas geométricas (como círculos e retângulos) ao invés de recriá-las do zero.
-
// Prototype Interface interface Shape extends Cloneable { Shape clone(); void render(); } // Concrete Prototype 1 class Circle implements Shape { private int radius; public Circle(int radius) { this.radius = radius; } public Circle(Circle source) { this.radius = source.radius; } @Override public Circle clone() { return new Circle(this); } @Override public void render() { System.out.println("Rendering a Circle with radius: " + radius); } } // Concrete Prototype 2 class Rectangle implements Shape { private int width; private int height; public Rectangle(int width, int height) { this.width = width; this.height = height; } public Rectangle(Rectangle source) { this.width = source.width; this.height = source.height; } @Override public Rectangle clone() { return new Rectangle(this); } @Override public void render() { System.out.println("Rendering a Rectangle with width: " + width + " and height: " + height); } } // Client public class PrototypeExample { public static void main(String[] args) { Circle circle1 = new Circle(10); Circle circle2 = circle1.clone(); Rectangle rectangle1 = new Rectangle(20, 15); Rectangle rectangle2 = rectangle1.clone(); circle1.render(); circle2.render(); rectangle1.render(); rectangle2.render(); } }
Quando usar os padrões criacionais?
Esses padrões são úteis quando:
- O código necessita ser flexível e escalável.
- A criação de objetos precisa ser centralizada ou controlada.
- A lógica de criação precisa ser separada para facilitar a manutenção e reduzir o acoplamento.
Conclusão
Os padrões criacionais do GoF são poderosos aliados no desenvolvimento de software, ajudando a criar sistemas mais limpos e organizados. Cada padrão resolve um problema específico relacionado à criação de objetos, permitindo que desenvolvedores escolham a melhor abordagem para cada cenário. Entender e aplicar esses padrões é uma habilidade essencial para criar código robusto e escalável.
Share this content:
Vamos evoluir juntos em tecnologia, carreira e disciplina.
Acompanhe novos artigos sobre backend, arquitetura, performance e os aprendizados que também surgem no esporte. Um espaço para compartilhar conhecimento, consistência e evolução ao longo da jornada.