Princípios de Design de Software – Dependency Inversion Principle

Como último post sobre os princípios de design SOLID, vamos analizar um pouco o Princípio da Inversão de Dependência (Dependency Inversion Principle – DIP). Como os outros posts da série, o artigo de Robert C. Martin [1] foi utilizado como base para a discussão.

Dependency Inversion Principle

O conceito deste princípio é um pouco abstrato, mas com alguns exemplos poderemos analisar de uma maneira mais prática. A ideia central é de que classes de um nível alto não devem depender de classes de um nível mais baixo. Vamos entender o que são classes de nível alto e nível baixo através do exemplo citado em [2].

Suponha uma classe FuncionarioDAO que busca os funcionários de um determinado turno:
Obs: DAO (Data Access Object) é um padrão de projeto Core Java2EE (Java 2, Enterprise Edition) que encapsula mecanismos para acessar dados [4], neste exemplo, a classe DAO encapsula mecanismos para consultar dados de funcionários.

public class FuncionarioDAO {
    public ArrayList<String> buscaPorTurno(Turno turno){
        // Busca no banco os funcionário do turno passado como parâmetro
    }
}

Neste exemplo, a classe FuncionarioDAO é uma classe de alto nível de abstração e a classe ArrayList da Java Collections é uma classe de um nível mais baixo de abstração. A ArrayList fornece a infraestrutura necessária para que a FuncionarioDAO funcione, assim a classe FuncionárioDAO é de um nível mais alto que a ArrayList.

Suponha que um código recebe o resultado e precisa ordená-lo, esse retorno será sempre um ArrayList, assim, o método de ordenação estará preso a esta classe. Caso um algoritmo mais eficiente utilize outra estrutura, ele não poderá ser usado, a menos que o sistema passe por uma grande mudança no seu design. Seria necessário percorrer todo o código que estivesse ligado a esta classe e trocar pela nova implementação. Além disso, outros problemas também poderiam surgir, que são descritos mais detalhadamente em [2].

A ideia do princípio então é inverter a dependência, fazendo com que as classes de alto nível não dependam diretamente de uma classe de baixo nível, objetivando facilitar mudanças e manutenções futuras. Este é um dos conceitos chave em Orientação a Objetos [3]:

Programe voltado para interface e não para implementação

Como visto no post anterior, a API de Collections do Java segue o Interface Segregation Principle (ISP), oferecendo um conjunto de interfaces para vários tipos de usos. Assim, se a classe DAO acima estivesse conforme o DIP, uma alteração da estrutura interna não causaria danos a toda a aplicação, pois a dependência seria de uma Interface e não de uma Implementação específica.

Padrões de Projeto

O princípio da inversão de dependência é bastante presente no design dos vários padrões de projeto, pois todos eles definem uma interface, para que não haja uma dependência forte de implementações. Um grande exemplo de aplicação do princípio é o padrão Bridge, que define interfaces para favorecer um baixo acoplamento no design do código.

Um outro padrão que é bem ligado ao DIP é o Dependency Injection [5], que ainda não foi comentado aqui no blog. Este padrão sugere que as dependências de uma classe (por exemplo: uma conexão com o banco de dados) sejam injetados na classe. Desta forma, além de inverter a dependência, a classe não precisa se preocupar com o ciclo de vida das suas dependências (no exemplo da conexão, a classe não precisa abrir ou fechar a conexão, apenas recebe uma referência para a conexão e a utiliza).

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] www.objectmentor.com/resources/articles/dip.pdf

[2] Silveira, P. et. al. Introdução à Arquitetura e Design de Software. Rio de Janeiro: Elsevier, 2012.

[3] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.

[4] http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

[5] http://en.wikipedia.org/wiki/Dependency_Injection

Anúncios

Princípios de Design de Software – Interface Segregation Principle

Neste post vamos analisar o Princípio da Segregação de Interfaces (Interface Segmentation Principle – ISP), que possui uma ideia simples, mas é considerado difícil de se alcançar. Também foi utilizado como texto base o seminal de Robert C. Martin [1].

