Skip to the content.
Skip to the content.

Padrões de Projeto - Padrões de Criação 

Quando um problema de desenvolvimento é recorrente, algumas soluções reutilizáveis podem ser aplicadas para resolvê-lo. Essas soluções são conhecidas como padrões de projeto, são independentes de linguagem e podem ser aplicados em diferentes contextos.

Quantas vezes você já não se deparou com um problema de desenvolvimento que parecia ser o mesmo de outros projetos anteriores? E não pensou já resolvi isso antes? Procurou em seus arquivos, em seus projetos anteriores e encontrou uma solução que poderia ser reaproveitada?

Alguns padrões de projeto projetos são tão comuns que são considerados como boas práticas de desenvolvimento. No livro “Design Patterns: Elements of Reusable Object-Oriented Software”, os autores Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides (conhecidos como Gang of Four) catalogaram 23 padrões de projeto que são amplamente utilizados no desenvolvimento de software.

Ao longo dos anos, muitos outros padrões de projeto foram criados e catalogados por diferentes autores e comunidades de desenvolvimento. Esses padrões de projeto são independentes de linguagem e podem ser aplicados em diferentes contextos, desde o desenvolvimento de software até a arquitetura de sistemas.

Os padrões de projeto catalogados pelo Gang of Four (GoF) são divididos em três categorias principais: padrões de criação, padrões estruturais e padrões comportamentais. Os padrões de criação se concentram na criação de objetos, os padrões estruturais se concentram na composição de classes e objetos, e os padrões comportamentais se concentram na interação entre objetos.

Nesse post vamos explorar os padrões de criação, nos próximos posts vamos explorar os padrões estruturais e comportamentais.

Padrões de Criação

Os padrões de criação são aqueles que lidam com a criação de objetos, eles fornecem uma maneira de criar objetos de forma flexível e controlada, sem expor a lógica de criação para o cliente. Eles são úteis quando a criação de um objeto é complexa ou quando queremos criar objetos de diferentes tipos sem acoplar o código do cliente às classes concretas. Os padrões de criação incluem o Abstract Factory, Builder, Factory Method, Prototype e Singleton.

Todos os exemplos a seguir estão implementados em C#, mas os conceitos podem ser aplicados em qualquer linguagem de programação orientada a objetos.

Abstract Factory

A Abstract Factory é um padrão de projeto que fornece uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas. Ele é útil quando um sistema deve ser independente de como seus objetos são criados, compostos e representados.

Diagrama de classes do padrão Abstract Factory

O diagrama acima mostra a estrutura do padrão Abstract Factory. Ele consiste em uma interface AbstractFactory que declara métodos para criar objetos de diferentes tipos. As classes concretas ConcreteFactory1 e ConcreteFactory2 implementam a interface AbstractFactory e criam objetos específicos. O cliente usa a interface AbstractFactory para criar objetos, sem se preocupar com as classes concretas que estão sendo usadas.

Primeiro vamos definir as nossas interfaces:

copy
public interface IProductA
{
    void OperationA();
}

public interface IProductB
{
    void OperationB();
}

public interface IAbstractFactory
{
    IProductA CreateProductA();
    IProductB CreateProductB();
}

Em seguida, vamos criar as classes concretas que implementam as interfaces dos produtos:

copy
public class ProductA1 : IProductA
{
    public void OperationA()
    {
        Console.WriteLine("ProductA1 OperationA");
    }
}

public class ProductA2 : IProductA
{
    public void OperationA()
    {
        Console.WriteLine("ProductA2 OperationA");
    }
}

public class ProductB1 : IProductB
{
    public void OperationB()
    {
        Console.WriteLine("ProductB1 OperationB");
    }
}

public class ProductB2 : IProductB
{
    public void OperationB()
    {
        Console.WriteLine("ProductB2 OperationB");
    }
}

Agora vamos criar as classes concretas que implementam a interface IAbstractFactory:

copy
public class ConcreteFactory1 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA1();
    }

    public IProductB CreateProductB()
    {
        return new ProductB1();
    }
}

public class ConcreteFactory2 : IAbstractFactory
{
    public IProductA CreateProductA()
    {
        return new ProductA2();
    }

