Relações UML

Este artigo tem como principal objectivo de ajudar aos programadores informáticos entender o significado de associação, agregação, composição, abstracção, generalização, especialização, realização e dependência.

Associação

Na elaboração dum diagrama de classes, as classes precisam de ter algum tipo de relação com as outras classes. A associação é caracterizado por: 1-para-muitos, 1-para-1 e muitos-para-muitos.

Associação
Vários tipos de associação
Agregação

É um tipo especial de associação em que um objecto contém outro objecto e é uma relação direccional. Também é conhecido como uma relação “has-a”.

Exemplo de Agregação
Exemplo de Agregação
Composição

É um caso especial de agregação. Nesta relação, um objecto contém outro objecto e também é uma relação “has-a”. Mas há uma característica importante: a composição especifica que quando a classe Pessoa é eliminada então as classes Perna e Mão são também eliminadas. Em contraste com a agregação, quando a classe Empresa é eliminada então a classe Produtos já não é eliminada obrigatoriamente.

Exemplo de composição
Exemplo de composição
Abstracção

Abstracção é um dos importantes conceitos de Programação Orientada por Objectos. Abstracção é esconder informação dum utilizador. Em vez de expor a implementação de código, declara os métodos e as suas informações detalhadas para ajudar a implementação. Exemplo de abstracção são as classes Abstractas e Interfaces.

Generalização

Generalização é uma relação entre superclasse e subclasse. É uma relação “is-a”. É um processo que copia o código comum da superclasse para subclasses. Ou seja, não é necessário reescrever as linhas de códigos gerais. Generalização é um tipo de herança.

Diagrama de classes da Template Method Pattern
Exemplo de Generalização
Especialização

É um processo de criação duma nova subclasse duma superclasse existente. É também uma relação “is-a” e é também um tipo de herança.

Realização

É uma relação entre interface e classe. Na realização, a nova classe implementa todos os métodos abstractos e fornecem a implementação concreta. Também é uma relação “is-a”.

Exemplo de Realização
Exemplo de Realização
Dependência

É uma relação entre duas ou mais classes. Se houver alguma modificação feita numa classe pode afectar as outras classes. Isto significa que uma classe depende doutra e desta maneira uma classe tem dependência na segunda classe.

Exemplo de Dependência
Exemplo de Dependência

Iterator Pattern

Iterator Pattern – provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation – Page 336, Chapter 9, Head First Design Patterns

Em português, a Iterator Pattern fornece um acesso dos elementos duma agregação de objectos sequencialmente sem expor as suas implementações de código.

Diagrama de classes da Iterator Pattern
Diagrama de classes da Iterator Pattern

Analisando o diagrama de classes da Iterator Pattern, considere as seguintes exposições:

  • Iterator: define uma interface para aceder e cruzar os elementos. Aqui usa-se a java.util.Iterator;
  • ConcreteIterator: implementa a interface Iterator. É responsável pela gestão da posição actual da iteração;
  • Aggregate: define uma interface para criar um objecto Iterator;
  • ConcreteAggregate: implementa a interface de criação Iterator para retornar a instância da ConcreteIterator apropriada. Tem uma colecção de objectos e implementa um método que retorna um Iterator para a sua colecção.

Amostra de código da classe “Iterator”

public interface Iterator {

    boolean hasNext();

    Object next();
}

Amostra de código da classe “ConcreteIterator”

public class DinerMenuIterator implements Iterator {

    MenuItem[] items;
    int position = 0;

    public DinerMenuIterator(MenuItem[] items) {
        this.items = items;
    }

    @Override
    public boolean hasNext() {
        return position < items.length && items[position] != null;
    }

    @Override
    public Object next() {
        MenuItem menuItem = items[position];
        position++;
        return menuItem;
    }

}

Amostra de código da classe “Aggregate”

public interface Menu {

    public Iterator createIterator();
}

Amostra de código da classe “ConcreteAggregate”

public class DinerMenu{

    static final int MAX_ITEMS = 6;
    int numberOfItems = 0;
    MenuItem[] menuItems;

