Skip to content

3. Classes, estruturas, interfaces

3.1. O objeto no exemplo

3.1.1. Noções gerais

Abordamos agora, através de exemplos, a programação orientada a objetos. Um objeto é uma entidade que contém dados que definem o seu estado (denominados propriedades) e funções (denominadas métodos). Um objeto é criado de acordo com um modelo denominado classe:

Public Class c1
     ' atributos
    Private p1 As type1
    Private p2 As type2
....

     ' método
    Public Sub m1(....)
...
    End Sub    

     ' método
    Public Function m2(...)
....
    End Function    
End Class

A partir da classe anterior C1, é possível criar vários objetos O1, O2,… Todos terão as propriedades p1, p2, … e os métodos m3, m4, … Mas terão valores diferentes para as suas propriedades pi, tendo assim cada um um estado que lhes é próprio. Por analogia, a declaração

    dim i, j as integer

cria dois objetos (o termo é incorreto neste contexto) do tipo (classe) Integer. A sua única propriedade é o seu valor. Se O1 é um objeto do tipo C1, O1.p1 designa a propriedade p1 de O1 e O1.m1 o método m1 de O1. Consideremos um primeiro modelo de objeto: a classe personne.

3.1.2. Definição da classe «pessoa»

A definição da classe personne será a seguinte:


Public Class personne
    ' atributos
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' método
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub

    ' método
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Temos aqui a definição de uma classe, ou seja, de um tipo de dados. Quando criarmos variáveis deste tipo, chamar-lhes-emos objetos ou instâncias de classes. Uma classe é, portanto, um modelo a partir do qual são construídos os objetos. Os membros ou campos de uma classe podem ser dados (atributos), métodos (funções) ou propriedades. As propriedades são métodos específicos que servem para conhecer ou definir o valor dos atributos do objeto. Estes campos podem ser acompanhados por uma das três palavras-chave seguintes:

privé
Um campo privado (private) só é acessível através dos métodos internos da classe
public
Um campo público (public) é acessível por qualquer função, definida ou não no âmbito da classe
protégé
Um campo protegido (protected) só é acessível através dos métodos internos da classe ou de um objeto derivado (ver mais adiante o conceito de herança).

Em geral, os dados de uma classe são declarados privados, enquanto os seus métodos e propriedades são declarados públicos. Isto significa que o utilizador de um objeto (o programador):

  • não terá acesso direto aos dados privados do objeto
  • poderá recorrer aos métodos públicos do objeto e, nomeadamente, àqueles que dão acesso aos seus dados privados.

A sintaxe de declaração de uma classe é a seguinte:


public class classe
    private  donnée ou méthode ou propriété privée
    public  donnée ou méthode ou propriété publique
    protected  donnée ou méthode ou propriété protégée
end class

A ordem de declaração dos atributos private, protected e public é arbitrária.

3.1.3. O método inicializa

Voltemos à nossa classe [personne], declarada da seguinte forma:


Public Class personne
    ' atributos
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' método
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub

    ' método
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Qual é a função do método initialise? Como nom, prenom e age são dados privados da classe personne, as instruções:

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

são ilegais. Temos de inicializar um objeto do tipo personne através de um método público. É essa a função do método initialise. Escreveremos:

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

A sintaxe p1.initialise é válida, uma vez que initialise é de acesso público.

3.1.4. O operador «new»

A sequência de instruções

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

está incorreta. A instrução

dim p1 as personne

declara p1 como uma referência a um objeto do tipo personne. Esse objeto ainda não existe e, por isso, p1 não está inicializado. É como se escrevêssemos:

dim p1 as personne=nothing

onde se indica explicitamente, com a palavra-chave nothing, que a variável p1 ainda não faz referência a nenhum objeto. Quando, em seguida, se escreve

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

estamos a chamar o método initialise do objeto referenciado por p1. No entanto, esse objeto ainda não existe e o compilador irá sinalizar o erro. Para que p1 faça referência a um objeto, é necessário escrever:

dim p1 as personne=new personne()

Isto tem como efeito a criação de um objeto do tipo personne ainda não inicializado: os atributos nom e prenom, que são referências a objetos do tipo String, terão o valor nothing, e age terá o valor 0. Existe, portanto, uma inicialização por predefinição. Agora que p1 faz referência a um objeto, a instrução de inicialização desse objeto

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

é válida.

3.1.5. A palavra-chave Me

Vejamos o código do método initialise:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub    

A instrução Me.prenom=P significa que o atributo prenom do objeto atual (Me) recebe o valor P. A palavra-chave Me designa o objeto atual: aquele no qual se encontra o método executado. Como é que o sabemos? Vejamos como se realiza a inicialização do objeto referenciado por p1 no programa chamador:

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

É o método initialise do objeto p1 que é chamado. Quando, neste método, se faz referência ao objeto Me, está-se, na verdade, a fazer referência ao objeto p1. O método initialise também poderia ter sido escrito da seguinte forma:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        prenom = P
        nom = N
        Me.age = age
    End Sub    

Quando um método de um objeto faz referência a um atributo A desse objeto, a notação Me.A é implícita. Deve ser utilizada explicitamente quando existe um conflito de identificadores. É o caso da instrução:


Me.age=age;

onde age designa um atributo do objeto atual, bem como o parâmetro age recebido pelo método. É então necessário eliminar a ambiguidade, designando o atributo age como Me.age.

3.1.6. Um programa de teste

Eis um pequeno programa de teste:


Public Class personne
    ' atributos
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' método
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub

    ' método
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

e os resultados obtidos:

dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>personne1
Jean,Dupont,30

3.1.7. Utilizar um ficheiro de classes compiladas (assembly)

Note-se que, no exemplo anterior, existem duas classes no nosso programa de teste: as classes personne e test1. Existe outra forma de proceder:

  • compila-se a classe «pessoa» num ficheiro específico denominado «assembly». Este ficheiro tem a extensão .dll

  • compila-se a classe «test1» fazendo referência ao assembly que contém a classe «pessoa».

Os dois ficheiros-fonte ficam assim:

test.vb

Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End module
personne2.vb

' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

Public Class personne
    ' atributos
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' método
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub    'initialise

    ' método
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub    'identifie
End Class 'personne

A classe personne é compilada pela seguinte instrução:

dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.dll

A compilação produziu um ficheiro personne2.dll. É a opção de compilação /t:library que indica a produção de um ficheiro «assembly». Agora, vamos compilar o ficheiro test.vb:

dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.dll
24/02/2004  16:51                3 072 test.exe

A opção de compilação /r:personne2.dll indica ao compilador que encontrará determinadas classes no ficheiro personne2.dll. Quando, no ficheiro fonte test.vb, encontrar uma referência à classe personne — classe não declarada no ficheiro fonte test.vb —, irá procurar a classe personne nos ficheiros .dll referenciados pela opção /r. Aí encontrará a classe personne no conjunto personne2.dll. Poderiam ter sido incluídas outras classes neste assembly. Para utilizar vários ficheiros de classes compiladas durante a compilação, escrever-se-á:

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

Consideremos novamente a classe personne e adicionemos-lhe o seguinte método:


    ' método
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub

Temos agora dois métodos com o nome initialise: isto é válido desde que aceitem parâmetros diferentes. É o que acontece neste caso. O parâmetro é agora uma referência P a uma pessoa. Os atributos da pessoa P são então atribuídos ao objeto atual (Me). Note-se que o método initialise tem acesso direto aos atributos do objeto P, embora estes sejam do tipo private. Isto é sempre verdade: um objeto O1 de uma classe C tem sempre acesso aos atributos dos objetos da mesma classe C. Eis um teste da nova classe personne, tendo esta sido compilada em personne.dll, tal como explicado anteriormente:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne
        p2.initialise(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

e os seus resultados:

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

3.1.9. Construtores da classe «pessoa»

Um construtor é um procedimento com o nome New que é chamado aquando da criação do objeto. É geralmente utilizado para o inicializar. Se uma classe tiver um construtor que aceite n argumentos argi, a declaração e a inicialização de um objeto dessa classe poderão ser feitas da seguinte forma:


        dim objet as classe =new classe(arg1,arg2, ... argn)

ou


        dim objet as classe

        objet=new classe(arg1,arg2, ... argn)

Quando uma classe tem um ou mais construtores, um desses construtores deve ser obrigatoriamente utilizado para criar um objeto dessa classe. Se uma classe C não tiver nenhum construtor, dispõe de um construtor por predefinição, que é o construtor sem parâmetros: public New(). Os atributos do objeto são então inicializados com valores por predefinição. Foi isso que aconteceu nos programas anteriores, onde escrevemos:

    dim p1 as personne
    p1=new personne

Vamos criar dois construtores para a nossa classe personne:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' a classe «pessoa»
Public Class personne
    ' atributos
    Private prenom As String
    Private nom As String
    Private age As Integer

    ' construtores
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        initialise(P, N, age)
    End Sub

    Public Sub New(ByVal P As personne)
        initialise(P)
    End Sub

    ' métodos de inicialização do objeto
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub

    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub

    ' método
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Os nossos dois construtores limitam-se a chamar os métodos initialise correspondentes. Recorde-se que, quando num construtor se encontra a notação initialise(P), por exemplo, o compilador traduz para Me.initialise(P). No construtor, o método initialise é, portanto, chamado para trabalhar no objeto referenciado por Me, ou seja, o objeto atual, aquele que está a ser construído. Eis um pequeno programa de teste:


' opções 
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

e os resultados obtidos:

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

3.1.10. As referências dos objetos

Continuamos a utilizar a mesma classe personne. O programa de teste fica assim:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        ' p1
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()

        ' p2 faz referência ao mesmo objeto que p1
        Dim p2 As personne = p1
        Console.Out.Write("p2=")
        p2.identifie()

        ' p3 refere-se a um objeto que será uma cópia do objeto referido por p1
        Dim p3 As New personne(p1)
        Console.Out.Write("p3=")
        p3.identifie()

        ' altera-se o estado do objeto referenciado por p1
        p1.initialise("Micheline", "Benoît", 67)
        Console.Out.Write("p1=")
        p1.identifie()

        ' como p2 = p1, o objeto referenciado por p2 deve ter mudado de estado
        Console.Out.Write("p2=")
        p2.identifie()

        ' como p3 não aponta para o mesmo objeto que p1, o objeto a que p3 aponta não deve ter mudado
        Console.Out.Write("p3=")
        p3.identifie()
    End Sub
End Module

Os resultados obtidos são os seguintes:

p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30

Quando se declara a variável p1 através de

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

p1 faz referência ao objeto personne("Jean","Dupont",30), mas não é o próprio objeto. Em C, dir-se-ia que se trata de um ponteiro, c.a.d, para a morada do objeto criado. Se escrevermos em seguida:

    p1=nothing

Não é o objeto personne("Jean","Dupont",30) que é alterado, mas sim a referência p1 que muda de valor. O objeto personne("Jean","Dupont",30) será «perdido» se não for referenciado por nenhuma outra variável.

Quando se escreve:

dim p2 as personne=p1

inicializa-se o ponteiro p2: este «aponta» para o mesmo objeto (designa o mesmo objeto) que o ponteiro p1. Assim, se se alterar o objeto «apontado» (ou referenciado) por p1, altera-se também o objeto referenciado por p2.

Quando se escreve:

dim p3 as personne =new personne(p1);

é criado um novo objeto, uma cópia do objeto referenciado por p1. Este novo objeto será referenciado por p3. Se se alterar o objeto «apontado» (ou referenciado) por p1, não se altera de forma alguma aquele referenciado por p3. É isso que mostram os resultados obtidos.

3.1.11. Os objetos temporários

Numa expressão, é possível invocar explicitamente o construtor de um objeto: este é criado, mas não temos acesso a ele (para o modificar, por exemplo). Este objeto temporário é criado para efeitos de avaliação da expressão e, em seguida, descartado. O espaço de memória que ocupava será automaticamente recuperado posteriormente por um programa denominado «garbage collector», cuja função é recuperar o espaço de memória ocupado por objetos que já não são referenciados pelos dados do programa. Consideremos o seguinte novo programa de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        Dim p As New personne(New personne("Jean", "Dupont", 30))
        p.identifie()
    End Sub
End Module

e alteremos os construtores da classe personne para que exibam uma mensagem:


    ' construtores
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Console.Out.WriteLine("Constructeur personne(String, String, integer)")
        initialise(P, N, age)
    End Sub

    Public Sub New(ByVal P As personne)
        Console.Out.WriteLine("Constructeur personne(personne)")
        initialise(P)
    End Sub

Obtenemos os seguintes resultados:

dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30

mostrando a construção sucessiva dos dois objetos temporários.

3.1.12. Métodos de leitura e gravação dos atributos privados

Adicionamos à classe personne os métodos necessários para ler ou alterar o estado dos atributos dos objetos:


Imports System

Public Class personne

    ' atributos
    Private prenom As [String]
    Private nom As [String]
    Private age As Integer

    ' construtores
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub

    Public Sub New(ByVal P As personne)
        Me.prenom = P.prenom
        Me.nom = P.nom
        Me.age = P.age
    End Sub

    ' identificadores
    Public Sub identifie()
        Console.Out.WriteLine((prenom + "," + nom + "," + age))
    End Sub

    ' acessores
    Public Function getPrenom() As [String]
        Return prenom
    End Function

    Public Function getNom() As [String]
        Return nom
    End Function

    Public Function getAge() As Integer
        Return age
    End Function

    'modificadores
    Public Sub setPrenom(ByVal P As [String])
        Me.prenom = P
    End Sub

    Public Sub setNom(ByVal N As [String])
        Me.nom = N
    End Sub

    Public Sub setAge(ByVal age As Integer)
        Me.age = age
    End Sub
End Class

Testamos a nova classe com o seguinte programa:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' página de teste
Public Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
        P.setAge(56)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
    End Sub
End Module

e obtemos os seguintes resultados:

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

3.1.13. As propriedades

Existe outra forma de aceder aos atributos de uma classe: criar propriedades. Estas permitem-nos manipular atributos privados como se fossem públicos. Consideremos a seguinte classe personne, na qual os acessores e modificadores anteriores foram substituídos por propriedades de leitura e escrita:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' classe pessoa
Public Class personne

    ' atributos
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer


    ' fabricantes
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub


    Public Sub New(ByVal P As personne)
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub

    ' identifica
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub

    ' propriedades
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property

    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property

    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' idade válida?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Uma propriedade permite ler (get) ou definir (set) o valor de um atributo. No nosso exemplo, colocámos o prefixo _ nos nomes dos atributos para que as propriedades tenham o mesmo nome que os atributos primitivos. Com efeito, uma propriedade não pode ter o mesmo nome que o atributo que gere, pois, nesse caso, haveria um conflito de nomes na classe. Por isso, denominámos os nossos atributos _prenom, _nom, _age e alterámos os construtores e métodos em conformidade. Em seguida, criámos três propriedades: nom, prenom e age. Uma propriedade é declarada da seguinte forma:


    Public Property nom() As Type
        Get
...
        End Get
        Set(ByVal Value As Type)
...
        End Set    
    End Property

onde Type deve ser o tipo do atributo gerido pela propriedade. Esta pode ter dois métodos denominados get e set. O método get é normalmente responsável por devolver o valor do atributo que gere (pode devolver outra coisa, nada o impede). O método set recebe um parâmetro chamado value, que normalmente atribui ao atributo que gere. Pode aproveitar para verificar a validade do valor recebido e, eventualmente, lançar uma exceção se o valor se revelar inválido. É isso que se faz aqui para a idade.

Como é que estes métodos get e set são chamados? Consideremos o seguinte programa de teste:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        P.age = 56
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        Try
            P.age = -4
        Catch ex As Exception
            Console.Error.WriteLine(ex.Message)
        End Try
    End Sub
End Module

Na instrução

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

procura-se obter os valores das propriedades prenom, nom e age da pessoa P. É o método get destas propriedades que é então chamado e que devolve o valor do atributo que elas gerem.

Na instrução

        P.age = 56

pretende-se definir o valor da propriedade age. É então o método set desta propriedade que é chamado. Este receberá 56 no seu parâmetro value.

Uma propriedade P de uma classe C que defina apenas o método get é designada como de leitura única. Se c for um objeto da classe C, a operação c.P=valor será rejeitada pelo compilador.

A execução do programa de teste anterior produz os seguintes resultados:

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. Os métodos e atributos de classe

Suponhamos que queremos contar o número de objetos [personne] criados numa aplicação. Podemos gerir nós próprios um contador, mas corremos o risco de nos esquecermos dos objetos temporários que são criados aqui e ali. Pareceria mais seguro incluir nos construtores da classe [personne] uma instrução que incremente um contador. O problema reside em passar uma referência a esse contador para que o construtor o possa incrementar: é necessário passar-lhes um novo parâmetro. Também é possível incluir o contador na definição da classe. Como se trata de um atributo da própria classe e não de um objeto específico dessa classe, declara-se de forma diferente com a palavra-chave Shared:

    Private Shared _nbPersonnes As Long = 0

Para o referenciar, escreve-se personne._nbPersonnes para indicar que se trata de um atributo da própria classe personne. Aqui, criámos um atributo privado ao qual não se terá acesso direto fora da classe. Criamos, portanto, uma propriedade pública para dar acesso ao atributo da classe nbPersonnes. Para definir o valor de nbPersonnes, o método get desta propriedade não necessita de um objeto personne específico: na verdade, _nbPersonnes não é o atributo de um objeto específico, mas sim o atributo de toda uma classe. Por isso, é necessária uma propriedade também declarada como Shared:

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

que, externamente, será chamada com a sintaxe personne.nbPersonnes. A propriedade é declarada como de leitura única (ReadOnly), uma vez que não disponibiliza um método set. Eis um exemplo. A classe personne passa a ter a seguinte forma:


Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' classe
Public Class personne
    ' atributos de classe
    Private Shared _nbPersonnes As Long = 0

    ' atributos de instância
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer

    ' construtores
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' mais uma pessoa
        _nbPersonnes += 1
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub

    Public Sub New(ByVal P As personne)
        ' mais uma pessoa
        _nbPersonnes += 1
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub

    ' identifica
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub

    ' propriedade de classe
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

    ' propriedades de instância
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property

    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property

    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' idade válida?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Com o seguinte programa:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Dim p2 As New personne(p1)
        Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
    End Sub
End Module

obtêm-se os seguintes resultados:

    Nombre de personnes créées : 2

3.1.15. Passagem de um objeto para uma função

Já referimos que, por predefinição, o VB.NET passa os parâmetros efetivos de uma função por valor: os valores dos parâmetros efetivos são copiados para os parâmetros formais. No caso de um objeto, não se deve deixar enganar pelo uso incorreto da linguagem que ocorre sistematicamente ao falar de «objeto» em vez de «referência de objeto». Um objeto só é manipulado através de uma referência (um ponteiro) a ele. O que é, portanto, transmitido a uma função não é o próprio objeto, mas uma referência a esse objeto. É, portanto, o valor da referência — e não o valor do próprio objeto — que é duplicado no parâmetro formal: não há criação de um novo objeto. Se uma referência de objeto R1 for passada para uma função, será copiada para o parâmetro formal correspondente R2. Assim, as referências R2 e R1 designam o mesmo objeto. Se a função alterar o objeto a que aponta R2, alterará, evidentemente, aquele a que aponta R1, uma vez que se trata do mesmo.

É isso que ilustra o exemplo seguinte:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        ' uma pessoa p1
        Dim p1 As New personne("Jean", "Dupont", 30)

        ' visualização p1
        Console.Out.Write("Paramètre effectif avant modification : ")
        p1.identifie()

        ' alteração p1
        modifie(p1)

        ' visualização p1
        Console.Out.Write("Paramètre effectif après modification : ")
        p1.identifie()
    End Sub

    Sub modifie(ByVal P As personne)
        ' visualização de pessoa P
        Console.Out.Write("Paramètre formel avant modification : ")
        P.identifie()
        ' alteração P
        P.prenom = "Sylvie"
        P.nom = "Vartan"
        P.age = 52
        ' visualização de P
        Console.Out.Write("Paramètre formel après modification : ")
        P.identifie()
    End Sub
End Module

Os resultados obtidos são os seguintes:

Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52

Verifica-se que apenas foi criado um objeto: o da pessoa p1 do procedimento Main e que o objeto foi efetivamente alterado pela função modifie.

3.1.16. Uma tabela de pessoas

Um objeto é um dado como qualquer outro e, como tal, vários objetos podem ser agrupados numa tabela:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        ' uma tabela de pessoas
        Dim amis(2) As personne
        amis(0) = New personne("Jean", "Dupont", 30)
        amis(1) = New personne("Sylvie", "Vartan", 52)
        amis(2) = New personne("Neil", "Armstrong", 66)
        ' visualização
        Console.Out.WriteLine("----------------")
        Dim i As Integer
        For i = 0 To amis.Length - 1
            amis(i).identifie()
        Next i
    End Sub
End Module

A instrução Dim amigos(2) As pessoa cria uma matriz de 3 elementos do tipo personne. Estes 3 elementos são aqui inicializados com os valores nothing e c.a.d, pelo que não referenciam nenhum objeto. Mais uma vez, por conveniência, fala-se de um tabuleiro de objetos, quando na verdade trata-se apenas de um tabuleiro de referências a objetos. A criação do tabuleiro de objetos, que é ele próprio um objeto, não cria nenhum objeto do tipo dos seus elementos: isso tem de ser feito posteriormente. Obtêm-se os seguintes resultados:

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

3.2. A herança por exemplo

3.2.1. Noções gerais

Abordamos aqui o conceito de herança. O objetivo da herança é «personalizar» uma classe existente para que esta satisfaça as nossas necessidades. Suponhamos que queremos criar uma classe enseignant: um professor é uma pessoa específica. Tem atributos que outra pessoa não terá: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome próprio, apelido e idade. Um professor faz, portanto, parte integrante da classe personne, mas possui atributos adicionais. Em vez de criar uma classe enseignant a partir do zero, preferimos aproveitar o que já existe na classe personne e adaptá-la às características específicas dos professores. É o conceito de herança que nos permite fazer isso. Para indicar que a classe enseignant herda as propriedades da classe personne, escrever-se-á:


Public Class enseignant
    Inherits personne

Note-se a sintaxe específica em duas linhas. A classe personne é designada por classe pai (ou mãe) e a classe enseignant por classe derivada (ou filha). Um objeto enseignant possui todas as características de um objeto personne: tem os mesmos atributos e os mesmos métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha: basta indicar os atributos e métodos adicionados pela classe filha. Suponhamos que a classe personne esteja definida da seguinte forma:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' classe «pessoa»
Public Class personne

    ' atributos da classe
    Private Shared _nbPersonnes As Long = 0

    ' atributos de instância
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer


    ' construtores
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' mais uma pessoa
        _nbPersonnes += 1
        ' construção
        Me._prenom = P
        Me._nom = N
        Me._age = age
        ' acompanhamento
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub


    Public Sub New(ByVal P As personne)
        ' mais uma pessoa
        _nbPersonnes += 1
        ' construção
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
        ' acompanhamento
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub

    ' propriedade de classe
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

    ' propriedades de instância
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property

    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property

    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' idade válida?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property

    Public ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property
End Class

O método identifie foi substituído pela propriedade identité, de leitura única, que identifica a pessoa. Criamos uma classe enseignant que herda da classe personne:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Public Class enseignant
    Inherits personne
    ' atributos
    Private _section As Integer

    ' construtor
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' acompanhamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' propriedade da secção
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
End Class 

A classe enseignant adiciona aos métodos e atributos da classe personne:

  • um atributo section, que corresponde ao número da secção a que o professor pertence no corpo docente (basicamente, uma secção por disciplina)
  • um novo construtor que permite inicializar todos os atributos de um professor

A declaração


Public Class enseignant
    Inherits personne

indica que a classe enseignant deriva da classe personne.

3.2.2. Criação de um objeto «professor»

O construtor da classe enseignant é o seguinte:


    ' construtor
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
         ' acompanhamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

A declaração


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)

indica que o construtor recebe quatro parâmetros: P, N, age e section. Tem de passar três deles, (P,N,age), para a sua classe base, neste caso a classe personne. Sabe-se que esta classe tem um construtor pessoa(string, string, int) que permitirá criar uma pessoa com os parâmetros passados (P,N,age). A classe [enseignant] passa os parâmetros (P, N, idade) à sua classe base da seguinte forma:


        MyBase.New(P, N, age)

Assim que a construção da classe base estiver concluída, a construção do objeto enseignant prossegue com a execução do corpo do construtor:


        Me._section = section

Em resumo, o construtor de uma classe derivada:

  • passa à sua classe base os parâmetros de que esta necessita para ser construída
  • utiliza os restantes parâmetros para inicializar os seus próprios atributos

Poderíamos ter preferido escrever:


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
        Me._section = section
        ' acompanhamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

Isso é impossível. A classe personne declarou como privados (private) os seus três campos _prenom, _nom e _age. Apenas os objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos derivados como neste caso, têm de recorrer a métodos públicos para aceder a esses campos. A situação teria sido diferente se a classe personne tivesse declarado os três campos como protegidos (protected): nesse caso, permitiria que as classes derivadas tivessem acesso direto aos três campos. No nosso exemplo, utilizar o construtor da classe pai era, portanto, a solução correta e é o método habitual: ao construir um objeto filho, chama-se primeiro o construtor do objeto pai e, em seguida, completam-se as inicializações específicas do objeto filho (section no nosso exemplo).

Vamos compilar as classes personne e enseignant em assemblies:

dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
25/02/2004  10:08                1 828 personne.vb
25/02/2004  10:11                  675 enseignant.vb
25/02/2004  10:12                  223 test.vb
25/02/2004  10:16                4 096 personne.dll
25/02/2004  10:16                3 584 enseignant.dll

Note-se que, para compilar a classe filha enseignant, foi necessário referenciar o ficheiro personne.dll, que contém a classe personne. Vamos tentar um primeiro programa de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

Este programa limita-se a criar um objeto enseignant (new) e a identificá-lo. A classe enseignant não possui o método identité, mas a sua classe pai possui um, que, além disso, é público: por herança, este torna-se um método público da classe enseignant. Os resultados obtidos são os seguintes:

dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)

Verifica-se que:

  • um objeto personne foi criado antes do objeto enseignant
  • a identidade obtida é a do objeto personne

3.2.3. Sobrecarga de um método ou de uma propriedade

No exemplo anterior, obtivemos a identidade da parte personne do professor, mas faltam algumas informações específicas da classe enseignant (a secção). Por isso, temos de definir uma propriedade que permita identificar o professor:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Public Class enseignant
    Inherits personne
    ' atributos
    Private _section As Integer

    ' construtor
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' acompanhamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' propriedade de secção
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property

    ' sobrecarregamento da propriedade de identidade
    Public Shadows ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property
End Class

O método identite da classe enseignant baseia-se no método identite da sua classe-mãe (MyBase.identite) para apresentar a sua parte «personne» e, em seguida, completa com o campo _section, que é específico da classe enseignant. Note-se a declaração da propriedade identite:


    Public Shadows ReadOnly Property identite() As String

que indica que a propriedade «identite» «oculta» o método com o mesmo nome que poderia existir na classe pai. Consideremos um objeto enseignant, denotado por E. Este objeto contém, no seu interior, um objeto personne:

A propriedade «identity» está definida tanto na classe enseignant como na sua classe pai personne. Na classe filha enseignant, a propriedade identite deve ser precedida pela palavra-chave shadows para indicar que se está a redefinir uma nova propriedade identite para a classe enseignant.


    Public Shadows ReadOnly Property identite() As String

A classe enseignant dispõe agora de duas propriedades identite:

  • a herdada da classe pai personne
  • a sua própria

Se E for um objeto enseignant, E.identite designa o método identite da classe enseignant. Diz-se que a propriedade identite da classe-mãe é «redefinida» pela propriedade identite da classe-filha. De um modo geral, se O for um objeto e M um método, para executar o método O.M, o sistema procura um método M na seguinte ordem:

  • na classe do objeto O
  • na sua classe-mãe, se tiver uma
  • na classe-mãe da sua classe-mãe, se esta existir
  • etc…

A herança permite, portanto, redefinir na classe filha métodos/propriedades com o mesmo nome que existem na classe pai. É isso que permite adaptar a classe filha às suas próprias necessidades. Associada ao polimorfismo, que veremos um pouco mais adiante, a sobrecarga de métodos/propriedades é o principal interesse da herança. Consideremos o mesmo exemplo anterior:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

Os resultados obtidos, desta vez, são os seguintes:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)

3.2.4. O polimorfismo

Consideremos uma hierarquia de classes: C0 C1 C2 … Cn, em que Ci Cj, indica que a classe Cj é derivada da classe Ci. Isto implica que a classe Cj possui todas as características da classe Ci, além de outras. Sejam Oi objetos do tipo Ci. É válido escrever:

    Oi=Oj avec j>i

Com efeito, por herança, a classe Cj possui todas as características da classe Ci, além de outras. Assim, um objeto Oj do tipo Cj contém, no seu interior, um objeto do tipo Ci. A operação

Oi=Oj

faz com que Oi seja uma referência ao objeto de tipo Ci contido no objeto Oj.

O facto de uma variável Oi da classe Ci poder, na verdade, referenciar não só um objeto da classe Ci, mas também qualquer objeto derivado da classe Ci, denomina-se polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos. Vejamos um exemplo e consideremos a seguinte função, independente de qualquer classe:


    Sub affiche(ByVal p As personne)

Também poderíamos escrever

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

ou

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

Neste último caso, o parâmetro formal do tipo personne da função affiche receberá um valor do tipo enseignant. Como o tipo enseignant deriva do tipo personne, isso é válido.

3.2.5. Redefinição e polimorfismo

Vamos completar a nossa função affiche:


    Sub affiche(ByVal p As personne)
         ' exibe identidade de p
        Console.Out.WriteLine(p.identite)
    End Sub    

O método p.identite devolve uma cadeia de caracteres que identifica o objeto personne.. O que acontece, no caso do nosso exemplo anterior, com um objeto enseignant:


        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)

Vejamos o exemplo seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        ' um professor
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' uma pessoa
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' exibe
    Sub affiche(ByVal p As personne)
        ' afixa a identidade de p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

Os resultados obtidos são os seguintes:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

A execução mostra que a instrução p.identite executou, em cada ocasião, a propriedade identite de uma personne, a pessoa contida na enseignant e, em seguida, a própria personne. Não se adaptou ao objeto efetivamente passado como parâmetro à affiche. Teríamos preferido ter a identidade completa do enseignant e. Para tal, teria sido necessário que a notação p.identite fizesse referência à propriedade identite do objeto efetivamente apontado por p, em vez da propriedade identite da parte «personne» do objeto efetivamente apontado por p. É possível obter este resultado declarando identite como uma propriedade redefiniável (overridable) na classe base personne:


    Public Overridable ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property

A palavra-chave «overridable» torna identite uma propriedade redefiniável ou virtual. Esta palavra-chave também se pode aplicar a métodos. As classes derivadas que redefinirem uma propriedade ou método virtual devem, então, utilizar a palavra-chave «overrides» em vez de «shadows» para qualificar a sua propriedade/método redefinido. Assim, na classe enseignant, a propriedade identite é definida da seguinte forma:


    ' sobreposição da propriedade de identidade
    Public Overrides ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property

O programa de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        ' um professor
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' uma pessoa
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' exibe
    Sub affiche(ByVal p As personne)
        ' cartaz de identificação de p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

produz então os seguintes resultados:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

Desta vez, conseguimos obter a identidade completa do professor. Vamos agora redefinir um método em vez de uma propriedade. A classe object é a classe «pai» de todas as classes VB.NET. Assim, quando escrevemos:

    public class personne

estamos, implicitamente, a escrever:

    public class personne 
         inherits object

A classe object define um método virtual ToString:

Image

O método ToString devolve o nome da classe à qual o objeto pertence, conforme ilustrado no exemplo seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test2
    Sub Main()
        ' um professor
        Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
        ' uma pessoa
        Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
    End Sub
End Module

Os resultados obtidos são os seguintes:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne

Note-se que, embora não tenhamos redefinido o método ToString nas classes personne e enseignant, é possível constatar que o método ToString da classe object consegue, mesmo assim, apresentar o nome real da classe do objeto. Vamos redefinir o método ToString nas classes personne e enseignant:


    ' ToString
    Public Overrides Function ToString() As String
        ' atribui-se a propriedade «identidade»
        Return identite
    End Function

A definição é a mesma nas duas classes. Consideremos o seguinte programa de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        ' um professor
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' uma pessoa
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub

    ' cartaz
    Sub affiche(ByVal p As personne)
        ' cartaz de identificação de p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

Os resultados da execução são os seguintes:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

3.3. Definir um indexador para uma classe

Consideremos a classe [ArrayList] predefinida na plataforma .NET. Esta classe permite armazenar objetos numa lista. Pertence ao espaço de nomes [System.Collections]. No exemplo seguinte, utilizamos esta classe para armazenar uma lista de pessoas (em sentido lato):


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System
Imports System.Collections

' classe listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList

    ' para adicionar uma pessoa à lista
    Public Overloads Sub Add(ByVal p As personne)
        MyBase.Add(p)
    End Sub

    ' um indexador
    Default Public Shadows Property Item(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property

    ' outro indexador
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' procura-se a pessoa com o nome N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property

    ' toString
    Public Overrides Function ToString() As String
        ' retorna (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' percorre-se a matriz dinâmica
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' último elemento
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

Vamos apresentar algumas informações sobre determinadas propriedades e métodos da classe [ArrayList]:

Count
atributo que indica o número de elementos da lista
Add(Object)
método que permite adicionar um objeto à lista
Item(Integer i)
método que devolve o elemento i da lista

Reparamos que, para obter o elemento n.º i da lista, não escrevemos [liste.Item(i)], mas sim diretamente [liste(i)], o que, à primeira vista, parece estar errado. No entanto, isto é possível porque a classe [ArrayList] define uma propriedade por predefinição [Item], de acordo com uma sintaxe semelhante à seguinte:


    Default Public Property Item(ByVal i As Integer) As Object
        Get
...
        End Get
        Set(ByVal Value As personne)
...
        End Set    
    End Property

Quando o compilador encontra a notação [liste(i)], verifica se a classe [ArrayList] definiu uma propriedade com a seguinte assinatura:


    Default Public Property Proc(ByVal var As Integer) As Type

Aqui, encontrará o procedimento [Item]. Traduzirá então a notação [liste(i)] para [liste.Item(i)]. A propriedade [Item] é designada como a propriedade indexada por predefinição da classe [ArrayList]. A execução do programa anterior produz os seguintes resultados:

dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:34             3 584 lstpersonnes1.exe
12/03/2004  13:39               661 lstpersonnes1.vb
11/03/2004  18:26             4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)

Vamos criar uma classe chamada [listeDePersonnes], que será uma lista de pessoas, ou seja, uma lista específica que parece natural derivar da classe [ArrayList]:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System
Imports System.Collections

' classe listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList

    ' para adicionar uma pessoa à lista
    Public Shadows Sub Add(ByVal p As Object)
        ' é necessário que p seja uma pessoa
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub

    ' um indexador
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property

    ' toString
    Public Overrides Function ToString() As String
        ' retorna (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' percorre-se a matriz dinâmica
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' último elemento
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

A classe apresenta os seguintes métodos e propriedades:

ToString
retorna uma cadeia de caracteres que «representa» o conteúdo da lista
Add(personne)
método que permite adicionar uma pessoa à lista
Item(Integer i)
propriedade indexada por predefinição que indica a pessoa i da lista

Consideremos as novidades:

Foi criado um novo método [Add].


     ' para adicionar uma pessoa à lista
    Public Shadows Sub Add(ByVal p As Object)
        ' é necessário que p seja uma pessoa
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub

Já existe um na classe pai [ArrayList] com a mesma assinatura, daí a palavra-chave [Shadows] para indicar que o novo procedimento substitui o da classe pai. O método [Add] da classe filha verifica se o objeto adicionado é efetivamente do tipo [personne] ou derivado da função [TypeOf]. Se não for esse o caso, é lançada uma exceção com a instrução [Throw]. Se o objeto adicionado for efetivamente do tipo [personne], o método [Add] da classe base é utilizado para efetuar a adição.

É criada uma propriedade indexada:


    ' um indexador
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property

Já existe uma propriedade por predefinição denominada [Item] na classe base com a mesma assinatura. Por isso, é necessário utilizar a palavra-chave [Shadows] para indicar que a nova propriedade indexada [Index] irá «ocultar» a da classe base [Item]. Note-se que isto é válido mesmo que as duas propriedades não tenham o mesmo nome. A propriedade [Index] permite referenciar a pessoa n.º i da lista. Baseia-se na propriedade [Item] da classe base para aceder ao elemento n.º i do objeto subjacente [ArrayList]. São efetuadas alterações de tipo para ter em conta que a propriedade [Item] trabalha com elementos do tipo [Object], enquanto a propriedade [Index] trabalha com elementos do tipo [personne].

Por fim, redefinimos (Overrides) o método [ToString] da classe [ArrayList]:


    ' toString
    Public Overrides Function ToString() As String
        ' retorna (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' percorre-se a matriz dinâmica
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
         ' último elemento
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function

Este método devolve uma cadeia de caracteres com o formato «(e1,e2,...,en)», em que ei são os elementos da lista. Note-se a notação [Me(i)], que designa o elemento n.º i do objeto atual [Me]. É utilizada a propriedade indexada por predefinição. Assim, [Me(i)] é equivalente a [Me.Index(i)].

O código da classe é colocado no ficheiro [lstpersonnes2.vb] e compilado:

dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll

É criado um programa de teste:


' opções
Option Explicit On 
Option Strict On

' espaços de nomes
Imports System
Imports System.Collections

Module test
    Sub Main()
        ' criação de uma lista vazia de pessoas
        Dim liste As listeDePersonnes = New listeDePersonnes
        ' criação de pessoas
        Dim p1 As personne = New personne("paul", "chenou", 31)
        Dim p2 As personne = New personne("nicole", "chenou", 11)
        Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
        ' preenchimento da lista
        liste.Add(p1)
        liste.Add(p2)
        liste.Add(e1)
        ' visualização da lista
        Console.Out.WriteLine(liste.ToString)
        ' adição de um objeto diferente de pessoa
        Try
            liste.Add(4)
        Catch e As Exception
            Console.Error.WriteLine(e.Message)
        End Try
    End Sub
End Module

É compilado:

dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
12/03/2004  15:50             3 584 test.exe
12/03/2004  15:49               623 test.vb

e, em seguida, executado:

dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne

Poderíamos querer escrever

dim p as personne=l("nom")

onde l seria do tipo [listeDePersonnes]. Neste caso, pretende-se indexar a lista l não por um número de elemento, mas por um nome de pessoa. Para tal, define-se uma nova propriedade indexada por predefinição:


    ' outro indexador
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' procura-se a pessoa com o nome N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property

A primeira linha


    Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer

indica que estamos a criar novamente uma propriedade indexada por predefinição. Todas as propriedades por predefinição devem ter o mesmo nome, neste caso [Index]. A nova propriedade [Index] indexa a classe listeDePersonnes por uma cadeia de caracteres N. O resultado de listeDePersonnes(N) é um número inteiro. Este número inteiro corresponderá à posição na lista da pessoa com o nome N ou a -1, caso essa pessoa não conste da lista. Define-se apenas a propriedade get, impedindo assim a escrita de listeDePersonnes ("nom")=valeur, que teria exigido a definição da propriedade set). Daí a palavra-chave [ReadOnly]. A palavra-chave [Shadows] é necessária para ocultar a propriedade por predefinição da classe base (embora esta não tenha a mesma assinatura).

No corpo do get, percorre-se a lista de pessoas à procura do nome N passado como parâmetro. Se for encontrado na posição i, devolve-se i; caso contrário, devolve-se -1.

Um novo programa de teste poderia ser o seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' página de teste
Module test
    Sub Main()
        ' uma lista de pessoas
        Dim l As New listeDePersonnes
        ' adição de pessoas
        l.Add(New personne("jean", "dumornet", 10))
        l.Add(New personne("pauline", "duchemin", 12))
        ' visualização
        Console.Out.WriteLine(("l=" + l.ToString))
        l.Add(New personne("jacques", "tartifume", 27))
        Console.Out.WriteLine(("l=" + l.ToString))
        ' alteração do elemento 1
        l(1) = New personne("sylvie", "cachan", 5)
        ' visualização do elemento 1
        Console.Out.WriteLine(("l[1]=" + l(1).ToString))
        ' visualização da lista l
        Console.Out.WriteLine(("l=" + l.ToString))
        ' pesquisa de pessoas
        Dim noms() As String = New [String]() {"cachan", "inconnu"}
        Dim i As Integer
        For i = 0 To noms.Length - 1
            Dim inom As Integer = l(noms(i))
            If inom <> -1 Then
                Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
            Else
                Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
            End If
        Next i
    End Sub    
End Module

A execução produz os seguintes resultados:

personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas

3.4. As estruturas

A estrutura VB.NET deriva diretamente da estrutura da linguagem C e é muito semelhante a uma classe. Uma estrutura é definida da seguinte forma:


Structure spersonne
' atributos
    ...
' propriedades
...
' construtores
...
' métodos
End Structure

Apesar da semelhança na sintaxe, existem diferenças importantes entre classes e estruturas. O conceito de herança, por exemplo, não existe nas estruturas. Se criarmos uma classe que não deva ser derivada, quais são as diferenças entre estrutura e classe que nos ajudarão a escolher entre as duas? Vamos recorrer ao exemplo seguinte para descobrir:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' estrutura spersonne
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure

' classe pessoa
Class cpersonne
    Public nom As String
    Public age As Integer
End Class

' um módulo de teste
Public Module test
    Sub Main()
        ' uma pessoa p1
        Dim sp1 As spersonne
        sp1.nom = "paul"
        sp1.age = 10
        Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
        ' uma pessoa p2
        Dim sp2 As spersonne = sp1
        Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
        ' sp2 é alterado
        sp2.nom = "nicole"
        sp2.age = 30
        ' verificação de sp1 e sp2
        Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
        Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))

        ' uma pessoa cp1
        Dim cp1 As New cpersonne
        cp1.nom = "paul"
        cp1.age = 10
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        ' uma pessoa cp2 P2
        Dim cp2 As cpersonne = cp1
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
        ' P2 foi alterado
        cp2.nom = "nicole"
        cp2.age = 30
        ' verificação de P1 e P2
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
    End Sub
End Module

Se executarmos este programa, obtemos os seguintes resultados:

sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)

Enquanto nas páginas anteriores deste capítulo se utilizava uma classe personne, agora utilizamos uma estrutura spersonne:


' estrutura da pessoa
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure

A declaração


        Dim sp1 As spersonne

cria uma estrutura (nome, idade) e o valor de sp1 é essa própria estrutura.

A declaração


        Dim cp1 As New cpersonne

cria um objeto [cpersonne] (grosso modo, o equivalente à nossa estrutura) e cp1 é, então, a morada (a referência) desse objeto.

Resumindo

  • no caso da estrutura, o valor de sp1 é a própria estrutura
  • no caso da classe, o valor de p1 é o endereço do objeto criado

Quando, no programa, se escreve


        Dim sp2 As spersonne = sp1

é criada uma nova estrutura (nome, idade) e inicializada com o valor de p1, ou seja, a própria estrutura.

A estrutura de sp1 é, portanto, duplicada em sp2. Trata-se de uma cópia de valores.

A instrução


        Dim cp2 As cpersonne = cp1

funciona de forma diferente. O valor de cp1 é copiado para cp2, mas como esse valor é, na verdade, o endereço do objeto, este não é duplicado. Tem simplesmente duas referências a ele:

No caso da estrutura, se alterarmos o valor de sp2, não alteramos o valor de sp1, como demonstra o programa. No caso do objeto, se alterarmos o objeto apontado por cp2, aquele apontado por cp1 é alterado, uma vez que se trata do mesmo. É isso que os resultados do programa também demonstram.

Conclui-se, portanto, destas explicações que:

  • o valor de uma variável do tipo estrutura é a própria estrutura
  • o valor de uma variável do tipo objeto é o endereço do objeto apontado

Uma vez compreendida esta diferença fundamental, a estrutura revela-se muito semelhante à classe, como demonstra o seguinte exemplo:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' estrutura de pessoa
Structure personne
    ' atributos
    Private _nom As String
    Private _age As Integer

    ' propriedades
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = value
        End Set
    End Property

    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            _age = value
        End Set
    End Property

    ' Construtor
    Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
        _nom = NOM
        _age = AGE
    End Sub    'New

    ' TOSTRING
    Public Overrides Function ToString() As String
        Return "personne(" & nom & "," & age & ")"
    End Function
End Structure

' um módulo de teste
Module test
    Sub Main()
        ' uma pessoa p1
        Dim p1 As New personne("paul", 10)
        Console.Out.WriteLine(("p1=" & p1.ToString))
        ' uma pessoa p2
        Dim p2 As personne = p1
        Console.Out.WriteLine(("p2=" & p2.ToString))
        ' p2 é alterado
        p2.nom = "nicole"
        p2.age = 30
        ' verificação de p1 e p2
        Console.Out.WriteLine(("p1=" & p1.ToString))
        Console.Out.WriteLine(("p2=" & p2.ToString))
    End Sub
End Module

Obtêm-se os seguintes resultados de execução:

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

A única diferença notável aqui entre a estrutura e a classe é que, com uma classe, os objetos p1 e p2 teriam o mesmo valor no final do programa, ou seja, o valor de p2.

3.5. As interfaces

Uma interface é um conjunto de protótipos de métodos ou propriedades que constitui um contrato. Uma classe que decide implementar uma interface compromete-se a fornecer uma implementação de todos os métodos definidos na interface. É o compilador que verifica essa implementação. Eis, por exemplo, a definição da interface Istats:

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 [moyenne] e [écartType] devem ser definidos na classe C. Consideremos o código seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' estrutura
Public Structure élève
    Public _nom As String
    Public _note As Double

    ' construtor
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub
End Structure

' classe notas
Public Class notes
    ' atributo
    Protected _matière As String
    Protected _élèves() As élève

    ' fabricante
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        ' registo de alunos e disciplinas
        Me._matière = MATIERE
        Me._élèves = ELEVES
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Dim valeur As String = "matière=" + _matière + ", notes=("
        Dim i As Integer
        ' concatenamos todas as notas
        For i = 0 To (_élèves.Length - 1) - 1
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
        Next i
        'última nota
        If _élèves.Length <> 0 Then
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
        End If
        valeur += ")"
        ' fim
        Return valeur
    End Function
End Class

A turma notes reúne as notas de uma turma numa disciplina:


Public Class notes
     ' atributo
    Protected _matière As String
    Protected _élèves() As élève

Os atributos são declarados como protected para que sejam acessíveis a partir de uma classe derivada. O tipo élève é uma estrutura que armazena o nome do aluno e a sua nota na disciplina:


Public Structure élève
    Public _nom As String
    Public _note As Double

     ' construtor
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub    
End Structure

Decidimos derivar esta classe notes numa classe notesStats que teria dois atributos adicionais: a média e o desvio-padrão das notas:


Public Class notesStats
    Inherits notes
    Implements Istats

     ' atributos
    Private _moyenne As Double
    Private _écartType As Double

A classe notesStats implementa a seguinte interface Istats:

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 moyenne e écartType com a assinatura indicada na interface Istats. A classe notesStats é a seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Public Class notesStats
    Inherits notes
    Implements Istats

    ' atributos
    Private _moyenne As Double
    Private _écartType As Double

    ' construtor
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        MyBase.New(MATIERE, ELEVES)
        ' cálculo da média das notas
        Dim somme As Double = 0
        Dim i As Integer
        For i = 0 To ELEVES.Length - 1
            somme += ELEVES(i)._note
        Next i
        If ELEVES.Length <> 0 Then
            _moyenne = somme / ELEVES.Length
        Else
            _moyenne = -1
        End If
        ' desvio-padrão
        Dim carrés As Double = 0
        For i = 0 To ELEVES.Length - 1
            carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
        Next i
        If ELEVES.Length <> 0 Then
            _écartType = Math.Sqrt((carrés / ELEVES.Length))
        Else
            _écartType = -1
        End If
    End Sub

    ' ToString
    Public Overrides Function ToString() As String
        Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
    End Function    'ToString

    ' métodos da interface Istats
    Public Function moyenne() As Double Implements Istats.moyenne
        ' calcula a média das notas
        Return _moyenne
    End Function

    Public Function écartType() As Double Implements Istats.écartType
        ' calcula o desvio-padrão
        Return _écartType
    End Function
End Class

A média _moyenne e o desvio-padrão _ecartType são calculados logo na criação do objeto. Assim, os métodos moyenne e écartType apenas têm de devolver o valor dos atributos _moyenne e _ecartType. Ambos os métodos devolvem -1 se a tabela de alunos estiver vazia.

A seguinte classe de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Module test
    Sub Main()
        ' alunos e notas
        Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
        ' que se guardam num objeto «notas»
        Dim anglais As New notes("anglais", ELEVES)
        ' e que são apresentadas
        Console.Out.WriteLine((anglais.ToString))
        ' o mesmo com média e desvio-padrão
        anglais = New notesStats("anglais", ELEVES)
        Console.Out.WriteLine((anglais.ToString))
    End Sub
End Module

produz os seguintes resultados:

matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545

A classe notesStats poderia muito bem ter implementado os métodos moyenne e écartType por si própria, sem indicar que implementava a interface Istats. Qual é a utilidade das interfaces? É a seguinte: uma função pode aceitar como parâmetro formal um dado com o tipo de uma interface I. Qualquer objeto de uma classe C que implemente a interface I poderá, então, ser um parâmetro efetivo dessa função. Consideremos o seguinte exemplo:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' uma interface Iexample
Public Interface Iexemple
    Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
    Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface

' uma 1.ª classe
Public Class classe1
    Implements Iexemple

    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 10
    End Function

    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 20
    End Function
End Class

'uma segunda classe
Public Class classe2
    Implements Iexemple

    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 100
    End Function

    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 200
    End Function
End Class

A interface Iexemple define dois métodos: ajouter e soustraire. As classes classe1 e classe2 implementam esta interface. Note-se que estas classes não fazem mais nada, para simplificar o exemplo. Consideremos agora o seguinte exemplo:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' classe de teste
Module test

    'calcular
    Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
        Console.Out.WriteLine(inter.ajouter(i, j))
        Console.Out.WriteLine(inter.soustraire(i, j))
    End Sub

    ' a função Main
    Sub Main()
        ' criação de dois objetos «classe1» e «classe2»
        Dim c1 As New classe1
        Dim c2 As New classe2
        ' chamadas à função estática «calcular»
        calculer(4, 3, c1)
        calculer(14, 13, c2)
    End Sub
End Module

