C# -  Apresentando o padrão Prototype


 Neste artigo vou apresentar com o padrão criacional Gof Prototype.

Segundo a definição do livro da Gang of four (Gof) o objetivo do padrão Prototype é : "Especificar tipos de objetos a serem criados usando uma instância protótipo e criar novos objetos pela cópia desse protótipo.”

Assim este padrão permite a criação de novos objetos a partir de modelo original que é clonado sem fazer o código ficar dependente de sua classe.

O padrão atua delegando o processo de clonagem para o próprio objeto que esta sendo clonado, e, para fazer isso ele
declara uma interface comum para todos os objetos que suportam a clonagem e permite clonar um objeto sem acoplar seu código à classe daquele objeto. (Geralmente, a interface contém apenas um único método clonar.)

O conceito usado pelo padrão é copiar um objeto existente em vez de criar uma nova instância do zero, algo que pode incluir operações custosa em termos de memória e tempo. O objeto existente atua como um protótipo e contém o estado do objeto. O objeto recém-copiado pode alterar as mesmas propriedades apenas se necessário.

Essa abordagem economiza recursos e tempo dispendiosos, especialmente quando a criação do objeto for um processo pesado.

Diagrama UML

No diagrama UML deste padrão temos os seguintes participantes:

1 - Prototype - Declara uma interface para objetos capazes de clonar a si mesmo;

2 - ConcretePrototype – Faz a implementação de um Prototype (implementa uma operação para se clonar)

3- Client Cria um novo objeto através de um Prototype que é capaz de clonar a si mesmo;

Quando podemos usar o padrão Singleton

Podemos considerar o uso deste padrão :

a- Quando a criação de novos objetos tem um grande custo e leva muito tempo;

b- Quando desejamos evitar criar um novo objeto utilizando a palavra new, o que diminui o custo de memória;

c- Quando existir a necessidade de clonar (criar cópias exatas) um objeto.

d- Quando o sistema deve ser independente de como os seus objetos são criados e :
   - As classes a serem instanciadas forem definidas em tempo de execução
   - For mais conveniente copiar uma instância existente do que criar uma
   - Forem necessários objetos semelhantes aos objetos existentes

Vantagens e desvantagens ao usar o padrão

Como vantagens do uso do padrão Prototype podemos citar :

  1. O padrão elimina a possível sobrecarga dispendiosa de inicializar um objeto;
  2. Ajuda na otimização do caso de uso em que vários objetos do mesmo tipo têm dados quase semelhantes;
  3. Fornece uma alternativa para herança quando o objeto tem predefinições de configuração para objetos complexos;

E como desvantagem temos que :

Implementar o padrão em uma hierarquia de classes já existente pode ser difícil, pois eles devem implementar o método clone(), e alguns objetos podem estar usando objetos internos que não suportam cópia ou podem ter referência circular

O uso excessivo do padrão pode afetar o desempenho, já que o próprio objeto de protótipo precisaria ser instanciado se você usar um registro de protótipos

Processo de clonagem

O processo de clonagem de um objeto pode ser feito usando duas abordagens:

  1. Shallow Copy (ou cópia superficial)

    Copia todos os tipos de referência ou tipos de valor, mas não copia os objetos aos quais as referências se referem. As referências no novo objeto apontam para os mesmos objetos para os quais as referências no objeto original apontam. (Aqui somente o objeto Pai é clonado)
     
  2. Deep Copy (ou cópia profunda)

    Copia os elementos e tudo o que é referenciado direta ou indiretamente pelos elementos.
    O objeto pai é clonado junto com os objetos que o contêm.

Usando a linguagem C# podemos usar recursos que a própria linguagem oferece para facilitar a implementação da clonagem, como o método Clone da interface ICloneable  que cria um novo objeto que é uma copia da instancia atual, e
o método MemberwiseClone() que cria uma copia superficial do objeto atual.

  1. ICloneable.Clone() :  Cria um novo objeto que é uma cópia da instância atual;

  2. Object.MemberwiseClone() :  Cria uma cópia superficial do Objeto atual;

Um cenário muito comum de uso deste padrão e a clonagem de personagens em jogos onde geralmente é necessário usar muitos objetos com as mesmas características básicas havendo a repetição de muitos personagens com pequenas variações

