6. Interfaces
-------------

Podemos querer modelar coisas em nossos programas que são
abstratas demais para serem representadas com classes.
Todos os objetos de uma classe possuem os mesmos campos, e
isso os torna muito parecidos entre si. Vejamos um exemplo
(o código está no projeto Funcoes). Queremos modelar
funções reais de uma variável, como as funções abaixo:

f(x) = 5x^2 + 2x
g(x) = 2^x
h(x) = ln(x)

Não podemos modelar todas essas funcões usando uma classe
"Funcao", pois cada uma dessas funções precisaria ser modelada
por diferentes campos: a primeira poderia ser modelada por
como uma função polinomial com coeficientes 0, 2 e 5, a segunda
como uma função exponencial com base 2, e a terceira como uma
função logarítmica de base e.

O que essas três funções, e qualquer outra função real de uma
variável, têm em comum é o fato de que dado um número real
(x) elas produzem outro número real (f(x), g(x), h(x), etc.).
Mas isso não é um campo, e sim um *método*: 
"double valor(double x)". 

Quando modelamos um conjunto de objetos através de seus
campos e as implementações de seus métodos não conseguimos
descrever conjuntos mais abstratos, mas se usarmos apenas
as *assinaturas* dos métodos ganhamos os graus de liberdade
que nos permitem modelar conjuntos mais abstratos. Assim,
funções são objetos que possuem um método "double valor(double x)",
não importa como ele é implementado.

A ferramenta que usamos em Java para modelar esses conjuntos
são as *interfaces*. Uma interface possui apenas *assinaturas*
de métodos (seus nomes e parâmetros), não podendo possuir
nem campos nem implementações dos métodos. Assim, nossas
funções reais de uma variável podem ser modeladas como a
seguinte interface "Funcao":

public interface Funcao {
  double valor(double x);
}

Não é necessário qualificar os métodos de uma interface como
"public", pois todos eles são públicos automaticamente. Uma
interface não pode ter o mesmo nome que outra interface ou
outra classe, mas ela pode ter métodos com o mesmo nome,
contanto que eles tenham assinaturas diferentes.

Não podemos instanciar uma interface; ela não tem campos, ou
construtores. Para ter objetos de uma interface precisamos
definir classes que *implementam* a interface. Essas classes
irão modelar subconjuntos mais concretos do conjunto abstrato
que estamos modelando com a interface. Para a interface Funcao
podemos modelar tipos específicos de função com classes:
funções polinomiais, exponenciais, logarítmicas... na
declaração da classe listamos as interfaces que ela implementa
usando a palavra-chave "implements":

public class Polinomial implements Funcao {
  ...
}

Como a classe vai implementar a interface fica a critério
dela. Ela apenas precisa declarar e fornecer implementações
para cada método da interface:

// Funções constantes
class Constante implements Funcao {
  public double c;
  public Constante(double c) { this.c = c; }
  
  // Implementação da interface
  public double valor(double x) {
      return c;
  }
}

Uma vez que temos classes que implementam uma interface
podemos criar instâncias da interface:

Funcao f = new Constante(10);
System.out.println(f.valor(2));  // imprime 10
f = new Polinomial(0, 2, 5);
System.out.println(f.valor(2));  // imprime 24

Uma variável com tipo de uma interface pode fazer referência
para objetos de *qualquer* classe que implementa a interface.
A única coisa que podemos fazer com uma variável dessas é
chamar os métodos da interface. Quaisquer campos ou outros
métodos daquele objeto ficam invisíveis, mesmo que sejam
públicos:

Funcao f = new Constante(10);
System.out.println(f.c);  // erro de compilação!


