[Pesquisar este blog]

domingo, 19 de novembro de 2017

Java 9::Métodos Privados em Interfaces

Uma das novidades da versão 9 do Java são os métodos com visibilidade privada nas interfaces. Este novo elemento, acrescido aos métodos default e estáticos incluídos na versão 8, vem para completar as alternativas construtivas nas interfaces.

Este post é parte da série Java 9::Primeiras Impressões, que comenta as principais características da nova versão do Java.

Interfaces

Uma interface é a definição de conjunto de elementos visíveis, ou seja, públicos, dos objetos de uma classe. Quando a implementação de uma classe contém todas as operações de conjunto particular, dizemos que tal classe realiza tal interface, ou seja, esta classe atende as especificações ditadas pela interface implementada.

Uma interface Java é algo simples como:

package jandl.j9;
public interface Adder {
void add(Number value);
Number getTotal();
void setTotal(Number initValue);
void reset();
}

Implicitamente, todas as operações de uma interface são, por padrão, públicas e abstratas, ou seja, todas poderiam receber o especificador de acesso public e o modificador abstract em suas declarações.

Desta maneira, uma interface é como um contrato, que dita quais operações devem estar disponíveis para obter-se um conjunto específico de funcionalidade (e de interoperabilidade, portanto). Isto é particularmente importante, pois a questão aqui é como os objetos podem ser utilizados, e não como foram implementados.

A classe AdderImpl que segue é uma implementação possível da interface Adder.

package jandl.j9;
public class AdderImpl implements Adder {
private double total;
public AdderImpl() { reset(); }
public AdderImpl(Number value) { setTotal(value); }
@Override
public void add(Number value) { total = total + ((Number) value).doubleValue(); }
@Override
public Number getTotal() { return new Double(total); }
@Override
public void setTotal(Number initValue) { total = ((Number) initValue).doubleValue(); }
@Override
public void reset() { total = 0; }
}

Desta maneira, classes pertencentes a diferentes hierarquias de objetos, mas que implementam uma interface comum, podem, polimorficamente, serem tratadas como de um mesmo tipo que corresponde a tal interface.

A modelagem de sistemas por meio da definição de interfaces é uma prática reconhecidamente boa, também conhecida como programação por contratos.

Assim, o uso de interfaces é particularmente importante no projeto de sistemas, o que também é enfatizado pelos princípios SOLID (discutidos numa série de posts iniciados por Os Princípios SOLID – Parte I).

Evolução de interfaces

Apesar das muitas conveniências no projeto e uso de interfaces, surgem situações onde uma interface necessita ser alterada. Aí o cenário deixa de positivo. Modificar uma interface, no sentido de alterar a assinatura de um dos métodos presentes ou acrescentar novas operações cria a exigência de modificar ou completar todas as classes que realizam tal interface, numa constrangedora propagação das alterações realizadas.

A partir do Java 8 se tornou possível realizar alterações numa interface existente, mantendo a compatibilidade com suas versões anteriores, isto é, sem propagar as alterações, simplificando muito o trabalho de manutenção do código. Para isto foram introduzidos os métodos default e estáticos às interfaces.

Os métodos default são aqueles declarados com o especificador de visibilidade public e com o novo modificador default. Além disso, tais métodos devem ser implementados na própria interface, pois não são abstratos, o que evita a propagação da modificação.

A interface Adder poderia receber um novo método, como segue, sem que as classes que a realizam sejam afetadas.

package jandl.j9;
public interface Adder {
:
// método default, cuja implementação é adicionada na interface modificada
default void add(Adder adder) {  add(adder.getTotal()); }
}

De forma análoga, os métodos estáticos são aqueles declarados com o especificador de visibilidade public e com o conhecido modificador static, o que também exige sua própria interface, pois, como qualquer elemento estático, pertencem a seu tipo e não a suas instâncias. Da mesma forma sua adição não propaga qualquer efeito nas classes que já realizam a interface modificada.

