Skip to content

3. Classes, estruturas, interfaces

3.1. O objeto no exemplo

3.1.1. Noções gerais

Abordaremos agora, por meio de exemplos, a programação orientada a objetos. Um objeto é uma entidade que contém dados que definem seu estado (chamados de propriedades) e funções (chamadas de métodos). Um objeto é criado de acordo com um modelo chamado de 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 C1 anterior, é 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 suas propriedades pi, possuindo, assim, cada um um estado próprio. Por analogia, a declaração

    dim i, j as integer

cria dois objetos (o termo está incorreto aqui) do tipo (classe) Integer. Sua única propriedade é seu valor. Se O1 é um objeto do tipo C1, O1.p1 designa a propriedade p1 de O1 e O1.m1 designa 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. Ao criarmos variáveis desse tipo, elas serão chamadas de objetos ou instâncias de classes. Uma classe é, portanto, um modelo a partir do qual os objetos são construídos. Os membros ou campos de uma classe podem ser dados (atributos), métodos (funções) ou propriedades. As propriedades são métodos específicos que servem para obter ou definir o valor dos atributos do objeto. Esses campos podem ser acompanhados por uma das três palavras-chave a seguir:

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

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

  • não terá acesso direto aos dados privados do objeto
  • poderá chamar os métodos públicos do objeto e, em especial, aqueles que darã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 inválidas. Precisamos inicializar um objeto do tipo personne por meio 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, pois 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, portanto, 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, escrevemos

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

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

dim p1 as personne=new personne()

Isso 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. Portanto, há uma inicialização padrã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

Vamos examinar 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 sabemos disso? Vejamos como ocorre a inicialização do objeto referenciado por p1 no programa chamador:

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

É chamado o método initialise do objeto p1. Quando, nesse método, se faz referência ao objeto Me, na verdade está-se fazendo 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-se utilizá-la explicitamente quando houver conflito de identificadores. Esse é 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. É necessário, então, eliminar a ambiguidade, designando o atributo age como Me.age.

3.1.6. Um programa de teste

Aqui está um breve 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. Usar um arquivo de classes compiladas (assembly)

Observe que, no exemplo anterior, há duas classes em nosso programa de teste: as classes personne e test1. Existe outra maneira de proceder:

  • compila-se a classe pessoa em um arquivo específico chamado assembly. Esse arquivo tem a extensão .dll

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

Os dois arquivos-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 gerou um arquivo personne2.dll. É a opção de compilação /t:library que indica a geração de um arquivo “assembly”. Agora, vamos compilar o arquivo 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 ele encontrará determinadas classes no arquivo personne2.dll. Quando, no arquivo-fonte test.vb, ele encontrar uma referência à classe personne — classe não declarada no arquivo-fonte test.vb —, ele procurará a classe personne nos arquivos .dll referenciados pela opção /r. Lá, ele encontrará a classe personne no assembly personne2.dll. Seria possível incluir outras classes nesse assembly. Para utilizar, durante a compilação, vários arquivos de classes compiladas, 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 é inicializado

Consideremos novamente a classe personne e adicionemos a ela 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

Agora temos dois métodos com o nome initialise: isso é válido, desde que aceitem parâmetros diferentes. É o que ocorre 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). Observe-se que o método initialise tem acesso direto aos atributos do objeto P, embora estes sejam do tipo private. Isso sempre é verdade: um objeto O1 de uma classe C sempre tem acesso aos atributos dos objetos da mesma classe C. Aqui está um teste da nova classe personne, que foi compilada em personne.dll, conforme 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 seus resultados:

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

3.1.9. Construtores da classe Pessoa

Um construtor é um procedimento chamado New, que é chamado durante a criação do objeto. Geralmente, ele é usado para inicializá-lo. 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 possui 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, ela possui um construtor padrão, que é o construtor sem parâmetros: public New(). Os atributos do objeto são, então, inicializados com valores padrão. Foi isso que aconteceu nos programas anteriores, nos quais escrevemos:

    dim p1 as personne
    p1=new personne

Vamos criar dois construtores para 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

