Últimas Palavras

Herança múltipla de implementação em Java

É fato conhecido que a linguagem Java não dá suporte nativo a herança múltipla de implementações, assim como C++, o que é possível é herança múltipla virtual, onde você consegue herdar assinaturas de métodos através das interfaces, mas você ainda tem que implementar todos esses métodos na classe que implementa a interface, ou declarar ela abstrata.
Por mais que os argumentos que fizeram o grupo que desenvolveu a linguagem sejam fortes, existem vários problemas que seriam melhor resolvidos através do uso de herança múltipla, como a persistência de objetos (que é o exemplo que nós vamos tratar aqui).


Outras linguagens de programação orientadas a objeto, como Ruby, utilizam uma maneira diferente pra se conseguir os efeitos de herança múltipla sem ter suporte direto a essa funcionalidade, os Mixins. Com Mixins você reúne atributos e comportamentos específicos em uma “unidade”, que se assemelha a uma classe, mas que não vai se relacionar da mesma forma que duas classes se relacionariam em herança.


Quando uma classe “herda” um Mixin ela não é uma especialização do Mixin, ela adquire (ou adiciona) os comportamentos do Mixin. É como implementar interfaces em linguagens sem suporte a herança múltipla, como Java ou C#, só que além das facilidades de polimorfismo conseguidas pelas declarações de métodos e propriedades (apenas em C#) das interfaces, a implementação do Mixin também é adquirida.

Inter-types e o Aspectj

Uma das bibliotecas mais utilizadas para programação orientada a aspectos em Java, o AspectJ, dá suporte a construção de objetos que lembram o funcionamento dos Mixins, através da criação de interfaces “com implementação” usando inter-type declarations (declarações inter-tipo). As declarações inter-tipo podem ser utilizadas para fornecer comportamentos ou capacidades comuns a objetos, sem que eles próprios tenham que implementar essas funcionalidades.


Um exemplo comum disso é a implementação do padrão de projeto Observer. Normalmente quando você implementa esse padrão, você não está interessado exatamente no padrão, mas sim em adicionar um comportamento a sua classe de negócio, que é avisar a outros objetos que ela foi atualizada. As vezes você termina herdando de Observable (em Java) simplesmente porque não existe outra opção, já que a linguagem não dispõe de herança múltipla de implementação. Mas utilizando os inter-types do AspectJ nós vamos conseguir a mesma modularidade de um Mixin em Java.


