Interfaces
sexta-feira, 20 de Junho de 2008 :: 348 Visualizações :: 0 Comentários :: :: Básicos

Introdução 

A Framework .Net define vários interfaces importantes, pelo que é necessário entender como se pode aproveitar o seu potencial, quer seja pela invocação dos seus métodos ou através da sua implementação em tipos próprios. 

Muitas vezes é possível definir a estrutura de uma aplicação através da implementação de interfaces. 

Os interfaces, em conjunto com conceitos como herança e atributos são um dos pilares para o desenho de aplicações .Net

Definição e implementação

Em traços gerais, um interface é o conjunto de membros que uma classe expõe.

Por exemplo, todos os membros públicos de uma classe pertencem ao interface principal da classe.

Uma classe pode no entanto expor outros grupos de propriedades e métodos que não tem visibilidade pública.

Interface … End Interface

Um interface define apenas a assinatura de propriedades e métodos, sendo a sua codificação da responsabilidade da classe que o implementa.

Diferentes classes podem implementar de forma diferente um mesmo interface (Polimorfismo).

Interface IAddin 
    ReadOnly Property Id() As Long 
    Property State() As Boolean 
    Function OnConnection(ByVal Environment As String) As Boolean 
    Sub OnDisconnection() 
End Interface

Regras para a criação de interfaces   

  • Os interfaces não podem incluir código, apenas assinaturas de propriedades e métodos. 
  • É possível definir as assinaturas de propriedades como "ReadOnly" e "WriteOnly", mas não é possível especificar valores específicos para os blocos "Set" ou "Get". 
  • Um interface não pode incluir variáveis, nem pode definir "Scope qualifiers", pois todos os membros são implicitamente públicos.
     
  • Um interface pode incluir eventos públicos, mas a sua utilização é rara e não é recomendada.
     
  • Por "default" o "Scope qualifier" de um interface é "Friend".  

Por convenção os nomes dos interfaces começam pelo caracter "I", não incluem o caracter "_" e utilizam a notação Pascal (ex: "IGetRange")   

Implementação

Uma classe implementa um interface através da utilização da expressão "Implements":

Class MyAddin 
    Implements IAddin 
    '… 
End Class

Ao implementar um interface, o "visual studio" cria automaticamente um template da implementação.

A sintaxe para implementação das propriedades e métodos individuais reutiliza a expressão "Implements" para especificar exactamente qual o membro que esta a ser implementado.

O código do template gerado automaticamente pelo "Visual Studio" utiliza o "Scope Qualifier" "Public" para todos os membros, para que estes membros possam ser invocados directamente pelos seus clientes, mas este comportamento pode ser alterado. Ex:

Class MyAddin 
    Implements IAddin 
    Protected Overridable Function OnConnect(ByVal environment As String) As Boolean Implements IAddin.OnConnection 
        '… 
    End Function 
End Class

A expressão "Implements" suporta múltiplos argumentos, pelo que é possível um único método implementar membros de vários interfaces: (Mesmo que os membros tenham nomes diferentes, embora com a mesma assinatura):

'Outro interface com apenas uma propriedade 
Interface IHostEnvironment 
    ReadOnly Property HashCode() As Long 
End Interface 

'Classe de implementação 
Class MyAddin 
    Implements IAddin, IHostEnvironment 
    Public ReadOnly Property Id() As Long Implements IAddin.Id, IHostEnvironment.HashCode 
        Get 
            '… 
        End Get 
    End Property 
End Class

Acesso

É possível aceder aos membros de um interface de duas formas:   

  • Directamente – Através de uma variável da classe. Neste caso o acesso ao membro é feito tal como se se tratasse de um membro regular   
  • Através de uma variável do interface. Neste caso o acesso ao membro é feito através da instanciação de uma variável do tipo do interface que a classe implementa.
'Uma instancia da classe 
Dim addin As New MyAddin 
'Cast para uma variável de interface 
Dim iadd As IAddin = addin 
'Agora a variavel de interface tem acesso a todos os membros do interface 
Iadd.state=True

Através de uma variável de interface é possível aceder a todos os membros independentemente do "Scope Qualifier" definido na classe que o implementa (Por exemplo, é possível invocar um método privado, desde que este pertença à implementação do interface).

Se o membro do interface for definido como "Public" na classe que o implementa, não há qualquer necessidade de utilizar uma variável do tipo do intreface.