    public IProductB CreateProductB()
    {
        return new ProductB2();
    }
}

Finalmente, vamos ver como usar o padrão Abstract Factory, no caso a classe Program que é o cliente que vai usar as fábricas para criar os produtos:

copy
public class Program
{
    public static void Main(string[] args)
    {
        IAbstractFactory factory1 = new ConcreteFactory1();
        IProductA productA1 = factory1.CreateProductA();
        IProductB productB1 = factory1.CreateProductB();

        productA1.OperationA();
        productB1.OperationB();

        IAbstractFactory factory2 = new ConcreteFactory2();
        IProductA productA2 = factory2.CreateProductA();
        IProductB productB2 = factory2.CreateProductB();

        productA2.OperationA();
        productB2.OperationB();
    }
}

Ao executar o código acima, teremos a seguinte saída:

copy
ProductA1 OperationA
ProductB1 OperationB
ProductA2 OperationA
ProductB2 OperationB

Builder

O padrão Builder é uma solução para construir objetos complexos de forma controlada e passo a passo, vamos entender como ele funciona e explorar um exemplo prático e ver como é simples de implementá-lo. Se você programa em C# e já utilizou a classe StringBuilder, você já utilizou o padrão Builder, pois ele é um exemplo clássico desse padrão de projeto.

Diagrama de classes do padrão Builder

Começando pela criação da interface IBuilder que declara os métodos para construir as partes do objeto complexo:

copy
public interface IBuilder
{
    void BuildPartA();
    void BuildPartB();
    void BuildPartC();
}

Em seguida, vamos criar a classe concreta ConcreteBuilder que implementa a interface IBuilder e constrói o objeto Product, que é o objeto complexo que queremos construir:

copy
public class Product
{
    private List<string> _parts = new List<string>();

    public void Add(string part)
    {
        _parts.Add(part);
    }

    public void Show()
    {
        Console.WriteLine("Product Parts:");
        foreach (var part in _parts)
        {
            Console.WriteLine(part);
        }
    }
}

public class ConcreteBuilder : IBuilder
{
    private Product _product = new Product();

    public void BuildPartA()
    {
        _product.Add("PartA");
    }

    public void BuildPartB()
    {
        _product.Add("PartB");
    }

    public void BuildPartC()
    {
        _product.Add("PartC");
    }

    public Product GetResult()
    {
        return _product;
    }
}

Finalmente vamos criar o diretor Director que é responsável por controlar o processo de construção do objeto complexo e também criar o programa Program que é o cliente que vai usar o padrão Builder:

copy
public class Director
{
    private IBuilder _builder;

    public void SetBuilder(IBuilder builder)
    {
        _builder = builder;
    }

    public void Construct()
    {
        _builder.BuildPartA();
        _builder.BuildPartB();
        _builder.BuildPartC();
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        Director director = new Director();
        ConcreteBuilder builder = new ConcreteBuilder();

        director.SetBuilder(builder);
        director.Construct();

        Product product = builder.GetResult();
        product.Show();
    }
}

Executando o código acima, teremos a seguinte saída:

copy
Product Parts:
PartA
PartB
PartC

Factory Method

O Factory Method é um padrão de projeto que define uma interface para criar um objeto, mas permite que as subclasses decidam qual classe instanciar. Ele é útil quando um sistema deve ser independente de como seus objetos são criados, compostos e representados. Se voce já programou em dart e utilizou a função factory para criar objetos, você já utilizou o padrão Factory Method.

Diagrama de classes do padrão Factory Method

Vamos começar criando as abstrações necessárias para implementar o padrão Factory Method. Primeiro, vamos criar a interface IProduct que declara o método Operation que será implementado pelas classes concretas de produtos e a classe abstrata Creator que declara o método FactoryMethod que será implementado pelas subclasses para criar os objetos concretos:

copy
public interface IProduct
{
    void Operation();
}

public abstract class Creator
{
    public abstract IProduct FactoryMethod();
}

Agora vamos criar as classes concretas de produtos que implementam a interface IProduct e as classes concretas de criadores que implementam o método FactoryMethod para criar os objetos concretos:

copy
public class ConcreteProduct : IProduct
{
    public void Operation()
    {
        Console.WriteLine("ConcreteProduct Operation");
    }
}

