Desvendando Interfaces
Neste artigo gostaria de abordar um assunto nem sempre bem compreendido pelos desenvolvedores e que está profundamente vinculado aos fundamentos da Orientação a Objetos. Refiro-me ao uso de interfaces.
Embora o termo seja ambíguo, dado seu amplo emprego no jargão da informática, chamo a atenção para uma estrutura de código peculiar que possibilita ampliar consideravelmente o nível de abstração de qualquer projeto orientado a objetos.
Tanto no Delphi, quanto em qualquer linguagem de programação que implemente OO, o uso das interfaces é sempre recorrente, visto que minimizam os problemas do desenvolvedor com implementações de terceiros, além de possibilitar um alto grau de versatilidade a seus próprios
modelos de classes.
A idéia central
Embora o termo seja ambíguo, dado seu amplo emprego no jargão da informática, chamo a atenção para uma estrutura de código peculiar que possibilita ampliar consideravelmente o nível de abstração de qualquer projeto orientado a objetos.
Tanto no Delphi, quanto em qualquer linguagem de programação que implemente OO, o uso das interfaces é sempre recorrente, visto que minimizam os problemas do desenvolvedor com implementações de terceiros, além de possibilitar um alto grau de versatilidade a seus próprios
modelos de classes.
A idéia central
Uma interface é um tipo especial de estrutura previsto no escopo da OOP que, de um modo geral, objetiva definir um “contrato” a ser seguido por um certo conjunto de classes. Tal “contrato” estipula uma única regra fundamental: a de que todas as classes vinculadas deverão implementar todos os métodos declarados
pela interface.
Por mais simples que possa parecer, esta regra tem grande impacto sobre qualquer aplicação cujas funcionalidades dependam de um modelo de classes bem estruturado.
Em primeiro lugar, a obrigatoriedade da implementação dos métodos declarados em uma interface, nos permite concluir que todas as classes em questão poderão invocar os mesmos métodos a partir de uma instância de objeto apropriadamente criada. De início, isto impõe determinado grau de saudável padronização.
Em segundo lugar, a padronização que se espera alcançar com isso, tem como conseqüência imediata o aumento da versatilidade do modelo de classes de um modo geral. É exatamente o que acontece que empregamos, nestes casos, os recursos de typecasting (conversão de tipos) da linguagem.
Como declarar uma interface
A declaração da uma interface é algo realmente simples ser feito. Na verdade, o que pode parecer um pouco difícil para o desenvolvedor que quiser utilizar esta técnica, é identificar as situações nas quais será recomendável o uso de interfaces, já que nem sempre precisamos utilizá-las diretamente em todas as aplicações que criarmos.
Identificar tais situações dependerá freqüentemente do domínio que o desenvolvedor possuir sobre os conceitos e aplicações da Orientação a Objetos. Em razão disso, abordaremos o lado mais prático deste recurso, de modo a não nos prendermos muito a estas questões.
Inicialmente, é preciso saber que a implementação de interfaces no Delphi depende de uma classe-base chamada TInterfacedObject.
Esta classe define os recursos fundamentais linguagem aplicados ao gerenciamento das interfaces que criarmos.
pela interface.
Por mais simples que possa parecer, esta regra tem grande impacto sobre qualquer aplicação cujas funcionalidades dependam de um modelo de classes bem estruturado.
Em primeiro lugar, a obrigatoriedade da implementação dos métodos declarados em uma interface, nos permite concluir que todas as classes em questão poderão invocar os mesmos métodos a partir de uma instância de objeto apropriadamente criada. De início, isto impõe determinado grau de saudável padronização.
Em segundo lugar, a padronização que se espera alcançar com isso, tem como conseqüência imediata o aumento da versatilidade do modelo de classes de um modo geral. É exatamente o que acontece que empregamos, nestes casos, os recursos de typecasting (conversão de tipos) da linguagem.
Como declarar uma interface
A declaração da uma interface é algo realmente simples ser feito. Na verdade, o que pode parecer um pouco difícil para o desenvolvedor que quiser utilizar esta técnica, é identificar as situações nas quais será recomendável o uso de interfaces, já que nem sempre precisamos utilizá-las diretamente em todas as aplicações que criarmos.
Identificar tais situações dependerá freqüentemente do domínio que o desenvolvedor possuir sobre os conceitos e aplicações da Orientação a Objetos. Em razão disso, abordaremos o lado mais prático deste recurso, de modo a não nos prendermos muito a estas questões.
Inicialmente, é preciso saber que a implementação de interfaces no Delphi depende de uma classe-base chamada TInterfacedObject.
Esta classe define os recursos fundamentais linguagem aplicados ao gerenciamento das interfaces que criarmos.
TInterfacedObject = class(TObject,
IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID;
out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
Como podemos ver na declaração acima, este gerenciamento se traduz basicamente pelo controle da alocação e desalocação dos recursos consumidos pelas interfaces definidas na sua aplicação.
Disso resulta uma constatação peculiar: a de que não precisamos nos preocupar com a destruição de referências. Em outras palavras você não precisará — nem poderá — usar métodos como o Free quando trabalhar diretamente com estas estruturas. Base herdar a classe TInterfacedObject na declaração da classe que estiver implementando.
Muito bem. Agora, suponha que precisamos projetar um modelo de classes altamente extensível que possa ser complementado por desenvolvedores que não participaram, desde o início, das definições iniciais.
Estes hipotéticos desenvolvedores precisarão conhecer, na medida do necessário os recursos básicos previstos no modelo de classes.
Para auxiliá-los desenvolveremos uma interface que estabelecerá as regras para que eles possam criar suas próprias classes descendentes sem que, com isso, tenhamos que nos preocupar com alterações no código-fonte das nossas classes originais.
type
ICalculadora = Interface
function getResult :
double;
procedure setResult
( value : double );
procedure calculate
( x, y : double );
property result : double
read getResult write setResult;
end;
A princípio, podemos ver que a declaração de uma interface é similar à de uma classe. Entretanto, todas as assinaturas de métodos são públicas e também não há declarações de variáveis internas, mas somente de métodos e propriedades.
Além disso, o mais importante é que os métodos são apenas declarados, nunca implementados. Esta tarefa será exclusiva das classes que fizerem referência à interface ICalculadora.
Como implementar uma interface
A implementação da interface ICalculadora consiste em redeclararmos os métodos definidos e inserirmos um código de programação que possibilite a execução de alguma rotina de cálculo.
TSoma = class ( TInterfacedObject,
ICalculadora )
private
fResult : double;
protected
function getResult : double;
procedure setResult ( value : double );
procedure calculate ( x, y : double );
end;
TDivisao = class ( TInterfacedObject,
ICalculadora )
private
fResult : double;
protected
function getResult : double;
procedure setResult ( value : double );
procedure calculate ( x, y : double );
end;
Note que o compilador obrigará a criação dos blocos begin/end para cada método, mas não exigirá que, de fato, escrevamos qualquer comando dentro deles.
Como dissemos, a finalidade das interfaces é apenas permitir o estabelecimento de um “contrato” entre as classes que obrigará o desenvolvedor a inserir uma referência dos métodos nas classes que tiver que implementar. Logo veremos a utilidade disso.
function TSoma.getResult:
double;
begin
result := Self.fResult;
end;
procedure TSoma.setResult(value:
double);
begin
self.fResult := value;
end;
function TDivisao.getResult:
double;
begin
result := Self.fResult;
end;
procedure TDivisao.setResult(value:
double);
begin
Self.fResult := value;
end;
Os métodos acima precisam ser implementados separadamente por TSoma e TDivisao porque ambas as classes seguem o “contrato” definido por ICalculadora. Neste exemplo, estamos partindo da idéia que cada classe implementadora poderá ser construída a partir da interface. Daí, poderíamos ter também uma classe “TMultiplicação”, “TSubstração” etc.
procedure TSoma.calculate(x,
y: double);
begin
setResult ( x + y );
end;
procedure TDivisao.calculate(x,
y: double);
var
r : double;
begin
if ( y = 0 ) then begin
setResult ( 0
);
raise exception.Create
( “Divisão por zero” );
end;
setResult ( x / y );
end;
Observe que temos agora duas classes que implementam, cada uma à sua maneira, o mesmo método Calculate.
A importância da interface ICalculadora foi a de garantir que o método esteja presente em ambas implementações.
Unindo os pontos
Se você acompanhou até aqui, estamos prontos para construir uma pequena aplicação que demonstre o funcionamento destas classes. Para isto sugiro que desenhe um formulário similar ao da figura abaixo.
No evento OnClick do botão “Calcular”, insira o código-fonte apresentado a seguir.
procedure TForm1.btnCalcularClick(Sender:
TObject);
var
x, y : double;
calc : ICalculadora;
begin
x := strToFloat ( edit1.text );
y := strToFloat ( edit2.text );
try
case grpOp.itemIndex of
0 : calc
:= TSoma.Create;
1 : calc
:= TDivisao.Create;
end;
calc.calculate( x, y );
showMessage ( floatToStr (
calc.output ) );
except
raise;
end;
end;
Conclusão
No código-fonte acima gostaria de destacar dois pontos fundamentais.
O primeiro deles refere-se à criação de uma referência à interface e à chamada ao método Create. Neste ponto, “calc” é uma referência genérica a todas as classes que implementam a interface. Sendo assim, representa apenas os recursos comuns publicados e não pode ser instanciada diretamente.
O segundo ponto refere-se à chamada do método Calculate e à leitura do retorno do cálculo via propriedade Output. Como podemos notar, o uso da interface assegurou a validade da mesma chamada tanto para a classe TSoma, quanto para a classe TDivisao.
Na verdade, o que mudou foi apenas a maneira como o cálculo foi implementado.
Por fim, gostaria apenas que ressaltar que o uso de interfaces simplifica o trabalho do projetista e impõe rígidas regras de implementação que padronizam qualquer modelo de classes. Isto é muito importante para garantir da extensibilidade e confiabilidade do seu projeto orientado a objetos.
Fonte: http://imasters.com.br/artigo/3506/linguagens/desvendando-interfaces?trace=1519021197&source=single
0 comentários:
Postar um comentário