Neste cenário criar cada objeto usando o operador new seria muito custoso em termos de recursos usando a clonagem e o padrão Prototype basta criar um objeto e fazer a clonagem alterando apenas algumas características de alguns personagens.

Exemplo de aplicação do padrão Prototype

Vejamos um exemplo de aplicação do padrão Prototype onde vamos clonar um soldado e gerar um exercito de soldados.

Para isso vamos criar um projeto Console do tipo .NET 5.0 usando a linguagem C#.



Inicialmente vamos usar a abordagem Shallow Copy e fazer uma clonagem superficial. Para isso vamos usar a interface ICloneable da linguagem C# que define o contrato para implementar o método clone que esta fazendo o papel do Prototype.



Vamos criar uma classe Soldado que representa o ConcretePrototype e vamos definir as propriedades Nome , Arma e Acessorio; aqui também estamos implementando o método Clone da interface que vai permitir fazer a clonagem do objeto.

    public class Acessorio
    {
        public string Nome { get; set; }

        public object Clone()
        {
            return (Acessorio)this.MemberwiseClone();
        }
    }

Vamos criar também a classe  Acessorio que representa um tipo Acessorio que é uma classe com a propriedade Nome que vai definir acessórios especiais de um soldado como visão noturna e recursos especiais Temos aqui um tipo complexo e veremos como ele se comporta na abordagem Shallow Copy.

Na classe Program que representa o Client vamos criar o objeto Soldado atribuir valores as suas propriedades e fazer a clonagem deste objeto.

using Prototype1_ShallowCopy.ConcretePrototype;
using static System.Console;

namespace Prototype1_ShallowCopy
{
    class Program
    {
        static void Main(string[] args)
        {
            Soldado soldado = new Soldado();
            soldado.Nome = "Soldado1";
            soldado.Arma = "Fuzil HK G36";
            soldado.Acessorio = new Acessorio { Nome = "Visor Noturno" };

            //clone1 do objeto original
            Soldado soldado_clone1 = (Soldado)soldado.Clone();
            soldado_clone1.Nome = "Soldado clone1";
            soldado_clone1.Arma = "Fuzil Kalashinikov";
            soldado_clone1.Acessorio.Nome = "Colete Especial";

            //clone2 do objeto original
            Soldado soldado_clone2 = (Soldado)soldado.Clone();
            soldado_clone2.Nome = "Soldado clone2";
            soldado_clone2.Arma = "Fuzil AK105";
            soldado_clone2.Acessorio.Nome = "Gás mostarda";

            //exibe valores do objeto original
            WriteLine(">> Objeto Original");
            WriteLine($"{soldado.Nome} - {soldado.Arma}");
            WriteLine($"{soldado.Acessorio.Nome}\n");

            //clone1
            WriteLine(">> Objeto Clone1");
            WriteLine($"{soldado_clone1.Nome} - {soldado_clone1.Arma}");
            WriteLine($"{soldado_clone1.Acessorio.Nome}\n");

            //clone2
            WriteLine(">> Objeto Clone2");
            WriteLine($"{soldado_clone2.Nome} - {soldado_clone2.Arma}");
            WriteLine($"{soldado_clone2.Acessorio.Nome}\n");

            ReadLine();

        }
    }
}

Para isso vamos implementar a classe Soldado da seguinte forma, e esta classe vai implementar a interface ICloneable disponível na linguagem C#.

Vamos definir as propriedades e vamos implementar o método Clone da interface onde estamos retornando uma nova instância de Soldado, e, no construtor recebemos a instancia e copiamos os valores das propriedades.

Note que temos um construtor vazio para poder criar um objeto Soldado inicial e fazemos a clonagem usando o método Clone.

Aqui estamos usando a abordagem Shallow Copy pois como você vai perceber apenas os tipos de valor ou tipos de referencia serão copiados o tipo Acessorio por ser um tipo complexo terá copiada somente a referencia para o tipo e não o valor.

Executando o projeto teremos o resultado abaixo:



Na próxima parte do artigo veremos como realizar uma cópia profunda ou Deep Copy usando padrão Prototype.

Pegue o código do projeto aqui : Prototype1.zip

"Os meus olhos anteciparam as vigílias da noite, para meditar na tua palavra."
Salmos 119:148

Referências:


José Carlos Macoratti