public class ConcreteCreator : Creator
{
    public override IProduct FactoryMethod()
    {
        return new ConcreteProduct();
    }
}

Finalmente, vamos criar o programa Program que é o cliente que vai usar o padrão Factory Method para criar os objetos concretos:

copy
public class Program
{
    public static void Main(string[] args)
    {
        Creator creator = new ConcreteCreator();
        IProduct product = creator.FactoryMethod();
        product.Operation();
    }
}

Ao executar o código acima, teremos a seguinte saída:

copy
ConcreteProduct Operation

Prototype

O padrão Prototype é um padrão de projeto que permite criar novos objetos a partir de um protótipo existente, clonando-o. Ele é útil quando a criação de um objeto é cara ou complexa, e queremos evitar a criação de objetos do zero.

Diagrama de classes do padrão Prototype

Vamos começar criando a interface IPrototype que declara o método Clone que será implementado pelas classes concretas de protótipos:

copy
public interface Prototype
{
    Prototype Clone();
}

Em seguida, vamos criar a classe concreta ConcretePrototype que implementa a interface IPrototype e o método Clone para criar uma cópia do objeto:

copy
public class ConcretePrototypeA : Prototype
{
    public Prototype Clone()
    {
        return (Prototype)this.MemberwiseClone();
    }
}

public class ConcretePrototypeB : Prototype
{
    public Prototype Clone()
    {
        return (Prototype)this.MemberwiseClone();
    }
}

Finalmente, vamos criar o programa Program que é o cliente que vai usar o padrão Prototype para criar novos objetos a partir de um protótipo existente:

copy
public class Program
{
    public static void Main(string[] args)
    {
        Prototype prototypeA = new ConcretePrototypeA();
        Prototype cloneA = prototypeA.Clone();

        Prototype prototypeB = new ConcretePrototypeB();
        Prototype cloneB = prototypeB.Clone();
        Console.WriteLine("Cloned Prototype A: " + cloneA.GetType().Name);
        Console.WriteLine("Cloned Prototype B: " + cloneB.GetType().Name);

    }
}

Ao executar o código acima, teremos a seguinte saída:

copy
Cloned Prototype A: ConcretePrototypeA
Cloned Prototype B: ConcretePrototypeB

Singleton

O padrão Singleton é um padrão de projeto que garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a essa instância. Ele é útil quando precisamos garantir que haja apenas um objeto de uma classe em todo o sistema, como por exemplo, um gerenciador de configuração ou um pool de conexões.

Diagrama de classes do padrão Singleton

Vamos começar criando a classe Singleton que implementa o padrão Singleton. Ela terá um construtor privado para impedir a criação de instâncias fora da classe, e um método estático GetInstance para fornecer acesso à única instância da classe:

copy
public class Singleton
{
    private static Singleton? _instance;

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Singleton();
                _instance._data = Guid.NewGuid().ToString();
            }
            return _instance;
        }
    }

    private string? _data;

    public string GetSingletonData()
    {
        return $"Singleton ID: {_data}";
    }
}

Utilizando o padrão Singleton, podemos acessar a única instância da classe Singleton e obter os dados associados a ela:

copy
public class Program
{
    public static void Main(string[] args)
    {
        Singleton singleton1 = Singleton.Instance;
        Console.WriteLine(singleton1.GetSingletonData());

        Singleton singleton2 = Singleton.Instance;
        Console.WriteLine(singleton2.GetSingletonData());

        Console.WriteLine("Are both instances the same? " + (singleton1 == singleton2));
    }
}

Ao executar o código acima, teremos a seguinte saída, ambas as instâncias do Singleton terão o mesmo ID, confirmando que são a mesma instância:

copy
Singleton ID: 5a50ed60-23d0-4d91-820e-ca2eb15d6bb3
Singleton ID: 5a50ed60-23d0-4d91-820e-ca2eb15d6bb3
Are both instances the same? True

O código fonte completo você encontra no repositório do GitHub

Aqui estão algumas recomendações relacionadas ao conteúdo. Esses itens podem ser comprados na Amazon com o meu link de associado.

Confira minhas recomendações de livros e outros itens na página de recomendações.