Exemplo:

'Código do Interface 
Public Interface IDemo 
    Function GetTime() As String 
End Interface 

'Código da classe de implementação 
Public Class Class1 
    Implements IDemo 
    Private Function GetTime() As String Implements IDemo.GetTime 
        Return Now 
    End Function 
End Class 

'Código do Cliente 
Module Module1 
    Sub Main() 
        Dim MyClass1 As New Class1 
        'A linha seguinte não é aceite 
        Dim MyClass1.GetTime 
        Dim MyIdemo As IDemo 
        MyIdemo = CType(MyClass1, IDemo) 
        'Agora já é possivel aceder ao membro privado 
        MsgBox(MyIdemo.GetTime) 
        'Alternativa, sem ser necessário criar variável explicita: 
        'MsgBox(CType(MyClass1, IDemo).GetTime()) 
    End Sub 
End Module

Herança

Um interface pode herdar de outro interface. Um interface derivado contem todos os membros do interface base, bem como todos os membros por si definidos. Esta característica permite criar um novo interface que é uma extensão de outro.

Public Interface IAddin2 
Inherits IAddin 
    Property Description() As String 
End Interface

A Framework .net dispobiliza vários interfaces derivados. Por exemplo o "ICollection" deriva do "IEnumerable" e é base para os "IList" e "IDictionary".