Interface Segmentation Principle

O principio diz basicamente que o cliente não deve utilizar interfaces das quais não precise. Uma maneira bem simples de entender o princípio, descrita em [1], é: considere um conjunto de clientes que possuem necessidades diferentes em alguns pontos, utilizar uma única interface que oferece vários métodos para todos os clientes fere o princípio, pois como os clientes possuem necessidades diferentes, esta interface acaba por oferecer métodos que não são necessários a todos os clientes.

Observe a imagem a seguir, nesse design todos os clientes utilizam uma mesma interface, assim, esta interface deve oferecer métodos que supram as necessidades de todos os clientes.

Essa classe “SuperInterface” fere o ISP, pois os clientes são obrigados a utilizar uma interface a qual não necessitam. As implementações desta interface precisarão também suprir as necessidades dos clientes, implementando métodos que não serão utilizados e/ou não são de sua responsabilidade.

Em [2] é mostrado como a API de coleções do Java segue o princípio, oferecendo conjuntos de interfaces específicas e genéricas para que o desenvolvedor escolha conforme sua necessidade:

Se um método precisa apenas percorrer uma coleção, pode ser utilizada a interface Iterable, no entanto, se for preciso acessar elementos através de índices, ou outras informações sobre a coleção, será necessário alterar o método. A chave para este princípio está em balancear as necessidades e o desacoplamento do código. [2]

Padrões de Projeto

Um ótimo exemplo de padrão de projeto para discutir o ISP é o padrão Composite. Lembra da discussão sobre colocar os métodos de gerenciamento da lista dos objetos compostos na interface geral ou na interface específica?

O ponto central da discussão é se a interface geral deve ser genérica o suficiente para que todos os clientes tenham acesso a todos os métodos (incluindo os métodos de gerenciamento da lista de objetos compostos), sem a necessidade de verificação de tipo, ou se a interface deve ser segregada, utilizando assim uma interface específica com métodos para gerenciar a lista de objetos compostos.

Outro bom exemplo de discussão sobre o princípio é o Adapter. Nele é preciso oferecer uma nova interface que adapte uma classe já existente as necessidades do cliente. Os outros padrões sempre oferecem uma interface para o cliente (baseado no princípio do próximo post) e esta interface possui um design bem simples, mas que atende as necessidades dos clientes.

Se o design já está feito, utilizar o padrão Adapter pode ser uma excelente saída para oferecer várias interfaces segregadas específicas para as necessidades dos clientes.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] http://www.objectmentor.com/resources/articles/isp.pdf

[2] Silveira, P. et. al. Introdução à Arquitetura e Design de Software. Rio de Janeiro: Elsevier, 2012.

[3] http://www.oodesign.com/interface-segregation-principle.html

Princípios de Design de Software – Liskov Substitution Principle

Continuando a série de posts sobre os princípios de Design OO, vamos analisar agora um pouco sobre o Princípio da Substituição de Liskov. Aqui também será utilizado como principal referência o texto de Robert C. Martin [1] sobre o princípio.

Liskov Substitution Principle

O LSP é um princípio muito ligado ao OCP, o que ficará mais evidente após o exemplo. Por enquanto vamos ver a definição do princípio.

O LSP sugere que deve ser possível substituir objetos por instâncias de seus subtipos [2]. Pode até parecer óbvio, uma vez que com a herança é possível utilizar subtipos de uma classe, no entanto, vamos analisar um pequeno problema exposto em [1] que exemplifica bem a quebra do princípio. Uma definição um pouco mais exata sobre o princípio pode ser encontrada em [3] e [1]:

Se q(x) é uma propriedade válida para objetos x de um tipo T. Então q(y) deve ser também válida para objetos y de um tipo S, onde S é um subtipo de T.

Analisando de maneira mais prática, no exemplo mostrando em [1] Martin apresenta duas classes: Retângulo e Quadrado. Para o exemplo, o Quadrado é considerado um subtipo de Retângulo, já que o quadrado é um caso especial de retângulo, pois os seus lados são iguais. Mostrando o código seria algo mais ou menos assim:

class Rectangle
{
public:
    virtual void   SetWidth(double w)  {itsWidth=w;}
    virtual void   SetHeight(double h) {itsHeight=w;}
    double GetHeight() const   {return itsHeight;}
    double GetWidth() const    {return itsWidth;}
private:
    double itsWidth;
    double itsHeight;
};

A classe quadrado vai herdar desta classe, mas uma pequena alteração será feita nos métodos set para que ambos os valores sejam alterados ao mesmo tempo. Assim fica garantido que a classe Quadrado está em conformidade com o modelo matemático de um quadrado.

void Square::SetWidth(double w)
{
    Rectangle::SetWidth(w);
    Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
    Rectangle::SetHeight(h);
    Rectangle::SetWidth(h);
}

Veja agora a seguinte asserção:

void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}

Para um retângulo, faz todo o sentido que uma largura de 5 e altura de 4 calcule uma área de 20, no entanto, ao passar um quadrado para este método, a área calculada seria de 4 * 4 = 16, pois o último set alteraria a largura e altura do quadrado para 4. Neste exemplo fica clara a violação ao princípio (Em [1] é possível encontrar uma discussão mais aprofundada sobre este exemplo).

Neste ponto fica então mais clara a ligação com o OCP pois seria necessário prever, de certa forma, que a classe quadrado surgiria e preparar o sistema de uma maneira que ele consiga suportar quadrados e retângulos. Um sistema que obedece a ambos os princípios (OCP e LSP) são considerados realmente reutilizáveis, pois nenhum método causará efeitos colaterais [1].

Um ponto mais profundo a ser discutido sobre o princípio é o Design por Contrato (Design by Contract – DbC) [4], que é uma metáfora sobre elementos de software que devem se comportar como clientes e fornecedores, que utilizam um contrato para garantir obrigações e benefícios. Nesse contexto, clientes oferecem certas garantias, evitando que os métodos chamados sejam obrigados a fazer verificações, e os fornecedores garantem propriedades que beneficiarão o cliente, garantindo assim a conformidade do sistema.

Unindo os conceitos do LSP com o DbC, surge uma ideia apresentada em [1] por Bertrand Meyer: ao redefinir uma rotina (nas classes derivadas), você deve apenas substituir precondições por outras mais fracas, e as pós condições por outras mais fortes. As precondições são condições que precisam ser verdadeiras para que o método seja executado, e ao completar as operações as pós condições precisam ser verdadeiras.

Padrões de Projeto

Um padrão de projeto que claramente exemplifica a substituição de classes mães por classes filhas é o State. O design que o padrão propõe se baseia no fato de que as classes filhas são perfeitas substitutas para suas mães, e esse é um dos problemas ao implementar o padrão, garantir que todas as classes filhas façam transições de estados seguras e válidas.

Além disso, outros padrões também precisam garantir a substituição de interfaces por implementações, como o Strategy, o Template Method ou o Adapter.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] http://www.objectmentor.com/resources/articles/lsp.pdf

[2] http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

[3] http://en.wikipedia.org/wiki/Liskov_substitution_principle

[4] http://en.wikipedia.org/wiki/Design_by_contract

Princípios de Design de Software – Open/Closed Principle

Continuando a série sobre os Princípios SOLID, vamos falar um pouco sobre o “O”, que significa Open/Closed Principle. Mais uma vez, será usado como guia principal um texto de Robert C. Martin [1], no entanto vou trazer mais algumas referências, pois achei que o post anterior ficou pobre em referências.

Open/Closed Principle

Este princípio pode ser definido como [2]:

“Entidades de software (classes, módulos, funções, etc) devem ser abertas para extensão mas fechadas para modificações” – Bertrand Meyer (1988)

O princípio basicamente sugere que as suas classes (ou demais entidades) sejam flexíveis a mudanças, para que estas não afetem muitas partes do sistema. Conforme descrito em [1], todo sistema sofre mudanças ao longo do seu ciclo de vida, e isto precisa está sempre na mente dos desenvolvedores que esperem que o sistema dure mais do que sua primeira versão.

