O padrão de design “Abstract Factory” é um dos padrões de criação mais conhecidos da programação orientada a objetos e foi formalmente descrito por Erich Gamma, Richard Helm, Ralph Johnson, e John Vlissides, conhecidos como o “Gang of Four” (GoF), em seu livro clássico Design Patterns: Elements of Reusable Object-Oriented Software, publicado em 1994. Esse padrão é parte do catálogo de padrões de design proposto pelos autores.
O que é o Abstract Factory?
O Abstract Factory fornece uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas. Isso é útil quando o sistema precisa ser independente da forma como os objetos são criados, compostos ou representados, garantindo que um conjunto de objetos relacionados trabalhe em conjunto, mesmo sendo de diferentes tipos. Esse padrão promove a flexibilidade e a extensibilidade de sistemas, facilitando a inclusão de novos produtos sem modificar o código existente.
O padrão define uma família de fábricas que cria objetos de diferentes classes, mas que compartilham alguma relação entre si. O uso de interfaces ou classes abstratas permite ao código cliente criar objetos de forma genérica, sem saber a implementação exata por trás dessas classes.
Como Funciona o Abstract Factory?
O Abstract Factory organiza a criação de objetos em uma estrutura bem definida. Ele cria uma separação entre a lógica que define quais objetos devem ser criados e a lógica que os utiliza. Essa separação é alcançada por meio de quatro elementos principais:
Essa estrutura é o coração do padrão Abstract Factory. Ela permite que famílias de objetos relacionados sejam criadas sem que o cliente tenha conhecimento das implementações específicas. Isso garante que novas variantes de objetos possam ser adicionadas ou trocadas facilmente, sem impactar o código principal que depende dessas fábricas.
Exemplo com TypeScript:
Sem o Abstract Factory
Com o Abstract Factory
Aqui, o código cliente (classe Application
) precisa conhecer a implementação concreta (WindowsButton
ou MacButton
). Isso cria uma dependência direta e dificulta a manutenção se novos tipos de botões precisarem ser adicionados no futuro.
Neste exemplo, o código cliente (Application
) não precisa mais conhecer os detalhes das implementações específicas. Ele trabalha com a interface GUIFactory
e a criação de objetos é delegada para as fábricas concretas (WindowsFactory
e MacFactory
).
//TypeScript (Sem Abstract Factory)
class WindowsButton {
render() {
console.log('Renderizando botão estilo Windows');
}
}
class MacButton {
render() {
console.log('Renderizando botão estilo Mac');
}
}
class Application {
renderUI(osType: string) {
let button;
if (osType === 'Windows') {
button = new WindowsButton();
} else if (osType === 'Mac') {
button = new MacButton();
}
button?.render();
}
}
const app = new Application();
// Output: Renderizando botão estilo Windows
app.renderUI('Windows');
app.renderUI('Mac'); // Output: Renderizando botão estilo Mac
//TypeScript (COM Abstract Factory)
interface Button {
render(): void;
}
class WindowsButton implements Button {
render() {
console.log('Renderizando botão estilo Windows');
}
}
class MacButton implements Button {
render() {
console.log('Renderizando botão estilo Mac');
}
}
interface GUIFactory {
createButton(): Button;
}
class WindowsFactory implements GUIFactory {
createButton(): Button {
return new WindowsButton();
}
}
class MacFactory implements GUIFactory {
createButton(): Button {
return new MacButton();
}
}
class Application {
private factory: GUIFactory;
constructor(factory: GUIFactory) {
this.factory = factory;
}
renderUI() {
const button = this.factory.createButton();
button.render();
}
}
// Output: Renderizando botão estilo Windows
const windowsApp = new Application(new WindowsFactory());
windowsApp.renderUI();
// Output: Renderizando botão estilo Mac
const macApp = new Application(new MacFactory());
macApp.renderUI();
Exemplo com GoLang:
Sem o Abstract Factory
Com o Abstract Factory
O código abaixo cria diretamente os botões baseados em uma string de entrada, o que pode resultar em acoplamento e dificuldade na manutenção.
Com o Abstract Factory, o código cliente (no caso Application
) depende apenas da fábrica abstrata (GUIFactory
), e pode facilmente trocar de implementação sem alterar o código principal.
//GO Lang (Sem Abstract Factory)
package main
import "fmt"
type WindowsButton struct{}
func (WindowsButton) Render() {
fmt.Println("Renderizando botão estilo Windows")
}
type MacButton struct{}
func (MacButton) Render() {
fmt.Println("Renderizando botão estilo Mac")
}
func renderUI(osType string) {
var button interface{ Render() }
if osType == "Windows" {
button = WindowsButton{}
} else if osType == "Mac" {
button = MacButton{}
}
if button != nil {
button.Render()
}
}
func main() {
// Output: Renderizando botão estilo
renderUI("Windows") Windows
// Output: Renderizando botão estilo Mac
renderUI("Mac")
}
//GO Lang (COM Abstract Factory)
package main
import "fmt"
type Button interface {
Render()
}
type WindowsButton struct{}
func (WindowsButton) Render() {
fmt.Println("Renderizando botão estilo Windows")
}
type MacButton struct{}
func (MacButton) Render() {
fmt.Println("Renderizando botão estilo Mac")
}
type GUIFactory interface {
CreateButton() Button
}
type WindowsFactory struct{}
func (WindowsFactory) CreateButton() Button {
return WindowsButton{}
}
type MacFactory struct{}
func (MacFactory) CreateButton() Button {
return MacButton{}
}
type Application struct {
factory GUIFactory
}
func (app Application) renderUI() {
button := app.factory.CreateButton()
button.Render()
}
func main() {
// Output: Renderizando botão estilo Windows
windowsApp := Application{factory: WindowsFactory{}}
windowsApp.renderUI()
// Output: Renderizando botão estilo Mac
macApp := Application{factory: MacFactory{}}
macApp.renderUI()
}
Conclusão
O padrão Abstract Factory é útil para sistemas que precisam ser independentes da forma como objetos relacionados são criados, sendo particularmente eficaz em projetos que podem envolver diferentes variações ou plataformas. Ele ajuda a reduzir o acoplamento entre o código e as classes concretas, permitindo uma maior flexibilidade e escalabilidade, com manutenção facilitada.