A função calculer aceita como parâmetro um elemento do tipo Iexemple. Assim, poderá receber para este parâmetro tanto um objeto do tipo classe1 como do tipo classe2. É isso que se faz no procedimento Main, com os seguintes resultados:

17
21
127
201

Vemos, portanto, que se trata de uma propriedade semelhante ao polimorfismo observado nas classes. Se um conjunto de classes Ci não ligadas entre si por herança (pelo que não é possível utilizar o polimorfismo de herança) apresentar um conjunto de métodos com a mesma assinatura, pode ser interessante agrupar esses métodos numa interface I, da qual todas as classes em questão herdariam. As instâncias dessas classes Ci podem então ser utilizadas como parâmetros de funções que aceitem um parâmetro do tipo I, c.a.d. Funções que utilizam apenas os métodos dos objetos Ci definidos na interface I e não os atributos e métodos específicos das diferentes classes Ci. Por fim, note-se que a herança de interfaces pode ser múltipla, c.a.d. podendo escrever-se


Public Class classe
    Implements I1,I2,...

onde os Ij são interfaces.

3.6. Os espaços de nomes

Para escrever uma linha no ecrã, utilizamos a instrução

Console.Out.WriteLine(...)

Se analisarmos a definição da classe Console


Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)

descobrimos que faz parte do espaço de nomes System. Isto significa que a classe Console deveria ser designada por System.Console e que, na verdade, deveríamos escrever:

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

Isto evita-se utilizando uma cláusula imports:

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

Diz-se que se importa o espaço de nomes System com a cláusula imports. Quando o compilador encontrar o nome de uma classe (neste caso, Console), irá procurá-la nos diferentes espaços de nomes importados pelas cláusulas imports. Aqui, encontrará a classe Console no espaço de nomes System. Observemos agora a segunda informação associada à classe Console:


Assembly: Mscorlib (in Mscorlib.dll)

Esta linha indica em que «assembly» se encontra a definição da classe Console. Quando se compila fora do Visual Studio.NET e é necessário indicar as referências das diferentes dll que contêm as classes a utilizar, esta informação pode revelar-se útil. Recorde-se que, para referenciar os dll necessários à compilação de uma classe, escreve-se:

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

Ao criar uma classe, é possível criá-la dentro de um espaço de nomes. O objetivo destes espaços de nomes é evitar conflitos de nomes entre classes quando estas são comercializadas, por exemplo. Consideremos duas empresas, E1 e E2, que distribuem classes empacotadas, respetivamente, nos dll, E1.dll e E2.dll. Suponhamos que um cliente C adquira estes dois conjuntos de classes, nos quais ambas as empresas definiram uma classe personne. O cliente C compila um programa da seguinte forma:

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

Se o código-fonte prog.vb utilizar a classe personne, o compilador não saberá se deve utilizar a classe personne da E1.dll ou a da E2.dll. O compilador irá sinalizar um erro. Se a empresa E1 tiver o cuidado de criar as suas classes num espaço de nomes denominado E1 e a empresa E2 num espaço de nomes denominado E2, as duas classes personne passarão a chamar-se E1.personne e E2.personne. O cliente deverá utilizar nas suas classes ou E1.personne, ou E2.personne, mas não personne. O espaço de nomes permite eliminar a ambiguidade. Para criar uma classe num espaço de nomes, escreve-se:


Namespace istia.st
    Public Class personne
        ' definição da classe
...
  end Class
end Namespace

A título de exemplo, vamos criar num espaço de nomes a nossa classe personne, analisada anteriormente. Escolheremos istia.st como espaço de nomes. A classe personne passa a ser:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

' criação do espaço de nomes istia.st
Namespace istia.st
    Public Class personne
        ' atributos
        Private prenom As String
        Private nom As String
        Private age As Integer

        ' método
        Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
            Me.prenom = P
            Me.nom = N
            Me.age = age
        End Sub

        ' método
        Public Sub identifie()
            Console.Out.WriteLine((prenom & "," & nom & "," & age))
        End Sub
    End Class
End Namespace

Esta classe é compilada em personne.dll:

dos>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 personne numa classe de teste:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System
Imports istia.st

' página de teste
Public Module test
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End Module

Para evitar escrever


        Dim p1 As New istia.st.personne

importámos o espaço de nomes istia.st com uma cláusula imports:


Imports istia.st

Agora, vamos compilar o programa de teste:

dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:05               254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:08             3 072 test.exe
12/03/2004  18:05               254 test.vb

Isto gera um ficheiro test.exe que, quando executado, apresenta os seguintes resultados:

Jean,Dupont,30

3.7. O exemplo IMPOTS

Retomamos o cálculo do imposto já analisado no capítulo anterior e processamo-lo utilizando uma classe. Recorde-se o problema:

Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:

  • calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
  • se tiver pelo menos três filhos, tem mais meia quota
  • calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
  • calcula-se o seu coeficiente familiar QF = R / nbParts
  • calcula-se o seu imposto I. Consideremos a seguinte tabela:
12620,0
0
0
13 190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31 810
0,2
3309,5
39 970
0,25
4900
48 360
0,3
6898,5
55790
0,35
9316,5
92 970
0,4
12106
127 860
0,45
16 754,5
151 250
0,50
23 147,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 23000, encontrar-se-á a linha

    24740        0.15        2072.5

O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:

    0                0.65        49062

o que dá o imposto I = 0,65*R - 49062*nbParts.

A classe «impost» será definida da seguinte forma:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Public Class impot
    ' os dados necessários para o cálculo do imposto
    ' provêm de uma fonte externa
    Private limites(), coeffR(), coeffN() As Decimal

    ' fabricante
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' verifica-se se as 3 tabelas têm o mesmo tamanho
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' está tudo bem
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub

    ' cálculo do imposto
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
        ' cálculo do número de quotas
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' cálculo do rendimento tributável e do quociente familiar
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' cálculo do imposto
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

É criado um objeto «imposto» com os dados que permitem o cálculo do imposto de um contribuinte. Esta é a parte estável do objeto. Uma vez criado este objeto, é possível chamar repetidamente o seu método «calcular», que calcula o imposto do contribuinte com base no seu estado civil (casado ou solteiro), no número de filhos e no seu salário anual. Um programa de teste poderia ser o seguinte:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System
Imports Microsoft.VisualBasic

Module test
    Sub Main()
        ' programa interativo de cálculo do imposto
        ' o utilizador introduz três dados através do teclado: casado nbEnfants salário
        ' o programa apresenta então o imposto a pagar
        Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"

        ' tabelas de dados necessárias para o cálculo do imposto
        Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
        Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
        Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}

        ' criação de um objeto de imposto
        Dim objImpôt As impot = Nothing
        Try
            objImpôt = New impot(limites, coeffR, coeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(1)
        End Try
        ' loop infinito
        Dim marié As String
        Dim nbEnfants As Integer
        Dim salaire As Long
        While True
            ' são solicitados os parâmetros para o cálculo do imposto
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
            ' há algo a fazer?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' verificação do número de argumentos na linha introduzida
            Dim erreur As Boolean = False
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe)
                erreur = True
            End If
            ' verificação da validade dos parâmetros
            If Not erreur Then
                ' casado
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    erreur = True
                End If
                ' nbEnfants
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
                ' salário
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
            End If
            ' se os parâmetros estiverem corretos - calcula-se o imposto
            If Not erreur Then
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
            Else
                Console.Error.WriteLine(syntaxe)
            End If
        End While
    End Sub
End Module

Eis um exemplo de execução do programa anterior:

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 :