O que não quer dizer que é preciso prever alterações (as Metodologias Ágeis estão ai para provar que adivinhar não funciona), mas que, com um pouco de cuidado e atenção no design do código, é possível reduzir o impacto de mudanças ao longo do sistema.

O princípio sugere então que, se um código está funcionando, e uma mudança precisa ser feita, é melhor adicionar novos códigos do que alterar entidades que já estão funcionando [1]. O design deve ser feito de tal maneira que, adicionar funcionalidades mantenha o máximo de código existente [4]. Código existente se refere a código que funciona, ou seja, código que já foi testado e aprovado, e que não precisa ser alterado (ou ao menos não deveria).

Em [1] a abstração é apontada como ponto chave para seguir o padrão. Para não cair apenas na teoria vamos analisar o exemplo mostrado em [1]. O problema consiste em uma aplicação que precisa desenhar formas geométricas (círculos e quadrados) em uma GUI.

Fazer então um método que desenhe as formar utilizando if’s para identificar qual o tipo de forma a ser desenhada é obviamente um mal design, como pode ser visto no código a seguir:

void DrawAllShapes(ShapePointer list[], int n)
{
  int i;
  for (i=0; i<n; i++)
  {
    struct Shape* s = list[i];
    switch (s->itsType)
    {
    case square:
      DrawSquare((struct Square*)s);
    break;
    case circle:
      DrawCircle((struct Circle*)s);
    break;
    }
  }
}

Acrescentar uma nova forma causaria uma modificação neste switch e em todos os outros switch’s espalhados pelo código do sistema. A solução seria utilizar uma classe base abstrata, que define o método de desenho, e definir as formas como subclasses que implementem este método, assim garantindo que o sistema é fechado para mudanças nas formas, como pode ser visto a seguir:

void DrawAllShapes(Set<Shape*>& list)
{
  for (Iterator<Shape*>i(list); i; i++)
     (*i)->Draw();
}

Ainda em [1], Martin comenta sobre como é possível fechar o sistema para outras mudanças, como a ordem em que as figuras são desenhadas. Apesar de simples, o exemplo acima mostra bem a importância deste princípio.

Seguir esse princípio traz um dos maiores benefícios propostos pela orientação a objetos: a facilidade de manutenção [1]. No entanto, como todo princípio, o OCP torna o sistema mais flexível, mas introduz complexidade e abstrações ao sistema, devendo assim ser utilizado apenas aonde é mais propenso a mudanças [4].

Padrões de Projeto

O exemplo comentado deixa bem claro que a aplicação do padrão Factory Method evitaria problemas com a alteração das formas. E o Factory Method é um bom exemplo de uso do princípio OCP, pois ele fecha o sistema a alterações nas classes produtos, deixando extensível a adição de novas classes.

Outro bom exemplo deste princípio é o padrão Template Method, pois ele fecha o sistema a mudanças no algoritmo, mas permite extensões, através dos pontos gancho que são definidos nas suas subclasses. Desta forma, adicionar um novo método apenas necessita que uma nova classe herde o comportamento da classe base e defina os pontos que precisam ser alterados.

O padrão State dá uma grande flexibilidade ao sistema, permitindo a adição de novos estados apenas utilizando a herança. No entanto, ao adicionar um novo estado, dependo da implementação, é preciso alterar a parte do código que decide qual o próximo estado. Neste caso, apesar de não ser muito bem fechado para mudanças de estados, o padrão State minimiza a necessidade de alterações.

Já o padrão Strategy oferece um fechamento muito bom a mudanças nos comportamentos, pois o código cliente utiliza apenas uma interface, permitindo variar sem grandes alterações o comportamento a ser utilizado.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] http://www.objectmentor.com/resources/articles/ocp.pdf

[2] http://blog.lambda3.com.br/2009/09/usando-o-principio-abertofechado-open-closed-principle-ocp/

[3] http://en.wikipedia.org/wiki/Open/closed_principle

