3. Classes, estruturas, interfaces
3.1. O objeto no exemplo
3.1.1. Noções gerais
Abordamos agora, através de exemplos, a programação orientada a objetos. Um objeto é uma entidade que contém dados que definem o seu estado (denominados propriedades) e funções (denominadas métodos). Um objeto é criado de acordo com um modelo denominado classe:
Public Class c1
' atributos
Private p1 As type1
Private p2 As type2
....
' método
Public Sub m1(....)
...
End Sub
' método
Public Function m2(...)
....
End Function
End Class
A partir da classe anterior C1, é possível criar vários objetos O1, O2,… Todos terão as propriedades p1, p2, … e os métodos m3, m4, … Mas terão valores diferentes para as suas propriedades pi, tendo assim cada um um estado que lhes é próprio. Por analogia, a declaração
cria dois objetos (o termo é incorreto neste contexto) do tipo (classe) Integer. A sua única propriedade é o seu valor. Se O1 é um objeto do tipo C1, O1.p1 designa a propriedade p1 de O1 e O1.m1 o método m1 de O1. Consideremos um primeiro modelo de objeto: a classe personne.
3.1.2. Definição da classe «pessoa»
A definição da classe personne será a seguinte:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Temos aqui a definição de uma classe, ou seja, de um tipo de dados. Quando criarmos variáveis deste tipo, chamar-lhes-emos objetos ou instâncias de classes. Uma classe é, portanto, um modelo a partir do qual são construídos os objetos. Os membros ou campos de uma classe podem ser dados (atributos), métodos (funções) ou propriedades. As propriedades são métodos específicos que servem para conhecer ou definir o valor dos atributos do objeto. Estes campos podem ser acompanhados por uma das três palavras-chave seguintes:
Um campo privado (private) só é acessível através dos métodos internos da classe | |
Um campo público (public) é acessível por qualquer função, definida ou não no âmbito da classe | |
Um campo protegido (protected) só é acessível através dos métodos internos da classe ou de um objeto derivado (ver mais adiante o conceito de herança). |
Em geral, os dados de uma classe são declarados privados, enquanto os seus métodos e propriedades são declarados públicos. Isto significa que o utilizador de um objeto (o programador):
- não terá acesso direto aos dados privados do objeto
- poderá recorrer aos métodos públicos do objeto e, nomeadamente, àqueles que dão acesso aos seus dados privados.
A sintaxe de declaração de uma classe é a seguinte:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
A ordem de declaração dos atributos private, protected e public é arbitrária.
3.1.3. O método inicializa
Voltemos à nossa classe [personne], declarada da seguinte forma:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qual é a função do método initialise? Como nom, prenom e age são dados privados da classe personne, as instruções:
são ilegais. Temos de inicializar um objeto do tipo personne através de um método público. É essa a função do método initialise. Escreveremos:
A sintaxe p1.initialise é válida, uma vez que initialise é de acesso público.
3.1.4. O operador «new»
A sequência de instruções
está incorreta. A instrução
declara p1 como uma referência a um objeto do tipo personne. Esse objeto ainda não existe e, por isso, p1 não está inicializado. É como se escrevêssemos:
onde se indica explicitamente, com a palavra-chave nothing, que a variável p1 ainda não faz referência a nenhum objeto. Quando, em seguida, se escreve
estamos a chamar o método initialise do objeto referenciado por p1. No entanto, esse objeto ainda não existe e o compilador irá sinalizar o erro. Para que p1 faça referência a um objeto, é necessário escrever:
Isto tem como efeito a criação de um objeto do tipo personne ainda não inicializado: os atributos nom e prenom, que são referências a objetos do tipo String, terão o valor nothing, e age terá o valor 0. Existe, portanto, uma inicialização por predefinição. Agora que p1 faz referência a um objeto, a instrução de inicialização desse objeto
é válida.
3.1.5. A palavra-chave Me
Vejamos o código do método initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
A instrução Me.prenom=P significa que o atributo prenom do objeto atual (Me) recebe o valor P. A palavra-chave Me designa o objeto atual: aquele no qual se encontra o método executado. Como é que o sabemos? Vejamos como se realiza a inicialização do objeto referenciado por p1 no programa chamador:
É o método initialise do objeto p1 que é chamado. Quando, neste método, se faz referência ao objeto Me, está-se, na verdade, a fazer referência ao objeto p1. O método initialise também poderia ter sido escrito da seguinte forma:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Quando um método de um objeto faz referência a um atributo A desse objeto, a notação Me.A é implícita. Deve ser utilizada explicitamente quando existe um conflito de identificadores. É o caso da instrução:
Me.age=age;
onde age designa um atributo do objeto atual, bem como o parâmetro age recebido pelo método. É então necessário eliminar a ambiguidade, designando o atributo age como Me.age.
3.1.6. Um programa de teste
Eis um pequeno programa de teste:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
e os resultados obtidos:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Utilizar um ficheiro de classes compiladas (assembly)
Note-se que, no exemplo anterior, existem duas classes no nosso programa de teste: as classes personne e test1. Existe outra forma de proceder:
-
compila-se a classe «pessoa» num ficheiro específico denominado «assembly». Este ficheiro tem a extensão .dll
-
compila-se a classe «test1» fazendo referência ao assembly que contém a classe «pessoa».
Os dois ficheiros-fonte ficam assim:
test.vb | |
personne2.vb | |
A classe personne é compilada pela seguinte instrução:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
A compilação produziu um ficheiro personne2.dll. É a opção de compilação /t:library que indica a produção de um ficheiro «assembly». Agora, vamos compilar o ficheiro test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
A opção de compilação /r:personne2.dll indica ao compilador que encontrará determinadas classes no ficheiro personne2.dll. Quando, no ficheiro fonte test.vb, encontrar uma referência à classe personne — classe não declarada no ficheiro fonte test.vb —, irá procurar a classe personne nos ficheiros .dll referenciados pela opção /r. Aí encontrará a classe personne no conjunto personne2.dll. Poderiam ter sido incluídas outras classes neste assembly. Para utilizar vários ficheiros de classes compiladas durante a compilação, escrever-se-á:
A execução do programa test1.exe produz os seguintes resultados:
3.1.8. Outro método inicializa
Consideremos novamente a classe personne e adicionemos-lhe o seguinte método:
' método
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Temos agora dois métodos com o nome initialise: isto é válido desde que aceitem parâmetros diferentes. É o que acontece neste caso. O parâmetro é agora uma referência P a uma pessoa. Os atributos da pessoa P são então atribuídos ao objeto atual (Me). Note-se que o método initialise tem acesso direto aos atributos do objeto P, embora estes sejam do tipo private. Isto é sempre verdade: um objeto O1 de uma classe C tem sempre acesso aos atributos dos objetos da mesma classe C. Eis um teste da nova classe personne, tendo esta sido compilada em personne.dll, tal como explicado anteriormente:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e os seus resultados:
3.1.9. Construtores da classe «pessoa»
Um construtor é um procedimento com o nome New que é chamado aquando da criação do objeto. É geralmente utilizado para o inicializar. Se uma classe tiver um construtor que aceite n argumentos argi, a declaração e a inicialização de um objeto dessa classe poderão ser feitas da seguinte forma:
dim objet as classe =new classe(arg1,arg2, ... argn)
ou
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Quando uma classe tem um ou mais construtores, um desses construtores deve ser obrigatoriamente utilizado para criar um objeto dessa classe. Se uma classe C não tiver nenhum construtor, dispõe de um construtor por predefinição, que é o construtor sem parâmetros: public New(). Os atributos do objeto são então inicializados com valores por predefinição. Foi isso que aconteceu nos programas anteriores, onde escrevemos:
Vamos criar dois construtores para a nossa classe personne:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' a classe «pessoa»
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' métodos de inicialização do objeto
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Os nossos dois construtores limitam-se a chamar os métodos initialise correspondentes. Recorde-se que, quando num construtor se encontra a notação initialise(P), por exemplo, o compilador traduz para Me.initialise(P). No construtor, o método initialise é, portanto, chamado para trabalhar no objeto referenciado por Me, ou seja, o objeto atual, aquele que está a ser construído. Eis um pequeno programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e os resultados obtidos:
3.1.10. As referências dos objetos
Continuamos a utilizar a mesma classe personne. O programa de teste fica assim:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 faz referência ao mesmo objeto que p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 refere-se a um objeto que será uma cópia do objeto referido por p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' altera-se o estado do objeto referenciado por p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' como p2 = p1, o objeto referenciado por p2 deve ter mudado de estado
Console.Out.Write("p2=")
p2.identifie()
' como p3 não aponta para o mesmo objeto que p1, o objeto a que p3 aponta não deve ter mudado
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Os resultados obtidos são os seguintes:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Quando se declara a variável p1 através de
p1 faz referência ao objeto personne("Jean","Dupont",30), mas não é o próprio objeto. Em C, dir-se-ia que se trata de um ponteiro, c.a.d, para a morada do objeto criado. Se escrevermos em seguida:
Não é o objeto personne("Jean","Dupont",30) que é alterado, mas sim a referência p1 que muda de valor. O objeto personne("Jean","Dupont",30) será «perdido» se não for referenciado por nenhuma outra variável.
Quando se escreve:
inicializa-se o ponteiro p2: este «aponta» para o mesmo objeto (designa o mesmo objeto) que o ponteiro p1. Assim, se se alterar o objeto «apontado» (ou referenciado) por p1, altera-se também o objeto referenciado por p2.
Quando se escreve:
é criado um novo objeto, uma cópia do objeto referenciado por p1. Este novo objeto será referenciado por p3. Se se alterar o objeto «apontado» (ou referenciado) por p1, não se altera de forma alguma aquele referenciado por p3. É isso que mostram os resultados obtidos.
3.1.11. Os objetos temporários
Numa expressão, é possível invocar explicitamente o construtor de um objeto: este é criado, mas não temos acesso a ele (para o modificar, por exemplo). Este objeto temporário é criado para efeitos de avaliação da expressão e, em seguida, descartado. O espaço de memória que ocupava será automaticamente recuperado posteriormente por um programa denominado «garbage collector», cuja função é recuperar o espaço de memória ocupado por objetos que já não são referenciados pelos dados do programa. Consideremos o seguinte novo programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
e alteremos os construtores da classe personne para que exibam uma mensagem:
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Obtenemos os seguintes resultados:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
mostrando a construção sucessiva dos dois objetos temporários.
3.1.12. Métodos de leitura e gravação dos atributos privados
Adicionamos à classe personne os métodos necessários para ler ou alterar o estado dos atributos dos objetos:
Imports System
Public Class personne
' atributos
Private prenom As [String]
Private nom As [String]
Private age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identificadores
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' acessores
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modificadores
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Testamos a nova classe com o seguinte programa:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
e obtemos os seguintes resultados:
3.1.13. As propriedades
Existe outra forma de aceder aos atributos de uma classe: criar propriedades. Estas permitem-nos manipular atributos privados como se fossem públicos. Consideremos a seguinte classe personne, na qual os acessores e modificadores anteriores foram substituídos por propriedades de leitura e escrita:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' classe pessoa
Public Class personne
' atributos
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' fabricantes
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriedades
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Uma propriedade permite ler (get) ou definir (set) o valor de um atributo. No nosso exemplo, colocámos o prefixo _ nos nomes dos atributos para que as propriedades tenham o mesmo nome que os atributos primitivos. Com efeito, uma propriedade não pode ter o mesmo nome que o atributo que gere, pois, nesse caso, haveria um conflito de nomes na classe. Por isso, denominámos os nossos atributos _prenom, _nom, _age e alterámos os construtores e métodos em conformidade. Em seguida, criámos três propriedades: nom, prenom e age. Uma propriedade é declarada da seguinte forma:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
onde Type deve ser o tipo do atributo gerido pela propriedade. Esta pode ter dois métodos denominados get e set. O método get é normalmente responsável por devolver o valor do atributo que gere (pode devolver outra coisa, nada o impede). O método set recebe um parâmetro chamado value, que normalmente atribui ao atributo que gere. Pode aproveitar para verificar a validade do valor recebido e, eventualmente, lançar uma exceção se o valor se revelar inválido. É isso que se faz aqui para a idade.
Como é que estes métodos get e set são chamados? Consideremos o seguinte programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
Na instrução
procura-se obter os valores das propriedades prenom, nom e age da pessoa P. É o método get destas propriedades que é então chamado e que devolve o valor do atributo que elas gerem.
Na instrução
pretende-se definir o valor da propriedade age. É então o método set desta propriedade que é chamado. Este receberá 56 no seu parâmetro value.
Uma propriedade P de uma classe C que defina apenas o método get é designada como de leitura única. Se c for um objeto da classe C, a operação c.P=valor será rejeitada pelo compilador.
A execução do programa de teste anterior produz os seguintes resultados:
As propriedades permitem-nos, portanto, manipular atributos privados como se fossem públicos.
3.1.14. Os métodos e atributos de classe
Suponhamos que queremos contar o número de objetos [personne] criados numa aplicação. Podemos gerir nós próprios um contador, mas corremos o risco de nos esquecermos dos objetos temporários que são criados aqui e ali. Pareceria mais seguro incluir nos construtores da classe [personne] uma instrução que incremente um contador. O problema reside em passar uma referência a esse contador para que o construtor o possa incrementar: é necessário passar-lhes um novo parâmetro. Também é possível incluir o contador na definição da classe. Como se trata de um atributo da própria classe e não de um objeto específico dessa classe, declara-se de forma diferente com a palavra-chave Shared:
Para o referenciar, escreve-se personne._nbPersonnes para indicar que se trata de um atributo da própria classe personne. Aqui, criámos um atributo privado ao qual não se terá acesso direto fora da classe. Criamos, portanto, uma propriedade pública para dar acesso ao atributo da classe nbPersonnes. Para definir o valor de nbPersonnes, o método get desta propriedade não necessita de um objeto personne específico: na verdade, _nbPersonnes não é o atributo de um objeto específico, mas sim o atributo de toda uma classe. Por isso, é necessária uma propriedade também declarada como Shared:
que, externamente, será chamada com a sintaxe personne.nbPersonnes. A propriedade é declarada como de leitura única (ReadOnly), uma vez que não disponibiliza um método set. Eis um exemplo. A classe personne passa a ter a seguinte forma:
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' classe
Public Class personne
' atributos de classe
Private Shared _nbPersonnes As Long = 0
' atributos de instância
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' mais uma pessoa
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' mais uma pessoa
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriedade de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriedades de instância
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Com o seguinte programa:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
obtêm-se os seguintes resultados:
3.1.15. Passagem de um objeto para uma função
Já referimos que, por predefinição, o VB.NET passa os parâmetros efetivos de uma função por valor: os valores dos parâmetros efetivos são copiados para os parâmetros formais. No caso de um objeto, não se deve deixar enganar pelo uso incorreto da linguagem que ocorre sistematicamente ao falar de «objeto» em vez de «referência de objeto». Um objeto só é manipulado através de uma referência (um ponteiro) a ele. O que é, portanto, transmitido a uma função não é o próprio objeto, mas uma referência a esse objeto. É, portanto, o valor da referência — e não o valor do próprio objeto — que é duplicado no parâmetro formal: não há criação de um novo objeto. Se uma referência de objeto R1 for passada para uma função, será copiada para o parâmetro formal correspondente R2. Assim, as referências R2 e R1 designam o mesmo objeto. Se a função alterar o objeto a que aponta R2, alterará, evidentemente, aquele a que aponta R1, uma vez que se trata do mesmo.
![]() |
É isso que ilustra o exemplo seguinte:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma pessoa p1
Dim p1 As New personne("Jean", "Dupont", 30)
' visualização p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' alteração p1
modifie(p1)
' visualização p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' visualização de pessoa P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' alteração P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' visualização de P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Os resultados obtidos são os seguintes:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Verifica-se que apenas foi criado um objeto: o da pessoa p1 do procedimento Main e que o objeto foi efetivamente alterado pela função modifie.
3.1.16. Uma tabela de pessoas
Um objeto é um dado como qualquer outro e, como tal, vários objetos podem ser agrupados numa tabela:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma tabela de pessoas
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' visualização
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
A instrução Dim amigos(2) As pessoa cria uma matriz de 3 elementos do tipo personne. Estes 3 elementos são aqui inicializados com os valores nothing e c.a.d, pelo que não referenciam nenhum objeto. Mais uma vez, por conveniência, fala-se de um tabuleiro de objetos, quando na verdade trata-se apenas de um tabuleiro de referências a objetos. A criação do tabuleiro de objetos, que é ele próprio um objeto, não cria nenhum objeto do tipo dos seus elementos: isso tem de ser feito posteriormente. Obtêm-se os seguintes resultados:
3.2. A herança por exemplo
3.2.1. Noções gerais
Abordamos aqui o conceito de herança. O objetivo da herança é «personalizar» uma classe existente para que esta satisfaça as nossas necessidades. Suponhamos que queremos criar uma classe enseignant: um professor é uma pessoa específica. Tem atributos que outra pessoa não terá: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome próprio, apelido e idade. Um professor faz, portanto, parte integrante da classe personne, mas possui atributos adicionais. Em vez de criar uma classe enseignant a partir do zero, preferimos aproveitar o que já existe na classe personne e adaptá-la às características específicas dos professores. É o conceito de herança que nos permite fazer isso. Para indicar que a classe enseignant herda as propriedades da classe personne, escrever-se-á:
Public Class enseignant
Inherits personne
Note-se a sintaxe específica em duas linhas. A classe personne é designada por classe pai (ou mãe) e a classe enseignant por classe derivada (ou filha). Um objeto enseignant possui todas as características de um objeto personne: tem os mesmos atributos e os mesmos métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha: basta indicar os atributos e métodos adicionados pela classe filha. Suponhamos que a classe personne esteja definida da seguinte forma:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' classe «pessoa»
Public Class personne
' atributos da classe
Private Shared _nbPersonnes As Long = 0
' atributos de instância
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' construtores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' mais uma pessoa
_nbPersonnes += 1
' construção
Me._prenom = P
Me._nom = N
Me._age = age
' acompanhamento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' mais uma pessoa
_nbPersonnes += 1
' construção
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' acompanhamento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' propriedade de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriedades de instância
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' idade válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
O método identifie foi substituído pela propriedade identité, de leitura única, que identifica a pessoa. Criamos uma classe enseignant que herda da classe personne:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriedade da secção
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
A classe enseignant adiciona aos métodos e atributos da classe personne:
- um atributo section, que corresponde ao número da secção a que o professor pertence no corpo docente (basicamente, uma secção por disciplina)
- um novo construtor que permite inicializar todos os atributos de um professor
A declaração
Public Class enseignant
Inherits personne
indica que a classe enseignant deriva da classe personne.
3.2.2. Criação de um objeto «professor»
O construtor da classe enseignant é o seguinte:
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
A declaração
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
indica que o construtor recebe quatro parâmetros: P, N, age e section. Tem de passar três deles, (P,N,age), para a sua classe base, neste caso a classe personne. Sabe-se que esta classe tem um construtor pessoa(string, string, int) que permitirá criar uma pessoa com os parâmetros passados (P,N,age). A classe [enseignant] passa os parâmetros (P, N, idade) à sua classe base da seguinte forma:
MyBase.New(P, N, age)
Assim que a construção da classe base estiver concluída, a construção do objeto enseignant prossegue com a execução do corpo do construtor:
Me._section = section
Em resumo, o construtor de uma classe derivada:
- passa à sua classe base os parâmetros de que esta necessita para ser construída
- utiliza os restantes parâmetros para inicializar os seus próprios atributos
Poderíamos ter preferido escrever:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Isso é impossível. A classe personne declarou como privados (private) os seus três campos _prenom, _nom e _age. Apenas os objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos derivados como neste caso, têm de recorrer a métodos públicos para aceder a esses campos. A situação teria sido diferente se a classe personne tivesse declarado os três campos como protegidos (protected): nesse caso, permitiria que as classes derivadas tivessem acesso direto aos três campos. No nosso exemplo, utilizar o construtor da classe pai era, portanto, a solução correta e é o método habitual: ao construir um objeto filho, chama-se primeiro o construtor do objeto pai e, em seguida, completam-se as inicializações específicas do objeto filho (section no nosso exemplo).
Vamos compilar as classes personne e enseignant em assemblies:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Note-se que, para compilar a classe filha enseignant, foi necessário referenciar o ficheiro personne.dll, que contém a classe personne. Vamos tentar um primeiro programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Este programa limita-se a criar um objeto enseignant (new) e a identificá-lo. A classe enseignant não possui o método identité, mas a sua classe pai possui um, que, além disso, é público: por herança, este torna-se um método público da classe enseignant. Os resultados obtidos são os seguintes:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Verifica-se que:
- um objeto personne foi criado antes do objeto enseignant
- a identidade obtida é a do objeto personne
3.2.3. Sobrecarga de um método ou de uma propriedade
No exemplo anterior, obtivemos a identidade da parte personne do professor, mas faltam algumas informações específicas da classe enseignant (a secção). Por isso, temos de definir uma propriedade que permita identificar o professor:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' construtor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' acompanhamento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriedade de secção
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' sobrecarregamento da propriedade de identidade
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
O método identite da classe enseignant baseia-se no método identite da sua classe-mãe (MyBase.identite) para apresentar a sua parte «personne» e, em seguida, completa com o campo _section, que é específico da classe enseignant. Note-se a declaração da propriedade identite:
Public Shadows ReadOnly Property identite() As String
que indica que a propriedade «identite» «oculta» o método com o mesmo nome que poderia existir na classe pai. Consideremos um objeto enseignant, denotado por E. Este objeto contém, no seu interior, um objeto personne:
![]() |
A propriedade «identity» está definida tanto na classe enseignant como na sua classe pai personne. Na classe filha enseignant, a propriedade identite deve ser precedida pela palavra-chave shadows para indicar que se está a redefinir uma nova propriedade identite para a classe enseignant.
Public Shadows ReadOnly Property identite() As String
A classe enseignant dispõe agora de duas propriedades identite:
- a herdada da classe pai personne
- a sua própria
Se E for um objeto enseignant, E.identite designa o método identite da classe enseignant. Diz-se que a propriedade identite da classe-mãe é «redefinida» pela propriedade identite da classe-filha. De um modo geral, se O for um objeto e M um método, para executar o método O.M, o sistema procura um método M na seguinte ordem:
- na classe do objeto O
- na sua classe-mãe, se tiver uma
- na classe-mãe da sua classe-mãe, se esta existir
- etc…
A herança permite, portanto, redefinir na classe filha métodos/propriedades com o mesmo nome que existem na classe pai. É isso que permite adaptar a classe filha às suas próprias necessidades. Associada ao polimorfismo, que veremos um pouco mais adiante, a sobrecarga de métodos/propriedades é o principal interesse da herança. Consideremos o mesmo exemplo anterior:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Os resultados obtidos, desta vez, são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. O polimorfismo
Consideremos uma hierarquia de classes: C0 C1 C2 … Cn, em que Ci Cj, indica que a classe Cj é derivada da classe Ci. Isto implica que a classe Cj possui todas as características da classe Ci, além de outras. Sejam Oi objetos do tipo Ci. É válido escrever:
Com efeito, por herança, a classe Cj possui todas as características da classe Ci, além de outras. Assim, um objeto Oj do tipo Cj contém, no seu interior, um objeto do tipo Ci. A operação
faz com que Oi seja uma referência ao objeto de tipo Ci contido no objeto Oj.
O facto de uma variável Oi da classe Ci poder, na verdade, referenciar não só um objeto da classe Ci, mas também qualquer objeto derivado da classe Ci, denomina-se polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos. Vejamos um exemplo e consideremos a seguinte função, independente de qualquer classe:
Sub affiche(ByVal p As personne)
Também poderíamos escrever
ou
Neste último caso, o parâmetro formal do tipo personne da função affiche receberá um valor do tipo enseignant. Como o tipo enseignant deriva do tipo personne, isso é válido.
3.2.5. Redefinição e polimorfismo
Vamos completar a nossa função affiche:
Sub affiche(ByVal p As personne)
' exibe identidade de p
Console.Out.WriteLine(p.identite)
End Sub
O método p.identite devolve uma cadeia de caracteres que identifica o objeto personne.. O que acontece, no caso do nosso exemplo anterior, com um objeto enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Vejamos o exemplo seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' exibe
Sub affiche(ByVal p As personne)
' afixa a identidade de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Os resultados obtidos são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
A execução mostra que a instrução p.identite executou, em cada ocasião, a propriedade identite de uma personne, a pessoa contida na enseignant e, em seguida, a própria personne. Não se adaptou ao objeto efetivamente passado como parâmetro à affiche. Teríamos preferido ter a identidade completa do enseignant e. Para tal, teria sido necessário que a notação p.identite fizesse referência à propriedade identite do objeto efetivamente apontado por p, em vez da propriedade identite da parte «personne» do objeto efetivamente apontado por p. É possível obter este resultado declarando identite como uma propriedade redefiniável (overridable) na classe base personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
A palavra-chave «overridable» torna identite uma propriedade redefiniável ou virtual. Esta palavra-chave também se pode aplicar a métodos. As classes derivadas que redefinirem uma propriedade ou método virtual devem, então, utilizar a palavra-chave «overrides» em vez de «shadows» para qualificar a sua propriedade/método redefinido. Assim, na classe enseignant, a propriedade identite é definida da seguinte forma:
' sobreposição da propriedade de identidade
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
O programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' exibe
Sub affiche(ByVal p As personne)
' cartaz de identificação de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
produz então os seguintes resultados:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Desta vez, conseguimos obter a identidade completa do professor. Vamos agora redefinir um método em vez de uma propriedade. A classe object é a classe «pai» de todas as classes VB.NET. Assim, quando escrevemos:
estamos, implicitamente, a escrever:
A classe object define um método virtual ToString:

O método ToString devolve o nome da classe à qual o objeto pertence, conforme ilustrado no exemplo seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test2
Sub Main()
' um professor
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' uma pessoa
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Os resultados obtidos são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Note-se que, embora não tenhamos redefinido o método ToString nas classes personne e enseignant, é possível constatar que o método ToString da classe object consegue, mesmo assim, apresentar o nome real da classe do objeto. Vamos redefinir o método ToString nas classes personne e enseignant:
' ToString
Public Overrides Function ToString() As String
' atribui-se a propriedade «identidade»
Return identite
End Function
A definição é a mesma nas duas classes. Consideremos o seguinte programa de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' um professor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' uma pessoa
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' cartaz
Sub affiche(ByVal p As personne)
' cartaz de identificação de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Os resultados da execução são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Definir um indexador para uma classe
Consideremos a classe [ArrayList] predefinida na plataforma .NET. Esta classe permite armazenar objetos numa lista. Pertence ao espaço de nomes [System.Collections]. No exemplo seguinte, utilizamos esta classe para armazenar uma lista de pessoas (em sentido lato):
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para adicionar uma pessoa à lista
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' um indexador
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' outro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' procura-se a pessoa com o nome N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Vamos apresentar algumas informações sobre determinadas propriedades e métodos da classe [ArrayList]:
atributo que indica o número de elementos da lista | |
método que permite adicionar um objeto à lista | |
método que devolve o elemento i da lista |
Reparamos que, para obter o elemento n.º i da lista, não escrevemos [liste.Item(i)], mas sim diretamente [liste(i)], o que, à primeira vista, parece estar errado. No entanto, isto é possível porque a classe [ArrayList] define uma propriedade por predefinição [Item], de acordo com uma sintaxe semelhante à seguinte:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Quando o compilador encontra a notação [liste(i)], verifica se a classe [ArrayList] definiu uma propriedade com a seguinte assinatura:
Default Public Property Proc(ByVal var As Integer) As Type
Aqui, encontrará o procedimento [Item]. Traduzirá então a notação [liste(i)] para [liste.Item(i)]. A propriedade [Item] é designada como a propriedade indexada por predefinição da classe [ArrayList]. A execução do programa anterior produz os seguintes resultados:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Vamos criar uma classe chamada [listeDePersonnes], que será uma lista de pessoas, ou seja, uma lista específica que parece natural derivar da classe [ArrayList]:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para adicionar uma pessoa à lista
Public Shadows Sub Add(ByVal p As Object)
' é necessário que p seja uma pessoa
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' um indexador
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
A classe apresenta os seguintes métodos e propriedades:
retorna uma cadeia de caracteres que «representa» o conteúdo da lista | |
método que permite adicionar uma pessoa à lista | |
propriedade indexada por predefinição que indica a pessoa i da lista |
Consideremos as novidades:
Foi criado um novo método [Add].
' para adicionar uma pessoa à lista
Public Shadows Sub Add(ByVal p As Object)
' é necessário que p seja uma pessoa
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
Já existe um na classe pai [ArrayList] com a mesma assinatura, daí a palavra-chave [Shadows] para indicar que o novo procedimento substitui o da classe pai. O método [Add] da classe filha verifica se o objeto adicionado é efetivamente do tipo [personne] ou derivado da função [TypeOf]. Se não for esse o caso, é lançada uma exceção com a instrução [Throw]. Se o objeto adicionado for efetivamente do tipo [personne], o método [Add] da classe base é utilizado para efetuar a adição.
É criada uma propriedade indexada:
' um indexador
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
Já existe uma propriedade por predefinição denominada [Item] na classe base com a mesma assinatura. Por isso, é necessário utilizar a palavra-chave [Shadows] para indicar que a nova propriedade indexada [Index] irá «ocultar» a da classe base [Item]. Note-se que isto é válido mesmo que as duas propriedades não tenham o mesmo nome. A propriedade [Index] permite referenciar a pessoa n.º i da lista. Baseia-se na propriedade [Item] da classe base para aceder ao elemento n.º i do objeto subjacente [ArrayList]. São efetuadas alterações de tipo para ter em conta que a propriedade [Item] trabalha com elementos do tipo [Object], enquanto a propriedade [Index] trabalha com elementos do tipo [personne].
Por fim, redefinimos (Overrides) o método [ToString] da classe [ArrayList]:
' toString
Public Overrides Function ToString() As String
' retorna (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' percorre-se a matriz dinâmica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Este método devolve uma cadeia de caracteres com o formato «(e1,e2,...,en)», em que ei são os elementos da lista. Note-se a notação [Me(i)], que designa o elemento n.º i do objeto atual [Me]. É utilizada a propriedade indexada por predefinição. Assim, [Me(i)] é equivalente a [Me.Index(i)].
O código da classe é colocado no ficheiro [lstpersonnes2.vb] e compilado:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
É criado um programa de teste:
' opções
Option Explicit On
Option Strict On
' espaços de nomes
Imports System
Imports System.Collections
Module test
Sub Main()
' criação de uma lista vazia de pessoas
Dim liste As listeDePersonnes = New listeDePersonnes
' criação de pessoas
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' preenchimento da lista
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' visualização da lista
Console.Out.WriteLine(liste.ToString)
' adição de um objeto diferente de pessoa
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
É compilado:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
e, em seguida, executado:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Poderíamos querer escrever
onde l seria do tipo [listeDePersonnes]. Neste caso, pretende-se indexar a lista l não por um número de elemento, mas por um nome de pessoa. Para tal, define-se uma nova propriedade indexada por predefinição:
' outro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' procura-se a pessoa com o nome N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
A primeira linha
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indica que estamos a criar novamente uma propriedade indexada por predefinição. Todas as propriedades por predefinição devem ter o mesmo nome, neste caso [Index]. A nova propriedade [Index] indexa a classe listeDePersonnes por uma cadeia de caracteres N. O resultado de listeDePersonnes(N) é um número inteiro. Este número inteiro corresponderá à posição na lista da pessoa com o nome N ou a -1, caso essa pessoa não conste da lista. Define-se apenas a propriedade get, impedindo assim a escrita de listeDePersonnes ("nom")=valeur, que teria exigido a definição da propriedade set). Daí a palavra-chave [ReadOnly]. A palavra-chave [Shadows] é necessária para ocultar a propriedade por predefinição da classe base (embora esta não tenha a mesma assinatura).
No corpo do get, percorre-se a lista de pessoas à procura do nome N passado como parâmetro. Se for encontrado na posição i, devolve-se i; caso contrário, devolve-se -1.
Um novo programa de teste poderia ser o seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' página de teste
Module test
Sub Main()
' uma lista de pessoas
Dim l As New listeDePersonnes
' adição de pessoas
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' visualização
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' alteração do elemento 1
l(1) = New personne("sylvie", "cachan", 5)
' visualização do elemento 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' visualização da lista l
Console.Out.WriteLine(("l=" + l.ToString))
' pesquisa de pessoas
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
A execução produz os seguintes resultados:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. As estruturas
A estrutura VB.NET deriva diretamente da estrutura da linguagem C e é muito semelhante a uma classe. Uma estrutura é definida da seguinte forma:
Structure spersonne
' atributos
...
' propriedades
...
' construtores
...
' métodos
End Structure
Apesar da semelhança na sintaxe, existem diferenças importantes entre classes e estruturas. O conceito de herança, por exemplo, não existe nas estruturas. Se criarmos uma classe que não deva ser derivada, quais são as diferenças entre estrutura e classe que nos ajudarão a escolher entre as duas? Vamos recorrer ao exemplo seguinte para descobrir:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' classe pessoa
Class cpersonne
Public nom As String
Public age As Integer
End Class
' um módulo de teste
Public Module test
Sub Main()
' uma pessoa p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' uma pessoa p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 é alterado
sp2.nom = "nicole"
sp2.age = 30
' verificação de sp1 e sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' uma pessoa cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' uma pessoa cp2 P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 foi alterado
cp2.nom = "nicole"
cp2.age = 30
' verificação de P1 e P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Se executarmos este programa, obtemos os seguintes resultados:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Enquanto nas páginas anteriores deste capítulo se utilizava uma classe personne, agora utilizamos uma estrutura spersonne:
' estrutura da pessoa
Structure spersonne
Public nom As String
Public age As Integer
End Structure
A declaração
Dim sp1 As spersonne
cria uma estrutura (nome, idade) e o valor de sp1 é essa própria estrutura.
A declaração
Dim cp1 As New cpersonne
cria um objeto [cpersonne] (grosso modo, o equivalente à nossa estrutura) e cp1 é, então, a morada (a referência) desse objeto.
Resumindo
- no caso da estrutura, o valor de sp1 é a própria estrutura
- no caso da classe, o valor de p1 é o endereço do objeto criado
![]() |
![]() |
Quando, no programa, se escreve
Dim sp2 As spersonne = sp1
é criada uma nova estrutura (nome, idade) e inicializada com o valor de p1, ou seja, a própria estrutura.
![]() |
A estrutura de sp1 é, portanto, duplicada em sp2. Trata-se de uma cópia de valores.
A instrução
Dim cp2 As cpersonne = cp1
funciona de forma diferente. O valor de cp1 é copiado para cp2, mas como esse valor é, na verdade, o endereço do objeto, este não é duplicado. Tem simplesmente duas referências a ele:
![]() |
No caso da estrutura, se alterarmos o valor de sp2, não alteramos o valor de sp1, como demonstra o programa. No caso do objeto, se alterarmos o objeto apontado por cp2, aquele apontado por cp1 é alterado, uma vez que se trata do mesmo. É isso que os resultados do programa também demonstram.
Conclui-se, portanto, destas explicações que:
- o valor de uma variável do tipo estrutura é a própria estrutura
- o valor de uma variável do tipo objeto é o endereço do objeto apontado
Uma vez compreendida esta diferença fundamental, a estrutura revela-se muito semelhante à classe, como demonstra o seguinte exemplo:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura de pessoa
Structure personne
' atributos
Private _nom As String
Private _age As Integer
' propriedades
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Construtor
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' um módulo de teste
Module test
Sub Main()
' uma pessoa p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' uma pessoa p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 é alterado
p2.nom = "nicole"
p2.age = 30
' verificação de p1 e p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Obtêm-se os seguintes resultados de execução:
A única diferença notável aqui entre a estrutura e a classe é que, com uma classe, os objetos p1 e p2 teriam o mesmo valor no final do programa, ou seja, o valor de p2.
3.5. As interfaces
Uma interface é um conjunto de protótipos de métodos ou propriedades que constitui um contrato. Uma classe que decide implementar uma interface compromete-se a fornecer uma implementação de todos os métodos definidos na interface. É o compilador que verifica essa implementação. Eis, por exemplo, a definição da interface Istats:
Qualquer classe que implemente esta interface será declarada como
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
Os métodos [moyenne] e [écartType] devem ser definidos na classe C. Consideremos o código seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' estrutura
Public Structure élève
Public _nom As String
Public _note As Double
' construtor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' classe notas
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
' fabricante
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' registo de alunos e disciplinas
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' concatenamos todas as notas
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'última nota
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' fim
Return valeur
End Function
End Class
A turma notes reúne as notas de uma turma numa disciplina:
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
Os atributos são declarados como protected para que sejam acessíveis a partir de uma classe derivada. O tipo élève é uma estrutura que armazena o nome do aluno e a sua nota na disciplina:
Public Structure élève
Public _nom As String
Public _note As Double
' construtor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Decidimos derivar esta classe notes numa classe notesStats que teria dois atributos adicionais: a média e o desvio-padrão das notas:
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
A classe notesStats implementa a seguinte interface Istats:
Isto significa que a classe notesStats deve ter dois métodos denominados moyenne e écartType com a assinatura indicada na interface Istats. A classe notesStats é a seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
' construtor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' cálculo da média das notas
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' desvio-padrão
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' métodos da interface Istats
Public Function moyenne() As Double Implements Istats.moyenne
' calcula a média das notas
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' calcula o desvio-padrão
Return _écartType
End Function
End Class
A média _moyenne e o desvio-padrão _ecartType são calculados logo na criação do objeto. Assim, os métodos moyenne e écartType apenas têm de devolver o valor dos atributos _moyenne e _ecartType. Ambos os métodos devolvem -1 se a tabela de alunos estiver vazia.
A seguinte classe de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Module test
Sub Main()
' alunos e notas
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' que se guardam num objeto «notas»
Dim anglais As New notes("anglais", ELEVES)
' e que são apresentadas
Console.Out.WriteLine((anglais.ToString))
' o mesmo com média e desvio-padrão
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
produz os seguintes resultados:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
A classe notesStats poderia muito bem ter implementado os métodos moyenne e écartType por si própria, sem indicar que implementava a interface Istats. Qual é a utilidade das interfaces? É a seguinte: uma função pode aceitar como parâmetro formal um dado com o tipo de uma interface I. Qualquer objeto de uma classe C que implemente a interface I poderá, então, ser um parâmetro efetivo dessa função. Consideremos o seguinte exemplo:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' uma interface Iexample
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' uma 1.ª classe
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'uma segunda classe
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
A interface Iexemple define dois métodos: ajouter e soustraire. As classes classe1 e classe2 implementam esta interface. Note-se que estas classes não fazem mais nada, para simplificar o exemplo. Consideremos agora o seguinte exemplo:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' classe de teste
Module test
'calcular
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' a função Main
Sub Main()
' criação de dois objetos «classe1» e «classe2»
Dim c1 As New classe1
Dim c2 As New classe2
' chamadas à função estática «calcular»
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
A função calculer aceita como parâmetro um elemento do tipo Iexemple. Assim, poderá receber para este parâmetro tanto um objeto do tipo classe1 como do tipo classe2. É isso que se faz no procedimento Main, com os seguintes resultados:
Vemos, portanto, que se trata de uma propriedade semelhante ao polimorfismo observado nas classes. Se um conjunto de classes Ci não ligadas entre si por herança (pelo que não é possível utilizar o polimorfismo de herança) apresentar um conjunto de métodos com a mesma assinatura, pode ser interessante agrupar esses métodos numa interface I, da qual todas as classes em questão herdariam. As instâncias dessas classes Ci podem então ser utilizadas como parâmetros de funções que aceitem um parâmetro do tipo I, c.a.d. Funções que utilizam apenas os métodos dos objetos Ci definidos na interface I e não os atributos e métodos específicos das diferentes classes Ci. Por fim, note-se que a herança de interfaces pode ser múltipla, c.a.d. podendo escrever-se
Public Class classe
Implements I1,I2,...
onde os Ij são interfaces.
3.6. Os espaços de nomes
Para escrever uma linha no ecrã, utilizamos a instrução
Se analisarmos a definição da classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
descobrimos que faz parte do espaço de nomes System. Isto significa que a classe Console deveria ser designada por System.Console e que, na verdade, deveríamos escrever:
Isto evita-se utilizando uma cláusula imports:
Diz-se que se importa o espaço de nomes System com a cláusula imports. Quando o compilador encontrar o nome de uma classe (neste caso, Console), irá procurá-la nos diferentes espaços de nomes importados pelas cláusulas imports. Aqui, encontrará a classe Console no espaço de nomes System. Observemos agora a segunda informação associada à classe Console:
Assembly: Mscorlib (in Mscorlib.dll)
Esta linha indica em que «assembly» se encontra a definição da classe Console. Quando se compila fora do Visual Studio.NET e é necessário indicar as referências das diferentes dll que contêm as classes a utilizar, esta informação pode revelar-se útil. Recorde-se que, para referenciar os dll necessários à compilação de uma classe, escreve-se:
Ao criar uma classe, é possível criá-la dentro de um espaço de nomes. O objetivo destes espaços de nomes é evitar conflitos de nomes entre classes quando estas são comercializadas, por exemplo. Consideremos duas empresas, E1 e E2, que distribuem classes empacotadas, respetivamente, nos dll, E1.dll e E2.dll. Suponhamos que um cliente C adquira estes dois conjuntos de classes, nos quais ambas as empresas definiram uma classe personne. O cliente C compila um programa da seguinte forma:
Se o código-fonte prog.vb utilizar a classe personne, o compilador não saberá se deve utilizar a classe personne da E1.dll ou a da E2.dll. O compilador irá sinalizar um erro. Se a empresa E1 tiver o cuidado de criar as suas classes num espaço de nomes denominado E1 e a empresa E2 num espaço de nomes denominado E2, as duas classes personne passarão a chamar-se E1.personne e E2.personne. O cliente deverá utilizar nas suas classes ou E1.personne, ou E2.personne, mas não personne. O espaço de nomes permite eliminar a ambiguidade. Para criar uma classe num espaço de nomes, escreve-se:
Namespace istia.st
Public Class personne
' definição da classe
...
end Class
end Namespace
A título de exemplo, vamos criar num espaço de nomes a nossa classe personne, analisada anteriormente. Escolheremos istia.st como espaço de nomes. A classe personne passa a ser:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
' criação do espaço de nomes istia.st
Namespace istia.st
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Esta classe é compilada em personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Agora, vamos utilizar a classe personne numa classe de teste:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports istia.st
' página de teste
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Para evitar escrever
Dim p1 As New istia.st.personne
importámos o espaço de nomes istia.st com uma cláusula imports:
Imports istia.st
Agora, vamos compilar o programa de teste:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
Isto gera um ficheiro test.exe que, quando executado, apresenta os seguintes resultados:
3.7. O exemplo IMPOTS
Retomamos o cálculo do imposto já analisado no capítulo anterior e processamo-lo utilizando uma classe. Recorde-se o problema:
Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:
- calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
- se tiver pelo menos três filhos, tem mais meia quota
- calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- calcula-se o seu coeficiente familiar QF = R / nbParts
- calcula-se o seu imposto I. Consideremos a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31 810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48 360 | 0,3 | 6898,5 |
55790 | 0,35 | 9316,5 |
92 970 | 0,4 | 12106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 23000, encontrar-se-á a linha
O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:
o que dá o imposto I = 0,65*R - 49062*nbParts.
A classe «impost» será definida da seguinte forma:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Public Class impot
' os dados necessários para o cálculo do imposto
' provêm de uma fonte externa
Private limites(), coeffR(), coeffN() As Decimal
' fabricante
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' verifica-se se as 3 tabelas têm o mesmo tamanho
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' está tudo bem
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' cálculo do imposto
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' cálculo do número de quotas
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo do rendimento tributável e do quociente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo do imposto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
É criado um objeto «imposto» com os dados que permitem o cálculo do imposto de um contribuinte. Esta é a parte estável do objeto. Uma vez criado este objeto, é possível chamar repetidamente o seu método «calcular», que calcula o imposto do contribuinte com base no seu estado civil (casado ou solteiro), no número de filhos e no seu salário anual. Um programa de teste poderia ser o seguinte:
' opções
Option Strict On
Option Explicit On
' espaços de nomes
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' programa interativo de cálculo do imposto
' o utilizador introduz três dados através do teclado: casado nbEnfants salário
' o programa apresenta então o imposto a pagar
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tabelas de dados necessárias para o cálculo do imposto
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' criação de um objeto de imposto
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' loop infinito
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' são solicitados os parâmetros para o cálculo do imposto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' há algo a fazer?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' verificação do número de argumentos na linha introduzida
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' verificação da validade dos parâmetros
If Not erreur Then
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salário
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' se os parâmetros estiverem corretos - calcula-se o imposto
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Eis um exemplo de execução do programa anterior:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





