Do que trata o artigo

Neste artigo continuaremos o estudo sobre os padrões de projeto. Iremos abordar o padrão Abstract Factory, implementando-o de duas formas: como uma classe abstrata e como uma interface. Daremos um enfoque maior à segunda opção.

Para que serve

Abstract Factory não é apenas a ancestral de todas as Concrete Factorys, mas é também um modo de desacoplar ainda mais os seus sistemas, por delegar a criação de famílias inteiras de objetos sem se especificar uma classe concreta e usando uma única interface. Pode ser até uma fábrica de fábricas.

Em que situação o tema é útil

Quando o seu sistema necessita de um alto nível de generalização ele também precisará de um alto nível de desacoplamento. Enquanto as concrete Factorys permitem criar uma gama diferente de objetos da mesma família através de seus Factory Methods, contanto que a fábrica concreta seja especificada, Abstract Factory pode flexibilizar até mesmo qual será a fábrica concreta a ser usada, delegando o tipo de retorno de seus Factory Methods a seus descendentes. Isso nos permitirá, por exemplo, criar conexões com bancos de dados diferentes por usar linhas/fabricantes de componentes diferentes.

Resumo do DevMan

Desacoplar é a chave para flexibilizar. Se o seu sistema for capaz de montar automaticamente todo um fluxo que vai desde as interfaces, passando pelos objetos de negócio e indo até a persistência, livre de qualquer dependência com outras tecnologias podemos dizer que o sistema é flexível. A flexibilidade no Delphi ainda traz os benefícios de se estar usando uma linguagem compilada, nativa e fortemente tipada.

Como se sabe a orientação a objetos sempre foi um dos pilares das boas práticas de programação, permitindo que seja possível construir aplicações robustas e ao mesmo tempo flexíveis, ou seja, que sejam capazes de sofrer modificações sem tornar isso uma tarefa árdua e desgastante. Embora todo o IDE do Delphi seja Orientado a Objetos, esta ainda é uma prática pouco utilizada pelos seguidores do Object Pascal, devido à metodologia procedural que sempre foi adotada pela maioria. Porém, acompanhando fóruns de discussão, é possível notar que este quadro tem se revertido, mostrando um interesse maior e uma mudança de ideias em relação à adoção da orientação a objetos como metodologia cotidiana dos desenvolvedores Delphi.

Para a melhor utilização da metodologia é necessário compreender os principais fundamentos e conceitos, e porque não dizer obrigatórios e indispensáveis, para visualizar a orientação a objetos de forma clara e organizada. Para isto, foram criados os padrões de projeto, que ganharam popularidade através dos autores Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, ou GoF (Gang Of Four) como é mais conhecida, através do livro Design Patterns: Elements of Resuble Object-Oriented Software.

Nota do DevMan

Os padrões de projeto são basicamente divididos em três famílias:

Padrões de criação ou criacionais:

Abstract Factory, Factory Method, Singleton, Builder e Prototype;

Padrões estruturais:

Adapter, Bridge, Composite, Decorator, Façade, Flyweight e Proxy;

Padrões comportamentais:

Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method e Visitor;

No artigo anterior foi comentado a respeito do padrão de projeto Factory Method. Factory Method é um método que se responsabiliza pela criação (e possível configuração ou ligação) de um objeto ou de um grupo de objetos.

Nesse contexto o construtor de uma classe (Create no Delphi Win32, new no Delphi Prism) pode ser considerado como um método fábrica degenerado, pois cria apenas um tipo de objeto, sem parametrizá-lo muito e ainda tem um alto acoplamento com a unidade desta classe por motivos óbvios: ele foi escrito como método desta classe, nesta mesma unidade.

Já nesta parte da série, será abordado o Abstract Factory. Para facilitar a compreensão do padrão, imagine já ter criado melhorias em um sistema por delegar a criação de certos objetos a alguns métodos que serão usados por “clientes” (outros objetos que necessitem destes).

A limitação aqui refere-se a necessidade de instanciar uma classe fábrica concreta para cada tipo ou família de objetos que seja criada. Supondo-se por exemplo, que uma fábrica de conexões com o Interbase fosse criada, ela criaria tanto o IBConnection como os IBDataSets, e talvez uma outra fábrica concreta poderia criar AdoConnections e AdoDataSets.

O objetivo do Abstract Factory é criar uma interface comum para acesso e manipulação de qualquer fábrica concreta. Com o Abstract Factory criam-se variáveis genéricas, com métodos que criarão o objeto indiretamente, delegando aos descendentes da Abstract Factory a responsabilidade de saber qual o tipo de objeto correto a se criar.

Há duas formas de atribuir uma interface comum a todas as fábricas de objetos: ou elas são descendentes do mesmo ancestral comum ou elas implementam uma mesma interface.

Será adotada a segunda opção pelas vantagens que o uso de interfaces proporciona: contagem de referências (veja nota do DevMan) e a possibilidade de criar uma fábrica concreta que não seja necessariamente “da família” das outras fábricas. Porém, antes de criar uma interface padrão para as fábricas concretas, será criada uma interface padrão para os produtos concretos. Isso flexibilizará a aplicação a tal ponto que para um objeto ser considerado um produto concreto basta que implemente a interface, mesmo que não seja “parente” dos outros produtos. Isso exibe um cenário em que virtualmente qualquer classe pode ser uma fábrica e criar qualquer tipo de produto.

Nota do DevMan

 Interfaces, contagem de referência, TInterfacedObject e IInterface 

Objetos que implementam interfaces, caso sejam descendentes de TInterfacedObject não necessitam ser destruídos, pois tem um contador de referências interno que automaticamente destrói o objeto que a implementa quando esse contador chega a zero. Além disso, TInterfacedObject tem um mecanismo para trabalhar com multithread que previne que um objeto seja destruído quando ainda não terminou de ser criado. Então, teoricamente usando interfaces e criando classes como descendentes de TInterfacedObject não precisamos nos preocupar com a destruição dos objetos e nem com Memory Leaks, que é o termo dado para um objeto que foi criado em tempo de execução, contudo, após seu devido uso e descarte, não é destruído / eliminado da memória.

Como sabemos, toda classe emObject Pascalacabará herdando de uma outra conhecida comoTObject. Desse modo, os métodos dessa classe estarão disponíveis a qualquer outra que for herdada. Não surpreendentemente, o mesmo acontece com as interfaces. Toda e qualquer interface no Delphi, mesmo que não seja declarado explicitamente, herdará deIInterface.

A diferença na herança entre classes e interfaces está na falta de implementação. Se uma interface X herda de Y, um objeto que implemente X também deverá implementar os métodos de Y. Ou seja, se seguirmos essa ideia até o topo da hierarquia, todos os objetos que implementem interfaces, deverão definir implementações para os métodos deIInterface. Na prática normalmente não implementamos diretamente nenhum método deIInterface(mesmo que ainda não saibamos quais eles sejam).

Vejaa seguir a declaração de IInterfacee TInterfacedObject, retirada dos fontes da VCL do Delphi XE:

 
    IInterface = interface
      ['{00000000-0000-0000-C000-000000000046}']
      function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
      function _AddRef: Integer; stdcall;
      function _Release: Integer; stdcall;
    end;
   
    TInterfacedObject = class(TObject, IInterface)
    protected
      FRefCount: Integer;
      function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
      function _AddRef: Integer; stdcall;
      function _Release: Integer; stdcall;
    public
      procedure AfterConstruction; override;
      procedure BeforeDestruction; override;
      class function NewInstance: TObject; override;
      property RefCount: Integer read FRefCount;
    end;  ... 

Quer ler esse conteúdo completo? Tenha acesso completo