[4] http://www.oodesign.com/open-close-principle.html

Princípios de Design de Software – Single Responsibility Principle

No post anterior foi feita uma pequena introdução sobre o que é Design de Software, principalmente do ponto de vista dos Métodos Ágeis. Ao final do post comentamos brevemente sobre os princípios de Design de Software conhecidos como SOLID. Então nesta série de posts vamos comentar sobre cada um deles, começando com o Single Responsibility Principle (SRP)

Single Responsibility Principle

Este princípio diz que cada classe deve ter apenas uma responsabilidade[1]. Quando várias responsabilidades são agregadas em uma única classe, mudanças em uma destas pode ocasionar problemas com as outras responsabilidades.

Em [1], Martin mostra um excelente exemplo de como este princípio é quebrado, os possíveis efeitos colaterais e como melhorar o design das classes.

O exemplo é simples, um sistema que precisa representar um retângulo, que será então utilizado por outros dois subsistemas: um que faz computações geométricas com os retângulos e outro que trabalha com aplicações gráficas. Observe o diagrama a seguir, exibido em [1]:

É construída uma classe Retângulo com dois métodos básicos: draw e area. A princípio parece não haver nenhum problema com o design acima, o subsistema que trabalha com gráficos com certeza utiliza o método de desenho e, eventualmente, pode realizar alguma computação geométrica. No entanto a aplicação que faz computação geométrica não vai utilizar o método de desenho.

O problema maior ocorre quando é feito o deploy da aplicação. Imagine que o sistema é feito em C++, todas as bibliotecas gráficas seriam lincadas com a aplicação geométrica, causando perca no desempenho do sistema. Se for uma aplicação Java, os arquivos .class das bibliotecas gráficas seriam também entregues junto com a aplicação de computação geométrica, mesmo que ela nunca utilize os métodos gráficos [1].

Além disso, se a aplicação gráfica causar alguma mudança na classe retângulo será necessário construir, testar e refazer a operação de deploy da aplicação de computação geométrica para garantir que as alterações não afetem este subsistema. Martin sugere então a seguinte alteração no design desse exemplo [1]:

Com este novo design é feita a separação das responsabilidades de computação geométrica e de desenho, deixando os subsistemas relacionados apenas com o que é realmente necessário.

Martin também explica em [1] que responsabilidade pode ser entendida como razão para mudança, ou seja, quando uma classe tem mais do que um motivo para mudar, quer dizer que ela tem mais que uma responsabilidade. Pelo simples exemplo acima é possível ver todos os problemas que o não respeito deste princípio pode trazer.

Padrões de Projeto

Observando os padrões de projeto, é fácil notar que quase todos os padrões procuram seguir este princípio. Observando, por exemplo, o padrão Strategy é fácil ver que, com a separação do comportamento e do contexto, busca-se separar as responsabilidades. Assim as mudanças são feitas localmente e dificilmente irão se propagar ao longo do sistema. De maneira similar, o padrão Visitor também busca separa operações do conjunto de dados. Os padrões Adapter e Bridge também buscam separar as abstrações das implementação, dando maior flexibilidade ao sistema.

Quase todos os padrões buscam melhorar o design e separar as responsabilidades. Outro bom exemplo são os padrões de criação, que assumem a responsabilidade de criação de objetos, deixando o cliente livre desta responsabilidade.

Para saber mais sobre o princípio SRP é possível analisar os padrões de projeto e observar como as responsabilidades são divididas entre todas as classes, bem como analisar outros design de sistemas que sigam o princípio. Como comenta Martin em [1], este princípio é um dos mais simples para entender, mas um dos mais difíceis para seguir.

Uma boa referência sobre o padrão é o material de Martin [1], bem como todas as outras referências comentadas no post sobre Design de Software.

Se gostou do post compartilhe com seus amigos e colegas, senão, comente o que pode ser melhorado. Possui alguma outra opinião ou alguma informação adicional? Comenta ai! 😀

Referências:

[1] http://www.objectmentor.com/resources/articles/srp.pdf