Skip to content

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

    dim i, j as integer

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:

privado
Um campo privado só é acessível pelos métodos internos da classe
público
Um campo público é acessível por qualquer função, independentemente de estar ou não definido dentro da classe
protegido
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:

dim p1 as personne p1
p1.prenom="Jean"
p1.nom="Dupont"
p1.age=30

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:

dim p1 as personne
p1.initialise("Jean","Dupont",30)

A sintaxe p1.initialize é válida porque initialize é pública.

3.1.4. O operador new

A sequência de instruções

dim p1 as personne
p1.initialise("Jean","Dupont",30)

está incorreto. A instrução

dim p1 as personne

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:

dim p1 as personne=nothing

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

p1.initialise("Jean","Dupont",30)

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:

dim p1 as personne=new personne()

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

p1.initialise("Jean","Dupont",30)

é 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:

p1.initialise("Jean","Dupont",30)

É 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
dos>personne1
Jean,Dupont,30

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

Módulo test1
    Sub Main()
        Dim p1 As New Person
        p1.initialize("Jean", "Dupont", 30)
        p1.identificar()
    Fim Sub
Fim do Módulo
person2.vb

' opções
Option Explicit On
Option Strict On

' namespaces
Importa System

Classe Pública Pessoa
    ' atributos
    Private firstName As String
    Private apelido As String
    Private idade As Integer

    ' método
    Public Sub Inicializar(ByVal NomeComoString, ByVal ApelidoComoString, ByVal IdadeComoInteiro)
        Me.firstName = P
        Me.lastName = N
        Me.age = age
    End Sub    'inicializar

    ' método
    Public Sub identificar()
        Console.Out.WriteLine((primeiroNome & "," & apelido & "," & idade))
    End Sub    'identificar
End Class 'pessoa

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:

vbc /r:fic1.dll /r:fic2.dll ... fichierSource.vb

A execução do programa test1.exe produz os seguintes resultados:

dos>test
Jean,Dupont,30

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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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:

    dim p1 as personne
    p1=new personne

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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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

dim p1 as personne=new personne("Jean","Dupont",30)

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:

    p1=nothing

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:

dim p2 as personne=p1

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:

dim p3 as personne =new personne(p1);

É 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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

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

        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))

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

        P.age = 56

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)
âge (-4) invalide

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:

    Private Shared _nbPersonnes As Long = 0

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:

    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

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:

    Nombre de personnes créées : 2

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:

Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66

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:

    Oi=Oj avec j>i

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

Oi=Oj

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

    dim p as personne
    ...
    affiche(p);

como

    dim e as enseignant
    ...
    affiche(e);

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:

    public class personne

estamos implicitamente a escrever:

    public class personne 
         inherits object

A classe object define um método ToString virtual:

Image

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]:

Contagem
propriedade que devolve o número de elementos na lista
Add(Object)
método para adicionar um objeto à lista
Item(Integer i)
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:

ToString
retorna uma string que "representa" o conteúdo da lista
Add(pessoa)
método para adicionar uma pessoa à lista
Item(Integer i)
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

dim p as personne=l("nom")

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:

p1=personne(paul,10)
p2=personne(paul,10)
p1=personne(paul,10)
p2=personne(nicole,30)

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:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

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:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

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:

17
21
127
201

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

Console.Out.WriteLine(...)

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:

System.Console.Out.WriteLine(...)

Evitamos isto utilizando uma cláusula imports:

imports System
...
Console.Out.WriteLine(...)

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:

vbc /r:fic1.dll /r:fic2.dll ... prog.vb

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:

vbc /r:E1.dll /r:E2.dll prog.vb

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>dir
11/03/2004  18:27               610 personne.vb
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>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb

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:

Jean,Dupont,30

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á

    24740        0.15        2072.5

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:

    0                0.65        49062

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:

dir>dir
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:21             2 805 test.vb
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 :