Nossos dois construtores limitam-se a chamar os métodos initialise correspondentes. Vale lembrar que, quando em um construtor encontramos a notação initialise(P), por exemplo, o compilador traduz como 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á sendo construído. Aqui está 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. Referências de objetos

Sempre utilizamos 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 faz referência a um objeto que será uma cópia do objeto referenciado 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 ao qual 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

Ao declarar a variável p1 por meio 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íamos que se trata de um ponteiro, c.a.d, que aponta para o endereço do objeto criado. Se, em seguida, escrevermos:

    p1=nothing

Não é o objeto personne("Jean","Dupont",30) que é modificado, e 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: ele “aponta” para o mesmo objeto (designa o mesmo objeto) que o ponteiro p1. Assim, se modificarmos o objeto “apontado” (ou referenciado) por p1, modificamos aquele referenciado por p2.

Quando se escreve:

dim p3 as personne =new personne(p1);

ocorre a criação de um novo objeto, que é uma cópia do objeto referenciado por p1. Esse novo objeto será referenciado por p3. Se modificarmos o objeto “apontado” (ou referenciado) por p1, não alteraremos em nada aquele referenciado por p3. É isso que mostram os resultados obtidos.

3.1.11. Os objetos temporários

Em uma expressão, é possível chamar explicitamente o construtor de um objeto: ele é criado, mas não temos acesso a ele (para modificá-lo, por exemplo). Esse objeto temporário é criado para fins de avaliação da expressão e, em seguida, descartado. O espaço de memória que ele ocupava será automaticamente recuperado posteriormente por um programa chamado “garimpo de lixo”, cuja função é recuperar o espaço de memória ocupado por objetos que não são mais 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 modifiquemos 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

Obtemos os seguintes resultados:

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

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

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

Adicionamos à classe personne os métodos necessários para ler ou modificar 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 maneira de acessar os atributos de uma classe: criar propriedades. Elas nos permitem manipular atributos privados como se fossem públicos. Consideremos a seguinte classe personne, na qual os acessadores e modificadores anteriores foram substituídos por propriedades de leitura e gravação:


' 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 (Property) permite ler (get) ou definir (set) o valor de um atributo. Em nosso exemplo, colocamos o prefixo _ nos nomes dos atributos para que as propriedades tenham o mesmo nome dos atributos primitivos. De fato, uma propriedade não pode ter o mesmo nome do atributo que ela gerencia, pois, nesse caso, haveria um conflito de nomes na classe. Portanto, nomeamos nossos atributos como _prenom, _nom, _age e modificamos os construtores e métodos de acordo com isso. Em seguida, criamos 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 gerenciado pela propriedade. Ela pode ter dois métodos chamados get e set. O método get geralmente é responsável por retornar o valor do atributo que gerencia (ele poderia retornar outra coisa; nada o impede). O método set recebe um parâmetro chamado value, que normalmente é atribuído ao atributo que ele gerencia. Ele pode aproveitar para verificar a validade do valor recebido e, eventualmente, lançar uma exceção caso o valor seja inválido. É isso que é feito aqui para a idade.

Como esses 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 & ")"))

buscamos obter os valores das propriedades prenom, nom e age da pessoa P. É o método get dessas propriedades que é chamado e que retorna o valor do atributo que elas gerenciam.

Na instrução

        P.age = 56

, deseja-se definir o valor da propriedade age. Nesse caso, é o método set dessa propriedade que é chamado. Ele receberá 56 em seu parâmetro **value**.

Uma propriedade P de uma classe C que defina apenas o método get é chamada de “somente leitura”. 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 nos permitem, portanto, manipular atributos privados como se fossem públicos.

3.1.14. Métodos e atributos de classe

Suponhamos que queiramos contar o número de objetos [personne] criados em um aplicativo. Podemos gerenciar um contador por conta própria, mas corremos o risco de esquecer os 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 é passar uma referência desse contador para que o construtor possa incrementá-lo: é preciso passar um novo parâmetro a eles. 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, ele é declarado de maneira diferente com a palavra-chave Shared:

    Private Shared _nbPersonnes As Long = 0

Para referenciá-lo, escreve-se personne._nbPersonnes para indicar que se trata de um atributo da própria classe personne. Aqui, criamos um atributo privado ao qual não teremos acesso direto fora da classe. Portanto, criamos uma propriedade pública para dar acesso ao atributo da classe nbPersonnes. Para definir o valor de nbPersonnes, o método get dessa propriedade não precisa 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 somente leitura (ReadOnly), pois não oferece um método set. Veja um exemplo. A classe personne fica da 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ág. 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á mencionamos que, por padrã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 se deixar enganar pelo uso incorreto da linguagem, que ocorre sistematicamente quando se fala de “objeto” em vez de “referência de objeto”. Um objeto só é manipulado por meio de uma referência (um ponteiro) a ele. Portanto, o que é transmitido a uma função não é o próprio objeto, mas uma referência a esse objeto. Assim, é o valor da referência — e não o valor do próprio objeto — que é 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, ela será copiada para o parâmetro formal correspondente R2. Portanto, as referências R2 e R1 apontam para o mesmo objeto. Se a função modificar o objeto apontado por R2, ela obviamente modificará aquele referenciado por R1, uma vez que se trata do mesmo objeto.

É o que mostra o exemplo a seguir:


' 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)

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

        ' alteração p1
        modifie(p1)

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

    Sub modifie(ByVal P As personne)
        ' exibiçã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
        ' exibiçã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

Percebe-se que houve a criação de apenas um objeto: o da pessoa p1 do procedimento Main e que o objeto foi efetivamente modificado pela função modifie.

3.1.16. Uma matriz de pessoas

Um objeto é um dado como qualquer outro e, por isso, vários objetos podem ser agrupados em uma 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)
        ' exibiçã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. Esses 3 elementos são inicializados aqui com os valores nothing e c.a.d, de modo que não fazem referência a nenhum objeto. Mais uma vez, por conveniência, fala-se em matriz de objetos, embora se trate apenas de uma matriz de referências a objetos. A criação da matriz de objetos, que é ela própria um objeto, não cria nenhum objeto do tipo de seus elementos: isso deve ser feito posteriormente. Obtêm-se os seguintes resultados:

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

3.2. 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 ela atenda às nossas necessidades. Suponhamos que queiramos criar uma classe enseignant: um professor é uma pessoa específica. Ele possui atributos que outra pessoa não terá: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome, sobrenome e idade. Um professor, portanto, faz parte integralmente da classe personne, mas possui atributos adicionais. Em vez de criar uma classe enseignant do zero, seria preferível 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 expressar que a classe enseignant herda as propriedades da classe personne, escreveremos:


Public Class enseignant
    Inherits personne

Observe-se a sintaxe específica em duas linhas. A classe personne é chamada de classe pai (ou mãe) e a classe enseignant, de classe derivada (ou filha). Um objeto enseignant possui todas as características de um objeto personne: ele 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 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
        ' 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é, que é somente de leitura e identifica a pessoa. Criamos uma classe enseignant herdando 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
        ' rastreamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' propriedade da seçã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 seção à qual o professor pertence no corpo docente (basicamente, uma seçã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)

declara que o construtor recebe quatro parâmetros: P, N, age e section. Ela deve passar três deles, (P,N,age), para sua classe base, neste caso a classe personne. Sabe-se que essa classe possui 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) para sua classe base da seguinte maneira:


        MyBase.New(P, N, age)

Assim que a construção da classe base for 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 para sua classe base os parâmetros de que ela precisa para ser construída
  • usa os demais parâmetros para inicializar seus próprios atributos

Também seria possível 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) seus três campos _prenom, _nom e _age. Apenas objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos derivados como neste caso, devem utilizar métodos públicos para ter acesso a eles. A situação teria sido diferente se a classe personne tivesse declarado os três campos como protegidos (protected): nesse caso, ela permitiria que classes derivadas tivessem acesso direto aos três campos. Em nosso exemplo, usar o construtor da classe pai foi, portanto, a solução correta e é o método habitual: ao construir um objeto filho, primeiro chama-se o construtor do objeto pai e, em seguida, completam-se as inicializações específicas do objeto filho (section em 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

Observe-se que, para compilar a classe filha enseignant, foi necessário referenciar o arquivo 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 se limita a criar um objeto enseignant (new) e a identificá-lo. A classe enseignant não possui o método identité, mas sua classe pai possui um, que, além disso, é público: por herança, ele se torna 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)

Percebe-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 seção). Portanto, precisamos 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
        ' rastreamento
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub

    ' propriedade de seçã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 de sua classe pai (MyBase.identite) para exibir sua parte “personne” e, em seguida, complementa com o campo _section, que é específico da classe enseignant. Observe a declaração da propriedade identite:


    Public Shadows ReadOnly Property identite() As String

que indica que a propriedade “identite” “oculta” o método de mesmo nome que poderia existir na classe pai. Consideremos um objeto enseignant, denotado por E. Esse objeto contém, em seu interior, um objeto personne:

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


    Public Shadows ReadOnly Property identite() As String

A classe enseignant agora possui duas propriedades identite:

  • aquela 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 pai é “redefinida” pela propriedade identite da classe filha. De 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 pai, se houver uma
  • na classe-mãe da sua classe-mãe, se ela 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 benefício 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, onde Ci Cj, indica que a classe Cj é derivada da classe Ci. Isso 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

De fato, por herança, a classe Cj possui todas as características da classe Ci, além de outras. Portanto, um objeto Oj do tipo Cj contém em si um objeto do tipo Ci. A operação

Oi=Oj

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

O fato de uma variável Oi da classe Ci poder, na verdade, referenciar não apenas um objeto da classe Ci, mas qualquer objeto derivado da classe Ci é chamado de polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos. 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);

quanto

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

Nesse ú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 nossa procedimento affiche:


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

O método p.identite retorna uma sequência de caracteres que identifica o objeto personne.. O que ocorre, no caso do nosso exemplo anterior, com um objeto enseignant:


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

Vejamos o exemplo a seguir:


' 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)
        ' exibe 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. Ela não se adaptou ao objeto realmente passado como parâmetro para a affiche. Seria preferível ter a identidade completa do enseignant. Para isso, seria 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 esse 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. Essa palavra-chave também pode ser aplicada 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 sua propriedade/método redefinido. Assim, na classe enseignant, a propriedade identite é definida da seguinte forma:


    ' sobrecarregamento 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)
        ' exibe identidade 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 identificar completamente o 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 escrevendo implicitamente:

    public class personne 
         inherits object

A classe object define um método virtual ToString:

Image

O método ToString retorna o nome da classe à qual o objeto pertence, conforme mostra o exemplo a seguir:


' 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 gerados são os seguintes:

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

Observe-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 ainda é capaz de exibir 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)
        ' exibe identidade 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. Essa classe permite armazenar objetos em uma lista. Ela pertence ao espaço de nomes [System.Collections]. No exemplo a seguir, utilizamos essa classe para armazenar uma lista de pessoas (em sentido amplo):


' 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
            ' procurando a pessoa chamada 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 certas 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 retorna o elemento i da lista

Observamos que, para obter o elemento nº i da lista, não escrevemos [liste.Item(i)], mas diretamente [liste(i)], o que, à primeira vista, parece estar errado. No entanto, isso é possível porque a classe [ArrayList] define uma propriedade padrã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)], ele verifica se a classe [ArrayList] definiu uma propriedade com a seguinte assinatura:


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

Nesse caso, ele encontrará o procedimento [Item]. Em seguida, ele converterá a notação [liste(i)] em [liste.Item(i)]. Chamamos a propriedade [Item] de propriedade indexada por padrã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 seria 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 sequência 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 padrão que retorna 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; por isso, utiliza-se 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 é realmente do tipo [personne] ou derivado com a função [TypeOf]. Caso contrário, é lançada uma exceção com a instrução [Throw]. Se o objeto adicionado for do tipo [personne], o método [Add] da classe base é utilizado para realizar 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 padrão chamada [Item] na classe base com a mesma assinatura. Portanto, é necessário usar a palavra-chave [Shadows] para indicar que a nova propriedade indexada [Index] irá “ocultar” a propriedade da classe base [Item]. Observe-se que isso se aplica mesmo que as duas propriedades não tenham o mesmo nome. A propriedade [Index] permite referenciar a pessoa nº i da lista. Ela se baseia na propriedade [Item] da classe base para ter acesso ao elemento nº i do objeto [ArrayList] subjacente. São realizadas alterações de tipo para levar 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 retorna uma sequência de caracteres no formato “(e1, e2, ..., en)”, em que e1, e2, ..., en são os elementos da lista. Observe-se a notação [Me(i)], que designa o elemento nº i do objeto atual [Me]. É utilizada a propriedade indexada por padrão. Assim, [Me(i)] é equivalente a [Me.Index(i)].

O código da classe é colocado no arquivo [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)
        ' exibiçã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

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

Pode-se querer escrever

dim p as personne=l("nom")

onde l seria do tipo [listeDePersonnes]. Aqui, queremos indexar a lista l não mais por um número de elemento, mas por um nome de pessoa. Para isso, definimos uma nova propriedade indexada por padrão:


    ' outro indexador
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' buscando 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 criando novamente uma propriedade indexada por padrão. Todas as propriedades por padrão devem ter o mesmo nome, neste caso, [Index]. A nova propriedade [Index] indexa a classe listeDePersonnes por uma sequência de caracteres N. O resultado de listeDePersonnes(N) é um inteiro. Esse inteiro representará a posição na lista da pessoa com o nome N ou -1, caso essa pessoa não conste na 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 padrão da classe base (embora ela não tenha a mesma assinatura).

No corpo do get, percorre-se a lista de pessoas em busca do nome N passado como parâmetro. Se ele for encontrado na posição i, retorna-se i; caso contrário, retorna-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))
        ' exibiçã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)
        ' exibição do elemento 1
        Console.Out.WriteLine(("l[1]=" + l(1).ToString))
        ' exibição da lista l
        Console.Out.WriteLine(("l=" + l.ToString))
        ' busca 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 classe e estrutura. 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 usar o exemplo a seguir 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 foi modificado
        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

Ao executar este programa, obtêm-se os seguintes resultados:

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

Enquanto nas páginas anteriores deste capítulo utilizávamos 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, o endereço (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, escrevemos


        Dim sp2 As spersonne = sp1

uma nova estrutura (nome, idade) é criada 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 maneira diferente. O valor de cp1 é copiado para cp2, mas, como esse valor é, na verdade, o endereço do objeto, este não é duplicado. Ele simplesmente passa a ter duas referências:

No caso da estrutura, se modificarmos o valor de sp2, não modificamos o valor de sp1, como mostra o programa. No caso do objeto, se modificarmos o objeto apontado por cp2, aquele apontado por cp1 será modificado, pois se trata do mesmo objeto. É isso que os resultados do programa também demonstram.

Portanto, podemos concluir dessas 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 ao qual ela aponta

Uma vez compreendida essa diferença fundamental, a estrutura se mostra muito semelhante à classe, como demonstra o novo exemplo a seguir:


' 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 é modificado
        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 estrutura e classe é que, com uma classe, os objetos p1 e p2 teriam o mesmo valor ao 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 forma um contrato. Uma classe que decide implementar uma interface se compromete a fornecer uma implementação de todos os métodos definidos na interface. É o compilador que verifica essa implementação. Veja, 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 essa 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] deverão ser definidos na classe C. Consideremos o código a seguir:


' 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)
        ' registro 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 em uma 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 possam ser acessados por uma classe derivada. O tipo élève é uma estrutura que armazena o nome do aluno e 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 essa classe notes em uma 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

Isso significa que a classe notesStats deve ter dois métodos chamados 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
        ' retorna a média das notas
        Return _moyenne
    End Function

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

A média _moyenne e o desvio-padrão _ecartType são calculados assim que o objeto é criado. Portanto, os métodos moyenne e écartType precisam apenas retornar o valor dos atributos _moyenne e _ecartType. Ambos os métodos retornam -1 se a matriz 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 são gravadas em um objeto de notas
        Dim anglais As New notes("anglais", ELEVES)
        ' e que são exibidas
        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

retorna 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 conta 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 do 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 primeira 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 essa interface. Observe-se que essas classes não realizam nenhuma outra ação, a fim de simplificar o exemplo. Agora, consideremos o exemplo a seguir:


' 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. Portanto, ela poderá receber para esse parâmetro tanto um objeto do tipo classe1 quanto do tipo classe2. É isso que é feito no procedimento Main, com os seguintes resultados:

17
21
127
201

Vemos, portanto, que temos aqui uma propriedade semelhante ao polimorfismo observado nas classes. Se um conjunto de classes Ci não relacionadas entre si por herança (portanto, 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 em uma interface I da qual todas as classes envolvidas herdariam. Instâncias dessas classes Ci podem, então, ser utilizadas como parâmetros de funções que aceitam um parâmetro do tipo I, c.a.d. Essas funções 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. Observe, por fim, que a herança de interfaces pode ser múltipla, c.a.d. pode-se escrever


Public Class classe
    Implements I1,I2,...

onde os Ij são interfaces.

3.6. Os espaços de nomes

Para escrever uma linha na tela, usamos a instrução

Console.Out.WriteLine(...)

Se analisarmos a definição da classe Console


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

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

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

Isso é evitado usando uma cláusula imports:

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

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


Assembly: Mscorlib (in Mscorlib.dll)

Essa linha indica em qual “assembly” se encontra a definição da classe Console. Ao compilar fora do Visual Studio.NET e precisar fornecer as referências das diferentes dll que contêm as classes a serem utilizadas, essa informação pode ser útil. Vale lembrar 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 desses 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, respectivamente, nos dll, E1.dll e E2.dll. Suponhamos que um cliente C compre esses dois conjuntos de classes, nos quais ambas as empresas definiram uma classe personne. O cliente C compila um programa da seguinte maneira:

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 de E1.dll ou a de E2.dll. Ele sinalizará um erro. Se a empresa E1 se certificar de criar suas classes em um espaço de nomes chamado E1 e a empresa E2 em um espaço de nomes chamado E2, as duas classes personne passarão a se chamar E1.personne e E2.personne. O cliente deverá utilizar em 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 em um 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 em um namespace nossa classe personne, estudada anteriormente. Escolheremos istia.st como namespace. 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

Essa 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 em uma 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

importamos o namespace istia.st com uma cláusula imports:


Imports istia.st

Agora vamos compilar o programa de teste:

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

Isso gera um arquivo test.exe que, quando executado, apresenta os seguintes resultados:

Jean,Dupont,30

3.7. O exemplo IMPOTS

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

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

  • calcula-se o número de quotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se tiver pelo menos três filhos, recebe mais meia parcela
  • calcula-se sua renda tributável R = 0,72 * S, onde S é seu salário anual
  • calcula-se seu coeficiente familiar QF = R / nbParts
  • calcula-se seu imposto I. Consideremos a tabela a seguir:
12620,0
0
0
13.190
0,05
631
15.640
0,1
1.290,5
24.740
0,15
2.072,5
31.810
0,2
3.309,5
39.970
0,25
4.900
48.360
0,3
6898,5
55.790
0,35
9.316,5
92.970
0,4
12.106
127.860
0,45
16.754,5
151.250
0,50
23.147,5
172.040
0,55
30.710
195.000
0,60
39.312
0
0,65
49.062

Cada linha possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 23000, será encontrada 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 serão utilizados os coeficientes da última linha. Aqui:

    0                0.65        49062

o que resulta no imposto I = 0,65 * R - 49.062 * nbParts.

A classe de imposto será definida da seguinte forma:


' opções
Option Strict On
Option Explicit On 

' espaços de nomes
Imports System

Public Class impot
    ' 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 três 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
        ' tudo certo
        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 cotas
        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 necessários para o cálculo do imposto de um contribuinte. Essa é a parte estável do objeto. Uma vez criado esse objeto, é possível chamar repetidamente seu método calcular, que calcula o imposto do contribuinte com base em seu estado civil (casado ou solteiro), no número de filhos e no 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 de imposto
        ' o usuário digita três dados no teclado: casado nbEnfants salário
        ' o programa exibe 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
            ' solicita-se 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()
            ' o que fazer?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' verificação do número de argumentos na linha inserida
            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

Aqui está 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 :