    public DinerMenu() {
        menuItems = new MenuItem[MAX_ITEMS];

        addItem("Vegetarian BLT",
                "(Fakin') Bacon with lettuce & tomato on whole wheat", true, 2.99);
        addItem("BLT",
                "Bacon with lettuce & tomato on whole wheat", false, 2.99);
        addItem("Soup of the day",
                "Soup of the day, with a side of potato salad", false, 3.29);
        addItem("Hotdog",
                "A hot dog, with saurkraut, relish, onions, topped with cheese",
                false, 3.05);
        addItem("Steamed Veggies and Brown Rice",
                "Steamed vegetables over brown rice", true, 3.99);
        addItem("Pasta",
                "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
                true, 3.89);
    }

    public void addItem(String name, String description, boolean vegetarian, double price) {
        MenuItem menuItem = new MenuItem(name, description, vegetarian, price);

        if (numberOfItems >= MAX_ITEMS) {
            System.err.println("Sorry, menu is full! Can't add item to menu");
        } else {
            menuItems[numberOfItems] = menuItem;
            numberOfItems++;
        }
    }
    
    public Iterator createIterator(){
        return new DinerMenuIterator(menuItems);
    }
}

Quando se deve usar Iterator Pattern?

  1. quando se pretende aceder ao conteúdo dos objectos agregados sem expor as suas representações internas;
  2. quando se pretende apoiar múltiplos cruzamentos de objectos agregados;
  3. quando se pretende fornecer uma interface uniforme para diferentes cruzamentos de estruturas agregadas.

As conclusões que eu retiro deste estudo são:

  • esta design pattern lida bem com várias variações de cruzamentos duma agregação de objectos;
  • os iteradores simplificam a interface da classe “Aggregate”.

Template Method vs Strategy

Para melhor compreensão nos estudos de Template Method e de Strategy, foi feito uma tabela que identifica as diferenças entre dois.

Template Method Strategy
1. implementa diferentes algoritmos individuais passo-a-passo define uma família de algoritmos
2. mantém controlo na estrutura do algoritmo perde controlo nos algoritmos implementados
3. usa a herança para implementar algoritmos usa composição para implementar algoritmos
4. não tem duplicação de código por causa do conceito herança (é mais eficiente) tem duplicação de código por causa do conceito composição (é menos eficiente)
5. menos flexível porque não pode mudar os algoritmos durante a execução dum programa por causa da herança mais flexível porque pode mudar os algoritmos durante a execução dum programa por causa da composição
6. é o design pattern mais usado pois fornece código fundamental para um determinado comportamento não é o design pattern menos usado mas sim usado para casos pontuais
7. é mais dependente do que Strategy por causa dos métodos de serem implementados na superclasse (herança) é menos dependente do que Template Method por causa dos métodos de não serem implementados na superclasse (composição)

Template Method Pattern

Template Method – defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algoritm’s structure – Page 289, Chapter 8, Head First Design Patterns

Em português, a Template Method Pattern define um esqueleto dum algoritmo no método, deferenciando em algums passos para as subclasses. Template Method deixa as subclasses redefinir determinados passos dum algoritmo sem mudar a estrutura do mesmo.

Diagrama de classes da Template Method Pattern
Diagrama de classes da Template Method Pattern

Analisando o diagrama de classes da Template Method Pattern, considere as seguintes exposições:

  • AbstractClass: define métodos abstractos para poder criar um template method, permitindo assim um esqueleto dum algoritmo.
  • ConcreteClass: herda todos os métodos da AbstractClass permitindo alterações dalguns passos num determinado algoritmo.

Amostra de código da classe “AbstractClass”

public abstract class CaffeineBeverage {
    
    final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    
    abstract void brew();
    
    abstract void addCondiments();
    
    void boilWater(){
        System.out.println("Boiling water");
    }
    
    void pourInCup(){
        System.out.println("Pouring into cup");
    }

}

Amostra de código da classe “ConcreteClass”

public class Coffee extends CaffeineBeverage{    

    @Override
    void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    @Override
    void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
}

Quando se deve usar Template Method Pattern?

  1. quando se pretende implementar as partes imutáveis uma só vez e deixar as partes mutáveis para as subclasses;
  2. quando um comportamento comum entre as subclasses deve ser criado e localizado numa classe comum, evitando assim a duplicação de código;
  3. quando se pretende controlar as heranças das subclasses;

As conclusões que eu retiro deste estudo são:

  • Template Method é a técnica fundamental para reuso de código;
  • Template Method conduz a uma estrutura de controlo invertida que é referenciado como “O princípio de Hollywood”, isto é, “Não nos ligue, nós ligaremos-te!”.