Regras para a herança de Interfaces  

  • Um Interface derivado não pode redefinir os membros do interface base (as expressões "Overridable" e "Overrides" não são aceites na definição de interfaces.   
  • Se um interface derivado possuir um membro com o mesmo nome que um membro no interface base, o membro do interface derivado esconde ("Shadows") o membro do interface base. (Neste caso ocorre uma situação bastante confusa: A classe que implementa o interface derivado é obrigada a codificar ambos os membros com o mesmo nome, por isso esta situação deve sempre ser evitada).   
  • Uma classe derivada herda automaticamente todos os interfaces (e a sua implementação) tal como definidos na classe base. Esta classe derivada não pode conter a expressão "Implements".   
  • A classe derivada pode redefinir a implementação de um método definido num interface, desde que não tenha sido definido como privado, e tenha a expressão "Overridable"   
  • Class Class2 
        Inherits MyAddin 
        Protected Overrides Function OnConnection(ByVal environment As String) As Boolean 
            '… 
        End Function 
    End Class 

Interfaces da Framework

A Framework .Net define vários interfaces, que podem ser implementados nas classes criadas pelos programadores.

Icomparable

O Tipo "System.Array" expõe um método estático chamado "Sort", que permite ordenar um "array" de tipos simples, como "integers" ou "strings", mas não permite ordenar objectos mais complexos pois não esta definida a forma como é feita a comparação.

Demo:

Com a implementação do interface "IComparable", é possível criar uma classe que seja ordenável através deste método "Sort". Este interface expõe apenas um método chamado "CompareTo", que recebe um objecto e é esperado que retorne -1, 0 ou 1 dependendo de se o objecto em que o "CompareTo" foi chamado é menor, igual ou maior que o objecto passado no argumento.

Demo:

ICloneable

Como no .Net todos os tipos derivam do tipo "Object", quando uma variável (de um tipo referência) é definida como sendo igual a outra variável, obtém-se duas variáveis a apontar para o mesmo objecto, em vez de dois objectos independentes.

Tipicamente, é possível obter uma cópia do objecto invocando um método especial exposto pela classe, chamado "Clone".

Para que a classe exponha este método especial, tem de implementar o interface "ICloneable"e o seu único método "Clone".

Ao contrário do "IComparable", o interface "ICloneable" nunca é chamado pela framework do .Net. O seu único propósito é fornecer uma forma padrão de disponibilizar um clone de um objecto.

Demo:

IEnumerable

Para que um tipo suporte enumeração através de um ciclo "For … Each" tem de implementar um interface "IEnumerable".

Quando o compilador chega a um ciclo "For … Each" o código gerado pelo compilador invoca o único método deste interface "GetEnumerator". Este método deve retornar um objecto que suporte o interface "IEnumerator", que por sua vez expõe os três membros seguintes:  

  1. "MoveNext" – É um método, chamado a cada interacção e deve retornar "True" se existir um novo elemento disponivel e "False" se não existir nenhum novo elemento.
  2. "Current"- É uma propriedade "Read-only" que devolve o valor a ser utilizado na interacção actual do ciclo.
  3. "Reset" – É um método que faz "reset" ao ponteiro utilizado internamente para que o próximo valor a ser obtido seja o primeiro.

       Demo:

     

Caso Prático

Pretende-se desenvolver uma pequena aplicação que demonstre a utilização de interfaces da plataforma .Net bem como a implementação de novos interfaces.

Esta aplicação será uma pequena agenda de contactos, mantendo uma lista de pessoas bem como os seus respectivos contactos.

A lógica da aplicação é a seguinte:   

  • São criados dois interfaces, que serão implementados pelas classes da aplicação:
  • É implementado o mecanismo de introdução de dados e sua manipulação, utilizando interfaces da plataforma .Net
  • É feita a apresentação de resultados na consola. 

Regras para desenvolvimento   

  1. Criar um novo projecto do tipo "ConsoleApplication"
  2. Criar um novo interface chamado "IContact" que defina:
    1. Uma propriedade "Morada" do tipo "string"
    2. Uma propriedade "Telefone" do tipo "string"
    3. Uma propriedade "Email" do tipo "string"
    4. Uma propriedade "Titulo" do tipo String (esta propriedade irá conter o titulo do contacto, exemplo: "escritório", "casa", etc.)
  3. Criar um novo interface chamado "IPerson" que defina:
    1. Uma propriedade só de leitura "PrimeiroNome" do tipo "String"
    2. Uma propriedade só de leitura "UltimoNome" do tipo "String"
    3. Uma propriedade "Nacionalidade" do tipo "string"
    4. Uma propriedade "Vip" do tipo "Boolean"
    5. Uma propriedade "Enabled" do tipo "Boolean"
    6. Um método "AlterarNome" que aceite como argumentos as "strings": "PrimeiroNome" e "UltimoNome" e devolva uma "string"
  4. Criar uma nova classe chamada "Contact" com as seguintes características:
    1. Implementação do interface "IContact"
    2. Um construtor que atribua valores "default" às propriedades: "Morada", "Telefone", "Email", "Titulo"
  5. Criar uma nova classe chamada "PersonContacts" com as seguintes características:
    1. Implementação dos interfaces: "IPerson", "IComparable", "IEnumerable" e "ICloneable"
    2. Criar um campo privado do tipo "array" de objectos "Contact" que irá possuir todos os contactos de uma pessoa
    3. Criar um construtor que defina os valores iniciais para as propriedades "PrimeiroNome" e "ÚltimoNome"
    4. Criar um método "AdicionarContacto" que aceite como argumentos todos os campos definidos para o construtor da classe "Contact". Este método deve instanciar um novo contacto e coloca-lo no "array" de contactos.
    5. Codificar a implementação do interface "IPerson"
    6. Codificar a implementação do interface "IComparable"
      1. Implementar o método "CompareTo" de forma a executar comparações pelo "UltimoNome"
    7. Codificar a implementação do interface "ICloneable"
    8. Codificar a implementação do interface "IEnumerator"
      1. Esta classe deverá possuir a capacidade de expor uma colecção de contactos, disponível através de um ciclo "For Each". Como tal o método "GetEnumerator" deve devolver um tipo que implemente o interface "IEnumerator" (exemplo:PersonContactEnumerator) com as seguintes características:
        1. Receber no construtor o array com todos os contactos da classe
        2. No método "MoveNext" avançar uma posição no array
        3. O método "Current" devolve a objecto actual do array
  6. No modulo "Main" implementar a seguinte lógica:
    1. Criar um "array" de "PersonContacts" com 4 elementos
    2. Para cada casa do "array" criar um novo "PersonContact"
      1. Atribuir valores para as propriedades "Vip" e "Nacionalidade"
      2. Apenas na primeira pessoa, adicionar o valor "False" à propriedade "Enabled"
      3. Adicionar alguns contactos a cada pessoa
    3. Ordenar o "array" de pessoas
    4. Executar dois ciclos encadeados "For Each" para mostrar no ecrã todas as pessoas bem como os respectivos contactos
    5. Criar um novo objecto do tipo "PersonContacts" e obter um clone do original
    6. Mostrar na consola que o novo objecto é um clone (utilizando o operador "IS")

    Solução

      

      

powered by metaPost