Antes de partirmos diretamente para os exemplos, você precisa ter o AspectJ instalado na sua máquina. Se você usa o Eclipse como IDE, é extremamente simples, basta instalar o plugin do AJDT ( http://eclipse.org/ajdt/ ) pra a versão do Eclipse que você está utilizando. Se você não usa o Eclipse (ou não pode usar), vai ter que utilizar o compilador de linha de comando do AspectJ, que está disponível na página do projeto ( http://eclipse.org/aspectj/ ). Estou assumindo que você tem o Eclipse instalado com o plugin do AJDT.


O AspectJ é uma extensão não oficial da linguagem Java que adiciona novos constructos para se trabalhar com o paradigma da programação orientada a aspectos. Ele foi desenvolvido originalmente no PARC da Xerox e hoje é um dos projetos sob o comando da Eclipse Foundation, junto com o AJDT, que é o plugin do AspectJ para o Eclipse.


Com a AJDT instalada no seu Eclipse, você já pode criar um projeto do AspectJ, basta ir em:
“File” -> “New” -> “AspectJ Project”


Pronto, você já está com o seu primeiro projeto do AspectJ pronto pra ser utilizado!

O que é um aspecto?

Agora que você já tem todo o ambiente de produção pronto, podemos começar a implementar os nossos exemplos. Um aspecto, assim como uma classe, é uma unidade de modularidade, encapsulamento e abstração, mas ele age de uma forma diferente de uma classe para atingir esses objetivos. Um aspecto é utilizado para implementar interesses ortogonais (crosscutting concerns).


Um interesse ortogonal é um comportamento que não faz parte diretamente do funcionamento de uma classe, como por exemplo aplicar um controle de segurança sobre certos objetos da sua aplicação que fazem acesso a informações críticas do banco de dados. É claro que você pode colocar o controle de segurança dentro desses próprios objetos, mas você vai estar misturando o trabalho deles de acessar ao banco de dados com o de controle de segurança, o que pode gerar tanto repetição de código (já que o mesmo código poderia ser utilizado em várias classes diferentes) como perda de “identidade” dos objetos, já que eles estão fazendo dois trabalhos distintos que não estão relacionados.

Implementando o padrão observer

Vamos começar pelas definições das classes e interfaces do nosso exemplo (imports e packages foram retirados dos arquivos):
Listagem 1 – interface Observável
public interface Observavel {

    void adicionarObservador( Observador obs ) ;

    void avisarObservadores();

    void removerObservador( Observador obs );

}
Listagem 2 – classe Observador

public class Observador {

            private Observavel observavel;

    public void atualizar( Object mensagem ) {
            // faz alguma coisa com o objeto
            System.out.print(" Recebi um aviso de: "  + mensagem.toString() );
    }

    public void setObservavel( Observavel observavel) {
            this.observavel = observavel;
    }

    public Observavel getObservavel () {
            return this.observavel;
    }

}
Listagem 3 – classe que vai se tornar observável – Curso
public class Curso {
            private static final long serialVersionUID = 1L;
            private String descricao;
            private String nome;

            public String getDescricao() {
                        return descricao;
            }
            public void setDescricao(String descricao) {
                        this.descricao = descricao;
                        this.avisarObservadores();
            }
            public String getNome() {
                        return nome;
            }
            public void setNome(String nome) {
                        this.nome = nome;
                        this.avisarObservadores();
            }
}

Listagem 4 – aspecto que implementa a interface Observável
public aspect ObservavelAspect {

            private Set Observavel.observadores = new HashSet();

            public void Observavel.adicionarObservador( Observador observador ) {
                        this.observadores.add(observador);
                        observador.setObservavel(this);
            }

            public void Observavel.removerObservador( Observador observador ) {
                        this.observadores.remove(observador);
                        observador.setObservavel(null);
            }

            public void Observavel.avisarObservadores(  ) {
                        Iterator iterator = this.observadores.iterator();
                        while ( iterator.hasNext() ) {
                                   Observador observador = (Observador) iterator.next();
                                   observador.atualizar(this);
                        }
            }

            declare parents : br.edu.cefetpb.db.Curso implements Observavel;

}

O código das três classes Java é normal, não existem novidades, a não ser o método “avisarObservadores()” da classe Curso que não deveria estar lá, já que Curso não implementa a interface Observável, mas essa implementação é feita pelo aspecto ObservavelAspect.


Um aspecto do AspectJ é declarado de forma parecida com as classes normais do Java. Um aspecto pode ter os modificadores “public”, “abstract”, “final”, “privileged” ou nenhum, a maioria funciona da mesma maneira que funcionam nas estruturas do Java. Um aspecto também pode herdar de outros aspectos (mas não de outra classe) e implementar interfaces, para todos os efeitos eles parecem classes Java normais, mas são “transformados” pelo compilador especial do AspectJ.
No nosso aspecto de exemplo (na listagem 4) nós vemos várias declarações de métodos e uma declaração de atributo. Como você já percebeu, essas declarações são parecidas com as declarações das classes Java comuns, entretanto, antes do nome do atributo ou método é colocado o nome da interface, que nesse caso é Observável. Essas são as inter-type declarations, elas estão declarando métodos e atributos na interface.


Mas aí você se pergunta, como é que declara um método ou um atributo em uma interface? O método ou atributo não é declarado na interface e sim no objeto que vai implementar a interface. Aquele código que está definido dentro do aspecto vai ser “inserido” dentro da classe quando ela for compilada pelo AspectJ, como se fosse realmente um código da própria classe, tanto que quando nós nos referenciamos a “this” dentro daquele código, não é uma instância do aspecto que nós nos referenciamos, mas sim ao objeto que implementou a interface Observável.


Sempre que você declarar um atributo, método ou até mesmo um construtor (esse não pode ser adicionado a interfaces) dentro de um aspecto que se inicie por um nome de classe, como por exemplo “Observável.avisarObservadores()”, você está dizendo que essa implementação deve ser inserida na classe “Observável” ou no objeto que implementar a interface “Observável”, no caso dela ser uma interface. Desse modo você garante que uma implementação vai poder ser reutilizada entre várias classes sem partir para o uso de herança, bastando apenas montar uma interface como nós fizemos.
Mas ainda existe um problema, a classe Curso não indicou em momento algum que implementa a interface Observável, como é que ela recebeu a implementação da interface? Através da declaração que está no fim do aspecto:

            declare parents : br.edu.cefetpb.db.Curso implements Observavel;

A diretiva “declare parents” indica que uma classe ou um conjunto de classes vai implementar ou herdar de uma classe específica e as classes nem mesmo precisam saber disso. Existem outros tipos de diretivas “declare”, como “error”, “soft” e outras, mas não vamos abordar elas nesse momento.


A sintaxe da diretiva é simples:

declare parents : “tipo” implements/extends Classe/Lista de interfaces separadas por “,”

Você simplesmente inicia por “declare parents :”, o tipo pode ser o nome completo de uma classe ou utilizar curingas para indicar várias classes ou pacotes. Os curingas que podem ser utilizados são:

Você pode usar implements para interfaces (elas são separadas por “,”) ou extends para indicar a herança de apenas uma classe. O mais comum é usar interfaces e implementar elas dentro do aspecto, mas você pode ter uma necessidade especial.

Implementando o padrão Active Record usando aspectos

O padrão de projeto Active Record é uma maneira mais simples de adicionar persistência a o seu sistema sem ter que criar dúzias de DAOs ou repositórios para cada objeto. Esse padrão é multo utilizado no framework de desenvolvimento web “Ruby On Rails” da linguagem Ruby, mas é pouco utilizado em aplicações Java, exatamente por causa dos problemas com herança única de implementação (em Ruby existem Mixins).


Mas agora que já sabemos como simular herança múltipla em Java, podemos fazer o mesmo que os nossos companheiros em Ruby fazem, matar algunas dúzias de DAOs e simplificar o nosso código de persistência. Vejamos como fazer um Active Record em Java utilizando o conhecido framework de mapeamento objeto/relacional Hibernate.

Listagem 5 – interface ActiveRecord
public interface ActiveRecord extends Serializable {

      public abstract void atualizar();

      public abstract  List buscarPorExemplo();

      public abstract  Object carregarPeloId(Integer id);

      public abstract  void excluir();

      public abstract  Integer getId();

      public abstract  Integer salvar();

      public abstract  void salvarOuAtualizar();

      public abstract  void setId(Integer id);

      public abstract  List listar();

}
Listagem 6 – Aspecto que implementa a interface ActiveRecord usando o Hibernate

public aspect ActiveRecordAspect {

      private Integer ActiveRecord.id;

      public void ActiveRecord.setId ( Integer id ) {
                  this.id = id;
      }

      public Integer ActiveRecord.getId() {
                  return this.id;
      }

      public Integer ActiveRecord.salvar() {
                  return (Integer) HibernateUtility.getSession().save(this);
      }

      public void ActiveRecord.atualizar() {
                  HibernateUtility.getSession().update(this);
      }

      public void ActiveRecord.excluir() {
                  HibernateUtility.getSession().delete(this);
      }

      public Object ActiveRecord.carregarPeloId(Integer id ) {
                  return HibernateUtility.getSession().get( this.getClass(), id);
      }

      public void ActiveRecord.salvarOuAtualizar( ) {
                  HibernateUtility.getSession().saveOrUpdate( this );
      }

      public List ActiveRecord.listar() {
                  return HibernateUtility.getSession().createCriteria( this.getClass() ).list();
      }

      public List ActiveRecord.buscarPorExemplo() {
                  Criteria criteria = HibernateUtility.getSession().createCriteria( this.getClass() );

                  Example sample = Example.create( this );
                  sample.enableLike();
                  sample.excludeZeroes();

                  criteria.add( sample );

                  return criteria.list();
      }

      declare parents : br.edu.cefetpb.db.Pessoa implements ActiveRecord;
}

Agora nós temos um Active Record implementado e pronto pra ser utilizado em qualquer aplicação que utilize o Hibernate, sem contar que o campo “id” vai ser padronizado para todos os objetos que sejam persistíveis, mantendo uma homogeneidade no acesso aos identificadores dos objetos.

Agora é só usar

Você já sabe como utilizar o AspectJ para simular herança múltipla em Java, mas a programação orientada a aspetos e as possibilidades do AspectJ são muito mais do que simplesmente isso. Veja a documentação do projeto e descubra tudo o que ele pode fazer pra facilitar a sua vida e simplificar o seu código.

Referências:


Colyer, Adrian; Clement, Andy; Harley, George; Webster, Matthew. Eclipse AspectJ: Aspect Oriented Programming with AspectJ and the Eclipse AspectJ Development Tools. Pearson Education Inc., Maryland, Estados Unidos. Dezembro de 2004.


Site do Eclipse: http://eclipse.org/


Site do AJDT: http://eclipse.org/ajdt/


Site do AspectJ: http://eclipse.org/aspectj/






















Creative Commons License
O conteúdo textual deste site (a não ser que definido outra coisa no próprio texto) está sobre a licença Creative Commons Attribution-NonCommercial 2.0 Brazil License. Imagens, marcas e outros são propriedade de seus respectivos donos e estão utilizadas aqui com intuito meramente informativo.