3. Classes, estruturas, interfaces
3.1. Objetos por exemplo
3.1.1. Visão geral
Vamos agora explorar a programação orientada a objetos através de exemplos. Um objeto é uma entidade que contém dados que definem o seu estado (chamados propriedades) e funções (chamadas métodos). Um objeto é criado com base num modelo chamado classe:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
A partir da classe anterior C1, podemos 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, pelo que cada um terá o seu próprio estado. Por analogia, a declaração
cria dois objetos (o termo é incorreto aqui) do tipo (classe) Integer. A sua única propriedade é o seu valor. Se O1 é um objeto do tipo C1, O1.p1 refere-se à propriedade p1 de O1 e O1.m1 ao método m1 de O1. Vamos considerar um primeiro modelo de objeto: a classe Pessoa.
3.1.2. Definição da classe Pessoa
A definição da classe Pessoa será a seguinte:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Aqui temos a definição de uma classe, que é um tipo de dados. Quando criamos variáveis deste tipo, chamamos-lhes objetos ou instâncias de classe. Uma classe é, portanto, um modelo a partir do qual os objetos são construídos. Os membros ou campos de uma classe podem ser dados (atributos), métodos (funções) ou propriedades. As propriedades são métodos especiais utilizados para recuperar ou definir o valor dos atributos de um objeto. Estes campos podem ser acompanhados por uma das três palavras-chave seguintes:
Um campo privado só é acessível pelos métodos internos da classe | |
Um campo público é acessível por qualquer função, independentemente de estar ou não definido dentro da classe | |
Um campo protegido é acessível apenas pelos métodos internos da classe ou por um objeto derivado (ver o conceito de herança mais adiante). |
Geralmente, os dados de uma classe são declarados como privados, enquanto os seus métodos e propriedades são declarados como públicos. Isto significa que o utilizador de um objeto (o programador):
- não terá acesso direto aos dados privados do objeto
- pode chamar os métodos públicos do objeto, em particular aqueles que fornecem acesso aos seus dados privados.
A sintaxe para declarar 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 em que os atributos privados, protegidos e públicos são declarados é arbitrária.
3.1.3. O método initialize
Voltemos à nossa classe [person] declarada como:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qual é a função do método initialize? Como lastName, firstName e age são dados privados da classe Person, as instruções:
são inválidas. Temos de inicializar um objeto do tipo Pessoa através de um método público. Esta é a função do método initialize. Vamos escrever:
A sintaxe p1.initialize é válida porque initialize é pública.
3.1.4. O operador new
A sequência de instruções
está incorreto. A instrução
declara p1 como uma referência a um objeto do tipo pessoa. Este objeto ainda não existe, pelo que p1 não é inicializado. É como se estivéssemos a escrever:
onde indicamos explicitamente com a palavra-chave nothing que a variável p1 ainda não faz referência a nenhum objeto. Quando escrevemos então
chamamos o método initialize do objeto referenciado por p1. No entanto, este objeto ainda não existe, e o compilador irá reportar um erro. Para fazer com que p1 refira um objeto, deve escrever:
Isto cria um objeto Person não inicializado: os atributos name e first_name, que são referências a objetos String, terão o valor nothing, e age terá o valor 0. Existe, portanto, uma inicialização por defeito. Agora que p1 faz referência a um objeto, a instrução de inicialização para este objeto
é válida.
3.1.5. A palavra-chave Me
Vejamos o código do método *Initialize*:
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.firstName = P significa que à propriedade firstName do objeto atual (Me) é atribuído o valor P. A palavra-chave Me refere-se ao objeto atual: aquele no qual o método está a ser executado. Como sabemos isto? Vejamos como o objeto referenciado por p1 é inicializado no programa de chamada:
É o método initialize do objeto p1 que é chamado. Quando o objeto Me é referenciado dentro deste método, ele na verdade refere-se ao objeto p1. O método initialize 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 está implícita. Deve ser usada explicitamente quando houver um conflito de identificadores. É o caso da instrução:
Me.age=age;
onde age se refere tanto a um atributo do objeto atual como ao parâmetro age recebido pelo método. A ambiguidade deve então ser resolvida referindo-se ao atributo age como Me.age.
3.1.6. Um programa de teste
Eis um pequeno programa de teste:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
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. Utilizando um ficheiro de classe compilado (assembly)
Note que, no exemplo anterior, existem duas classes no nosso programa de teste: as classes person e test1. Existe outra forma de proceder:
-
Compilar a classe Person num ficheiro separado chamado assembly. Este ficheiro tem a extensão .dll
-
Compilamos a classe test1 fazendo referência ao assembly que contém a classe person.
Os dois ficheiros fonte ficam assim:
test.vb | |
person2.vb | |
A classe Person é 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 chamado personne2.dll. É a opção de compilação /t:library que especifica a criação de um ficheiro de «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 irá encontrar determinadas classes no ficheiro personne2.dll. Quando encontrar uma referência à classe Person no ficheiro fonte test.vb — uma classe não declarada no ficheiro fonte test.vb — irá procurar a classe Person nos ficheiros .dll referenciados pela opção /r. Encontrará a classe Person aqui, no assembly Person2.dll. Poderíamos ter incluído outras classes neste assembly. Para utilizar vários ficheiros de classes compilados durante a compilação, escreveríamos:
A execução do programa test1.exe produz os seguintes resultados:
3.1.8. Outro método inicializa
Vamos continuar com a classe Person e adicionar-lhe o seguinte método:
' method
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Temos agora dois métodos chamados Initialize: isto é válido desde que aceitem parâmetros diferentes. É esse o caso aqui. 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 que o método Initialize tem acesso direto aos atributos do objeto P, mesmo que estes sejam do tipo privado. Isto é sempre verdade: um objeto O1 da classe C tem sempre acesso aos atributos de objetos da mesma classe C. Aqui está um teste da nova classe Pessoa, que foi compilada em Pessoa.dll, conforme explicado anteriormente:
' options
Option Explicit On
Option Strict On
' namespaces
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 denominado New que é chamado quando o objeto é criado. É geralmente utilizado para inicializar o objeto. Se uma classe tiver um construtor que aceite n argumentos argi, a declaração e a inicialização de um objeto dessa classe podem 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 utilizado para criar um objeto dessa classe. Se uma classe C não tiver construtores, ela possui um construtor padrão, que é o construtor sem parâmetros: public New(). Os atributos do objeto são então inicializados com valores padrão. Foi isso que aconteceu nos programas anteriores, onde escrevemos:
Vamos criar dois construtores para a nossa classe Pessoa:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the person class
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' manufacturers
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
' object initialization methods
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Ambos os nossos construtores limitam-se a chamar os métodos initialise correspondentes. Note-se que, quando, por exemplo, a notação initialise(P) aparece num construtor, o compilador traduz-a para Me.initialise(P). No construtor, o método initialise é, portanto, chamado para atuar sobre o objeto referenciado por Me, ou seja, o objeto atual, aquele que está a ser construído. Aqui está um pequeno programa de teste:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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. Referências a objetos
Continuamos a utilizar a mesma classe Pessoa. O programa de teste fica assim:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 references the same object as p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' change the state of the object referenced by p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' as p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identifie()
' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
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
Ao declarar a variável p1 com
p1 faz referência ao objeto Person("Jean", "Dupont", 30), mas não é o próprio objeto. Em C, diríamos que é um ponteiro, ou seja, o endereço do objeto criado. Se escrevermos então:
Não é o objeto person("Jean","Dupont",30) que é modificado; é a referência p1 que muda de valor. O objeto person("Jean","Dupont",30) será "perdido" se não for referenciado por nenhuma outra variável.
Quando escrevemos:
inicializamos o ponteiro p2: ele «aponta» para o mesmo objeto (refere-se ao mesmo objeto) que o ponteiro p1. Assim, se modificarmos o objeto «apontado» (ou referenciado) por p1, modificamos o objeto referenciado por p2.
Quando escrevemos:
É criado um novo objeto, que é uma cópia do objeto referenciado por p1. Este novo objeto será referenciado por p3. Se modificar o objeto «para o qual aponta» (ou referenciado) por p1, não modifica de forma alguma aquele referenciado por p3. É isso que os resultados mostram.
3.1.11. Objetos temporários
Numa expressão, pode chamar explicitamente o construtor de um objeto: o objeto é criado, mas não pode aceder-lhe (para o modificar, por exemplo). Este objeto temporário é criado com o objetivo de avaliar a expressão e, em seguida, descartado. O espaço de memória que ocupou será automaticamente recuperado mais tarde por um programa chamado «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. Considere o seguinte novo programa de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
e vamos modificar os construtores da classe Pessoa para que exibam uma mensagem:
' manufacturers
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
Obtemos 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 para ler e escrever atributos privados
Adicionamos os métodos necessários à classe Person para ler ou modificar o estado dos atributos dos objetos:
Imports System
Public Class personne
' attributes
Private prenom As [String]
Private nom As [String]
Private age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessors
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
'modifiers
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:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
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. Propriedades
Existe outra forma de aceder aos atributos de uma classe: criando propriedades. Estas permitem-nos manipular atributos privados como se fossem públicos. Considere a seguinte classe Person, onde os getters e setters anteriores foram substituídos por propriedades de leitura e escrita:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' class person
Public Class personne
' attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' properties
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)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Uma propriedade permite-lhe ler (obter) ou definir (atribuir) o valor de um atributo. No nosso exemplo, colocámos um sublinhado (_) à frente dos nomes dos atributos para que as propriedades tenham os mesmos nomes que os atributos primitivos. Isto porque uma propriedade não pode ter o mesmo nome que o atributo que gere; caso contrário, haveria um conflito de nomenclatura dentro da classe. Por isso, nomeámos os nossos atributos _firstName, _lastName e _age e modificámos os construtores e métodos em conformidade. Em seguida, criámos três propriedades: lastName, firstName 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. Pode ter dois métodos chamados 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 de o fazer). O método set recebe um parâmetro chamado value, que normalmente atribui ao atributo que gere. Pode aproveitar esta oportunidade para verificar a validade do valor recebido e, se necessário, lançar uma exceção caso o valor seja inválido. É isso que se faz aqui para a idade.
Como são chamados estes métodos get e set? Considere o seguinte programa de teste:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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
estamos a tentar recuperar os valores das propriedades first_name, last_name e age da pessoa P. É o método get destas propriedades que é então chamado e devolve o valor do atributo que elas gerem.
Na instrução
pretendemos definir o valor da propriedade age. O método set desta propriedade é então chamado. Receberá 56 no seu parâmetro de valor.
Uma propriedade P de uma classe C que define apenas o método get é considerada 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. Métodos e atributos de classe
Suponhamos que queremos contar o número de objetos [pessoa] criados numa aplicação. Poderíamos gerir um contador nós próprios, mas corremos o risco de esquecer objetos temporários que são criados aqui e ali. Pareceria mais seguro incluir uma instrução nos construtores da classe [pessoa] que incremente um contador. O problema é passar uma referência a este contador para que o construtor o possa incrementar: precisamos de lhes passar um novo parâmetro. Também podemos incluir o contador na definição da classe. Uma vez que se trata de um atributo da própria classe e não de um objeto específico dessa classe, declaramo-lo de forma diferente utilizando a palavra-chave Shared:
Para fazer referência a ele, escrevemos person._nbPeople para indicar que se trata de um atributo da própria classe Person. Aqui, criámos um atributo privado que não pode ser acedido diretamente a partir do exterior da classe. Criamos, portanto, uma propriedade pública para fornecer acesso ao atributo de classe nbPeople. Para devolver o valor de nbPeople, o método Get desta propriedade não necessita de um objeto Person específico: na verdade, _nbPeople não é o atributo de um objeto específico; é o atributo de toda a classe. Por isso, precisamos de uma propriedade que também seja declarada como Shared:
que, a partir do exterior, será chamada utilizando a sintaxe person.nbPeople. A propriedade é declarada como de leitura apenas (ReadOnly) porque não fornece um método set. Aqui está um exemplo. A classe Person fica da seguinte forma:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
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:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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
Os seguintes resultados são obtidos:
3.1.15. Passar um objeto para uma função
Já mencionámos que, por predefinição, o VB.NET passa os parâmetros reais da função por valor: os valores dos parâmetros reais são copiados para os parâmetros formais. Ao lidar com um objeto, não se deixe enganar pela convenção linguística comum de se referir a um «objeto» em vez de a uma «referência a um objeto». Um objeto é manipulado apenas por meio de uma referência (um ponteiro) a ele. O que é, portanto, passado para uma função não é o próprio objeto, mas uma referência a esse objeto. É, assim, o valor da referência — e não o valor do próprio objeto — que é copiado para o parâmetro formal: nenhum novo objeto é criado. Se uma referência a um objeto R1 for passada para uma função, ela será copiada para o parâmetro formal correspondente R2. Assim, as referências R2 e R1 apontam para o mesmo objeto. Se a função modificar o objeto apontado por R2, obviamente modificará aquele referenciado por R1, uma vez que são o mesmo.
![]() |
Isto é ilustrado pelo seguinte exemplo:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' one person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' p1 display
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' p1 display
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' display person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' display 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
Podemos ver que apenas um objeto é criado: o da pessoa p1 no procedimento Main, e que o objeto foi efetivamente modificado pela função modify.
3.1.16. Uma matriz de pessoas
Um objeto é um dado como qualquer outro e, como tal, vários objetos podem ser agrupados numa matriz:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a table of people
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)
' display
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 friends(2) As Person cria uma matriz de 3 elementos do tipo Person. Estes 3 elementos são inicializados aqui com o valor Nothing, o que significa que não referenciam quaisquer objetos. Mais uma vez, num sentido técnico, referimo-nos a uma «matriz de objetos» quando, na realidade, se trata apenas de uma matriz de referências a objetos. A criação da matriz de objetos, que é ela própria um objeto, não cria quaisquer objetos do tipo dos seus elementos: isso deve ser feito posteriormente. Os seguintes resultados são obtidos:
3.2. Herança por exemplo
3.2.1. Visão geral
Aqui discutimos o conceito de herança. O objetivo da herança é «personalizar» uma classe existente para que ela atenda às nossas necessidades. Suponhamos que queremos criar uma classe Professor: um professor é um tipo específico de pessoa. Eles têm atributos que outras pessoas não têm: a disciplina que ensinam, por exemplo. Mas também têm os atributos de qualquer pessoa: nome próprio, apelido e idade. Um professor é, portanto, um membro de pleno direito da classe Pessoa, mas possui atributos adicionais. Em vez de escrever uma classe Professor do zero, preferimos basear-nos na classe Pessoa existente e adaptá-la às características específicas dos professores. É isso que o conceito de herança nos permite fazer. Para expressar que a classe Professor herda as propriedades da classe Pessoa, escrevemos:
Public Class enseignant
Inherits personne
Repare na sintaxe específica de duas linhas. A classe Pessoa é chamada de classe pai (ou base), e a classe Professor é chamada de classe derivada (ou filha). Um objeto Professor possui todas as características de um objeto Pessoa: tem os mesmos atributos e métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha; especificamos simplesmente os atributos e métodos adicionados pela classe filha. Assumimos que a classe Pessoa está definida da seguinte forma:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' class person
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
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 Identify foi substituído pela propriedade Identity, de leitura apenas, que identifica a pessoa. Criamos uma classe Teacher que herda da classe Person:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
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 Professor adiciona o seguinte aos métodos e atributos da classe Pessoa:
- um atributo `section`, que é o número da secção a que o professor pertence no corpo docente (aproximadamente uma secção por disciplina)
- um novo construtor que inicializa todos os atributos de um professor
A declaração
Public Class enseignant
Inherits personne
indica que a classe Professor deriva da classe Pessoa.
3.2.2. Criação de um objeto Professor
O construtor da classe Professor é o seguinte:
' manufacturer
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
' follow-up
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)
declara que o construtor recebe quatro parâmetros: P, N, idade e secção. Deve passar três deles (P, N, idade) para a sua classe base, neste caso a classe Pessoa. Sabemos que esta classe tem um construtor Person(string, string, int) que nos permitirá criar um objeto Person utilizando os parâmetros passados (P, N, age). A classe [Teacher] passa os parâmetros (P, N, age) para a sua classe base da seguinte forma:
MyBase.New(P, N, age)
Assim que a classe base for construída, a construção do objeto Teacher continua 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 necessita para se construir
- utiliza os outros parâmetros para inicializar os seus próprios atributos
Podíamos ter optado por 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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Isso é impossível. A classe Person declarou os seus três campos — _firstName, _lastName e _age — como privados. Apenas objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos filhos, como neste caso, devem usar métodos públicos para aceder aos mesmos. Isto teria sido diferente se a classe Person tivesse declarado os três campos como protegidos: teria então permitido que as classes derivadas tivessem acesso direto aos três campos. No nosso exemplo, utilizar o construtor da classe pai foi, portanto, a solução correta e é a abordagem padrão: ao construir um objeto filho, chamamos primeiro o construtor do objeto pai e, em seguida, completamos as inicializações específicas do objeto filho (secção no nosso exemplo).
Vamos compilar as classes Person e Teacher 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 que, para compilar a classe filha teacher, tivemos de referenciar o ficheiro person.dll, que contém a classe person. Vamos tentar um primeiro programa de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Este programa simplesmente cria um objeto Teacher (new) e identifica-o. A classe Teacher não possui um método Identity, mas a sua classe pai possui um, que também é público: através da herança, torna-se um método público da classe Teacher. 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)
Podemos ver que:
- um objeto pessoa foi construído antes do objeto professor
- a identidade obtida é a do objeto pessoa
3.2.3. Sobrecarga de um método ou propriedade
No exemplo anterior, tínhamos a identidade da pessoa como parte do professor, mas faltam algumas informações específicas da classe Professor (a secção). Por isso, precisamos de escrever uma propriedade que nos permita identificar o professor:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overload property identity
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
O método identity da classe Teacher baseia-se no método identity da sua classe pai (MyBase.identity) para apresentar a sua parte «pessoa» e, em seguida, complementa-a com o campo _section, que é específico da classe Teacher. Repare na declaração da propriedade identity:
Public Shadows ReadOnly Property identite() As String
o que indica que a propriedade identity «oculta» o método com o mesmo nome que possa existir na classe pai. Considere um objeto Teacher E. Este objeto contém um objeto Person no seu interior:
![]() |
A propriedade identity está definida tanto na classe Teacher como na sua classe pai Person. Na classe filha Teacher, a propriedade identity deve ser precedida pela palavra-chave shadows para indicar que estamos a redefinir uma nova propriedade identity para a classe Teacher.
Public Shadows ReadOnly Property identite() As String
A classe Teacher tem agora duas propriedades de identidade:
- a herdada da classe pai Person
- a sua própria
Se E for um objeto Professor, E.identity refere-se ao método identity da classe Professor. Dizemos que a propriedade identity da classe pai é «sobrescrita» pela propriedade identity da classe filha. Em geral, se O for um objeto e M for 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 pai, se tiver uma
- na classe pai da sua classe pai, se existir
- etc…
A herança permite, portanto, que métodos/propriedades com o mesmo nome na classe pai sejam redefinidos na classe filha. É isto que permite que a classe filha seja adaptada às suas próprias necessidades. Combinada com o polimorfismo, que discutiremos em breve, a sobrecarga de métodos/propriedades é o principal benefício da herança. Vamos considerar o mesmo exemplo de antes:
' options
Option Strict On
Option Explicit On
' namespaces
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. Polimorfismo
Considere 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 características adicionais. Sejam Oi objetos do tipo Ci. É válido escrever:
De facto, por herança, a classe Cj possui todas as características da classe Ci, além de outras adicionais. Portanto, um objeto Oj do tipo Cj contém em si um objeto do tipo Ci. A operação
significa que Oi é uma referência ao objeto do 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 qualquer objeto derivado da classe Ci, é chamado de polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos. Vamos dar um exemplo e considerar a seguinte função independente de qualquer classe:
Sub affiche(ByVal p As personne)
Poderíamos escrever com a mesma facilidade
como
Neste último caso, o parâmetro formal do tipo «pessoa» na função de exibição receberá um valor do tipo «professor». Uma vez que o tipo «professor» deriva do tipo «pessoa», isto é válido.
3.2.5. Redefinição e Polimorfismo
Vamos completar o nosso procedimento de exibição:
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
O método p.identity devolve uma cadeia de caracteres que identifica o objeto Person. O que acontece no nosso exemplo anterior quando se lida com um objeto Teacher:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Vejamos o seguinte exemplo:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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.identity executou a propriedade de identidade de uma pessoa em cada ocasião: primeiro a pessoa contida no professor e, depois a própria pessoa p. Não se adaptou ao objeto efetivamente passado como parâmetro para a exibição. Teríamos preferido ter a identidade completa do professor e. Para conseguir isso, a notação p.identity teria de referenciar a propriedade identity do objeto efetivamente apontado por p, em vez da propriedade identity da parte «pessoa» do objeto efetivamente apontado por p. É possível alcançar este resultado declarando identity como uma propriedade substituível na classe base Pessoa:
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 refinável ou virtual. Esta palavra-chave também pode ser aplicada a métodos. As classes derivadas que redefinem uma propriedade ou método virtual devem utilizar a palavra-chave overrides em vez de shadows para qualificar a sua propriedade ou método redefinido. Assim, na classe enseignant, a propriedade identite é definida da seguinte forma:
' overload property identity
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
O programa de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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, obtivemos com sucesso 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 ToString virtual:

O método ToString devolve o nome da classe a que o objeto pertence, conforme ilustrado no exemplo seguinte:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' a person
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Os resultados são os seguintes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Note que, embora não tenhamos reescrito o método ToString nas classes Pessoa e Professor, ainda podemos ver que o método ToString da classe Object continua a ser capaz de exibir o nome real da classe do objeto. Vamos reescrever o método ToString nas classes Pessoa e Professor:
' ToString
Public Overrides Function ToString() As String
' we return the identity property
Return identite
End Function
A definição é a mesma em ambas as classes. Considere o seguinte programa de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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
Considere a classe [ArrayList] predefinida na plataforma .NET. Esta classe permite-lhe armazenar objetos numa lista. Pertence ao namespace [System.Collections]. No exemplo seguinte, utilizamos esta classe para armazenar uma lista de pessoas (em sentido lato):
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' an indexer
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
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Vamos fornecer algumas informações sobre determinadas propriedades e métodos da classe [ArrayList]:
propriedade que devolve o número de elementos na lista | |
método para adicionar um objeto à lista | |
método que devolve o i-ésimo elemento da lista |
Notamos que, para obter o i-ésimo elemento da lista, não escrevemos [list.Item(i)] mas diretamente [list(i)], o que, à primeira vista, parece incorreto. No entanto, isto é possível porque a classe [ArrayList] define uma propriedade padrão [Item] com 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 [list(i)], verifica se a classe [ArrayList] definiu uma propriedade com a seguinte assinatura:
Default Public Property Proc(ByVal var As Integer) As Type
Aqui, irá encontrar o procedimento [Item]. Em seguida, irá converter a notação [list(i)] em [list.Item(i)]. A propriedade [Item] é 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 [listOfPeople] que será uma lista de pessoas, ou seja, uma lista específica que parece natural derivar da classe [ArrayList]:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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
' an indexer
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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
A classe possui os seguintes métodos e propriedades:
retorna uma string que "representa" o conteúdo da lista | |
método para adicionar uma pessoa à lista | |
propriedade indexada padrão que devolve a i-ésima pessoa da lista |
Vamos analisar as novas funcionalidades:
É criado um novo método [Add].
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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 [person] ou de um tipo derivado, utilizando a função [TypeOf]. Se não for esse o caso, é lançada uma exceção utilizando a instrução [Throw]. Se o objeto adicionado for efetivamente do tipo [person], o método [Add] da classe base é utilizado para realizar a adição.
É criada uma propriedade indexada:
' an indexer
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 predefinida chamada [Item] na classe base com a mesma assinatura. Por isso, temos de utilizar a palavra-chave [Shadows] para indicar que a nova propriedade indexada [Index] irá «ocultar» a propriedade [Item] da classe base. Note-se que isto se aplica mesmo que as duas propriedades não tenham o mesmo nome. A propriedade [Index] permite referenciar a pessoa no índice i da lista. Ela depende da propriedade [Item] da classe base para aceder ao elemento no índice i do objeto [ArrayList] subjacente. São feitas alterações de tipo para ter em conta o facto de que a propriedade [Item] funciona com elementos do tipo [Object], enquanto a propriedade [Index] funciona com elementos do tipo [Person].
Por fim, reescrevemos o método [ToString] da classe [ArrayList]:
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
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 e1, e2, ..., en são os elementos da lista. Note a notação [Me(i)], que se refere ao i-ésimo elemento do objeto atual [Me]. Esta é a propriedade indexada predefinida que é utilizada. 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:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' create an empty list of people
Dim liste As listeDePersonnes = New listeDePersonnes
' creating people
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)
' list filling
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' list display
Console.Out.WriteLine(liste.ToString)
' add an object other than person
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
depois executou:
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
Pode-se querer escrever
onde l seria do tipo [listOfPeople]. Aqui, queremos indexar a lista l não por um número de elemento, mas pelo nome de uma pessoa. Para fazer isso, definimos uma nova propriedade indexada por padrão:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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 uma nova 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 PeopleList utilizando uma cadeia de caracteres N. O resultado de PeopleList(N) é um inteiro. Este inteiro corresponderá à posição na lista da pessoa com o nome N, ou -1 se essa pessoa não estiver na lista. Definimos apenas a propriedade get, impedindo assim a atribuição listOfPeople("name")=value, que teria exigido a definição da propriedade set. Daí a palavra-chave [ReadOnly]. A palavra-chave [Shadows] é necessária para ocultar a propriedade padrão da classe base (mesmo que não tenha a mesma assinatura).
No corpo do método get, percorremos a lista de pessoas à procura do nome N passado como parâmetro. Se o encontrarmos na posição i, devolvemos i; caso contrário, devolvemos -1.
Um novo programa de teste poderia ser o seguinte:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a list of people
Dim l As New listeDePersonnes
' add people
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change item 1
l(1) = New personne("sylvie", "cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' people search
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. 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
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
Apesar das semelhanças na sintaxe, existem diferenças significativas entre classes e estruturas. Por exemplo, o conceito de herança não existe nas estruturas. Se estivermos a escrever uma classe que não se destina a ser derivada de outra, quais são as diferenças entre estruturas e classes que nos ajudarão a escolher entre as duas? Vamos usar o exemplo seguinte para descobrir:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' class cperson
Class cpersonne
Public nom As String
Public age As Integer
End Class
' a test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' a p2 person
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is modified
sp2.nom = "nicole"
sp2.age = 30
' checking sp1 and sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' a cperson cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' a P2 person
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is modified
cp2.nom = "nicole"
cp2.age = 30
' p1 and P2 verification
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 utilizámos uma classe Pessoa, agora utilizamos uma EstruturaPessoa:
' structure spersonne
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] (equivalente, em termos gerais, à nossa estrutura), e cp1 é, então, o endereço (a referência) desse objeto.
Resumindo
- no caso da estrutura, o valor de sp1 é a própria estrutura
- no caso de uma classe, o valor de p1 é o endereço do objeto criado
![]() |
![]() |
Quando no programa escrevemos
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 é, assim, duplicada em sp2. Trata-se de uma cópia de valor.
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, o próprio objeto não é duplicado. Simplesmente tem duas referências a apontar para ele:
![]() |
No caso da estrutura, se modificarmos o valor de sp2, não modificamos o valor de sp1, como demonstra o programa. No caso do objeto, se modificarmos o objeto apontado por cp2, o apontado por cp1 é modificado, uma vez que são o mesmo. Isto também é demonstrado pelos resultados do programa.
Podemos, portanto, concluir a partir 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 para o qual aponta
Uma vez compreendida esta diferença fundamental, a estrutura revela-se muito semelhante a uma classe, como se pode ver no seguinte novo exemplo:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure person
Structure personne
' attributes
Private _nom As String
Private _age As Integer
' properties
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
' Manufacturer
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
' a test module
Module test
Sub Main()
' one person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' one person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.nom = "nicole"
p2.age = 30
' checking p1 and p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
São obtidos os seguintes resultados de execução:
A única diferença notável aqui entre uma estrutura e uma classe é que, com uma classe, os objetos p1 e p2 teriam o mesmo valor no final do programa, nomeadamente o de p2.
3.5. Interfaces
Uma interface é um conjunto de protótipos de métodos ou propriedades que forma 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 verifica esta implementação. Aqui, por exemplo, está 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 [mean] e [standardDev] devem ser definidos na classe C. Considere o seguinte código:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' class notes
Public Class notes
' attribute
Protected _matière As String
Protected _élèves() As élève
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' student & subject memorization
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
' concatenate all the notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'final note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' end
Return valeur
End Function
End Class
A classe notes recolhe as notas de uma turma numa disciplina:
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
Os atributos são declarados como protegidos para que possam ser acedidos a partir de uma classe derivada. O tipo «student» é 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
' manufacturer
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
' attributes
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 average e standardDeviation com as assinaturas especificadas na interface Istats. A classe notesStats é a seguinte:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' average score calculation
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
' standard deviation
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
' istats interface methods
Public Function moyenne() As Double Implements Istats.moyenne
' makes the average score
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' makes the standard deviation
Return _écartType
End Function
End Class
A média _mean e o desvio padrão _standardDev são calculados quando o objeto é criado. Portanto, os métodos mean e standardDev simplesmente devolvem os valores dos atributos _mean e _standardDev. Ambos os métodos devolvem -1 se a matriz student estiver vazia.
A seguinte classe de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' some students & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' recorded in a notes object
Dim anglais As New notes("anglais", ELEVES)
' and display
Console.Out.WriteLine((anglais.ToString))
' idem with mean and standard deviation
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
dá 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 average e standardDev por si própria, sem especificar que implementava a interface Istats. Qual é o objetivo das interfaces? É o seguinte: uma função pode aceitar como parâmetro formal um valor do tipo I. Qualquer objeto da classe C que implemente a interface I pode então ser um parâmetro real dessa função. Considere o seguinte exemplo:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an Iexample interface
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
' a 1st class
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
'a 2nd class
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 Iexample define dois métodos: add e subtract. As classes Class1 e Class2 implementam esta interface. Note-se que estas classes não fazem mais nada, para simplificar o exemplo. Considere agora o seguinte exemplo:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Module test
'calculate
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
' the Hand function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New classe1
Dim c2 As New classe2
' static function calls calculate
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
A função calculate aceita um elemento do tipo Iexample como parâmetro. Por conseguinte, pode receber um objeto do tipo class1 ou class2 para este parâmetro. É isto que é feito no procedimento Main, com os seguintes resultados:
Podemos ver, então, que esta propriedade é semelhante ao polimorfismo que discutimos no caso das classes. Se um conjunto de classes Ci que não estão relacionadas entre si por herança (e, portanto, não podem utilizar o polimorfismo baseado na herança) partilham um conjunto de métodos com a mesma assinatura, pode ser útil agrupar esses métodos numa interface I, da qual todas as classes relevantes herdariam. As instâncias destas classes Ci podem então ser utilizadas como parâmetros para funções que aceitam um parâmetro do tipo I, ou seja, 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 várias classes Ci. Por fim, note-se que a herança de interfaces pode ser múltipla, ou seja, podemos escrever
Public Class classe
Implements I1,I2,...
onde os Ij são interfaces.
3.6. Espaços de nomes
Para escrever uma linha no ecrã, usamos a instrução
Se olharmos para a definição da classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
vemos que faz parte do namespace System. Isto significa que a classe Console deve ser referida como System.Console, e devemos, na verdade, escrever:
Evitamos isto utilizando uma cláusula imports:
Dizemos que importamos o namespace System utilizando a cláusula imports. Quando o compilador encontra um nome de classe (aqui, Console), procura-o nos vários namespaces importados pelas cláusulas imports. Aqui, encontrará a classe Console no namespace System. Agora, vamos observar 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. Ao compilar fora do Visual Studio.NET e quando for necessário especificar as referências às várias DLLs que contêm as classes que precisa de utilizar, esta informação pode ser útil. Lembre-se de que, para referenciar as DLLs necessárias para compilar uma classe, deve escrever:
Ao criar uma classe, pode criá-la dentro de um namespace. O objetivo destes namespaces é evitar conflitos de nomes entre classes, por exemplo, quando estas são comercializadas. Considere duas empresas, E1 e E2, que distribuem classes empacotadas, respetivamente, nas DLLs E1.dll e E2.dll. Suponha que um cliente C adquira estes dois conjuntos de classes, nos quais ambas as empresas definiram uma classe Person. O cliente C compila um programa da seguinte forma:
Se o ficheiro fonte prog.vb utilizar a classe Person, o compilador não saberá se deve utilizar a classe Person da E1.dll ou a da E2.dll. Irá apresentar um erro. Se a empresa E1 tiver o cuidado de criar as suas classes num espaço de nomes chamado E1 e a empresa E2 num espaço de nomes chamado E2, as duas classes Person passarão a chamar-se E1.Person e E2.Person. O cliente deve utilizar E1.Person ou E2.Person nas suas classes, mas não Person. O espaço de nomes resolve esta ambiguidade. Para criar uma classe num espaço de nomes, escreve-se:
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
Para este exemplo, vamos criar a classe Pessoa que estudámos anteriormente num namespace. Escolheremos istia.st como namespace. A classe Pessoa fica assim:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' creation of istia.st namespace
Namespace istia.st
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Esta classe é compilada em person.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 Person numa classe de teste:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports istia.st
' pg test
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 namespace 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 produz um ficheiro test.exe que, quando executado, apresenta os seguintes resultados:
3.7. O cálculo do imposto Exemplo
Vamos revisitar o cálculo de impostos já abordado no capítulo anterior e processá-lo utilizando uma classe. Vamos rever o problema:
Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:
- calculamos o número de escalões de imposto para o empregado como nbParts = nbEnfants / 2 + 1 se for solteiro, e nbEnfants / 2 + 2 se for casado, onde nbEnfants é o número de filhos.
- Se tiver pelo menos três filhos, recebe uma meia quota adicional
- Calculamos o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- Calculamos o seu coeficiente familiar QF = R / nbParts
- calculamos o seu imposto I. Considere a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15 640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31 810 | 0,2 | 3.309,5 |
39 970 | 0,25 | 4.900 |
48 360 | 0,3 | 6.898,5 |
55 790 | 0,35 | 9.316,5 |
92 970 | 0,4 | 12 106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30 710 |
195 000 | 0,60 | 39 312 |
0 | 0,65 | 49 062 |
Cada linha tem 3 campos. Para calcular o imposto I, encontre a primeira linha em que QF <= campo1. Por exemplo, se QF = 23.000, a linha encontrada será
O Imposto I é então igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a condição QF<=field1 nunca seja satisfeita, então são utilizados os coeficientes da última linha. Aqui:
o que dá o imposto I = 0,65 * R - 49062 * nbParts.
A classe **impot** será definida da seguinte forma:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class impot
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays have the same size
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
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
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
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
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 fiscal com os dados necessários para calcular o imposto de um contribuinte. Esta é a parte estável do objeto. Uma vez criado este objeto, o seu método **calculate** pode ser chamado repetidamente para calcular o imposto do contribuinte com base no seu estado civil (casado ou não), número de filhos e salário anual. Um programa de teste pode ter o seguinte aspeto:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
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"
' data tables required for tax calculation
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}
' tax object creation
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
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
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()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
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
' checking parameter validity
If Not erreur Then
' married
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
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
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
Aqui está um exemplo do programa anterior em ação:
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 :