package jandl.j9;
public interface Adder {
:
// método estático, cuja implementação é adicionada na interface modificada
static void add(Adder adder, List<Number> list) {
    list.stream().forEach((value)->{ adder.add(value); });
}
}

As duas alternativas, dos métodos default e dos métodos estáticos, possibilitam a evolução de interfaces existentes, sem propagação de alterações e com garantia da compatibilidade binária com suas versões antigas.

Métodos privados em interfaces

A adição da possibilidade de declarar métodos privados em interfaces, embora estranha à primeira vista, é um complemento para auxiliar o programador no uso dos métodos default e métodos estáticos nas interfaces.

Os novos métodos privados são declarados com o especificador de visibilidade private e, opcionalmente, com o modificar static. Como qualquer membro privado, só podem ser acessados pelos demais membros do seu tipo.

Sendo assim, o uso de métodos privados, estáticos ou não, numa interface tem como objetivo permitir que o programador defina operações auxiliares para os demais métodos default e estáticos públicos, sem expor tal operação.

Na nova implementação da interface Adder, um método privado estático add(Adder, Number[]) provê o serviço de adição para três outros métodos da interface.

package jandl.j9;
import java.util.List;

public interface Adder {
void add(Number value);
Number getTotal();
void setTotal(Number initValue);
void reset();
default void add(Adder adder) { add(adder.getTotal()); }
// métodos default e estáticos que utilizam método privado desta interface
default void add(Number[] array) { add(this, array); }
default void add(List<Number> list) { add(this, list.toArray(new Number[0])); }
static void add(Adder adder, List<Number> list) {
   add(adder, list.toArray(new Number[0]));
}
// método estático privado que prove serviço para os demais
private static void add(Adder adder, Number[] array) {
   for(int i=0; i<array.length;i++) { adder.add(array[i]);
}
}
}

Considerações Finais

As interfaces na versão 9 Java suportam, então, uma variada combinação de especificadores e modificadores, como sumarizado na tabela que segue:

Especificador
Modificador
Situação
Nenhum
Nenhum
Declaração válida de método, implicitamente, público e abstrato.
Nenhum
abstract
Declaração válida de método abstrato, implicitamente público.
Nenhum
default
Declaração válida de método default, implicitamente público.
Nenhum
static
Declaração válida de método estático, implicitamente público.
public
Nenhum
Declaração válida de método público, implicitamente abstrato.
public
abstract
Declaração válida explícita de método público e abstrato.
public
default
Declaração válida explícita de método público e default.
public
static
Declaração válida explícita de método público e estático.
private
Nenhum
Declaração válida explícita de método privado.
private
abstract
Declaração inválida. Erro de compilação.
private
default
Declaração inválida. Erro de compilação.
private
static
Declaração válida explícita de método privado e estático.

Os métodos privados não são herdados por subinterfaces ou implementações das classes.
Finalmente, apesar da utilidade exemplificada, de permitir modificações em interfaces existentes sem a propagação de efeitos colaterais indesejados, cabe destacar que, se uma interface existente deve ser modificada, isto indica duas situações: alterações nas regras de negócio da aplicação ou identificação de falhas no projeto do software. Enquanto a primeira razão pode ser imprevisível e inevitável; a segunda reforça o cuidado necessário com o projeto de qualquer software.

A implementação de métodos (default ou estáticos, públicos ou privados) é, do ponto de vista da Orientação a Objetos, uma violação dos seus propósitos. Se é necessária uma implementação parcial de uma interface, é mais adequada a construção de uma classe abstrata contendo a codificação destas operações, mantendo a interface como uma classe abstrata pura (sem implementação de qualquer operação).

Reforço que isto não é um defeito do Java, mas, acredito, alternativas possíveis na plataforma para solução de problemas envolvendo interfaces.

Assim, muito cuidado no projeto de suas interfaces, que devem conter apenas as operações minimamente necessárias (tal como defendido pelo princípio de segregação das interfaces ou ISP dos princípios SOLID). Qualquer coisa diferente disso, pode não ser adequada, mesmo com todas as possibilidades ofertadas pela plataforma Java.

Para saber mais




Nenhum comentário: