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: