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