Introdução
Neste artigo serão apresentados os conceitos básicos de OOP (Desenvolvimento Orientado a Objectos). No final será apresentado um exemplo prático de um simulador de extracção de bolas do "Euromilhões" com todo o código fonte em Visual Basic .Net.
As classes são templates para objectos
As classes definem os membros de um objecto, definem o seu comportamento e atribuem os seus valores iniciais quando apropriado. Quando uma classe é iniciada é criada em memória uma instância. Esta instância é chamada "Objecto".
Objectos e Membros
Os objectos são compostos por membros, que representam a sua estrutura e funcionalidade. Membros são:
- Campos e Propriedades: São membros de informação sobre o objecto.
- Métodos: São acções que o objecto pode realizar.
- Eventos: São notificações que o objecto recebe ou envia para outros objectos quando acontece uma actividade.
Num cenário baseado na vida real, podemos considerar um objecto carro. Este objecto tem propriedades tais como: Cor, Marca, Modelo, Idade, etc. Esta informação descreve o estado do objecto.
O objecto carro tem também métodos, como: Acelerar, trocar mudança ou virar. Os métodos representam comportamentos que o objecto pode executar.
Eventos representam notificações. Por exemplo, no caso do objecto carro pode ser por exemplo o sobre aquecimento do motor ou evento "Batida" ao interagir com um objecto árvore.
Modelo de objectos
Os objectos mais simples podem consistir somente em algumas propriedades, alguns métodos e até talvez um ou dois eventos, mas objectos complexos podem requerer imensos membros e objectos subordinados. Os objectos podem expor outros objectos como membros .
O modelo de Objectos define a hierarquia dos objectos filhos que compõem a estrutura de um objecto.
Voltando ao exemplo do objecto carro, um carro é um único objecto, mas contém outros objectos subordinados, como por exemplo um objecto motor, quatro objectos rodas, etc.
A composição destes objectos subordinados afecta directamente o funcionamento do carro como um todo.
Encapsulamento
Encapsulamento é o conceito de que a implementação de um objecto é independente do seu interface. Ou seja, uma aplicação interage com um objecto através do seu interface, que consiste nas suas propriedades públicas e métodos. Enquanto este interface permanecer constante a aplicação pode interagir com o componente, mesmo que a sua implementação interna seja totalmente reformulada.
Os objectos apenas devem interagir uns com os outros através dos seus métodos e propriedades públicas. Isto significa que os objectos devem conter em si toda a informação e todos os mecanismos para a manipular. A informação e mecanismos internos do objecto nunca devem ser expostos através do interface.
Se um objecto carro interagir com um objecto condutor, o interface deverá consistir em membros como: método avançar, método recuar, método parar, etc. Esta é toda a informação que o objecto condutor necessita para interagir com o objecto carro.
O objecto carro contem um objecto motor, mas o objecto condutor não tem que saber sequer da sua existência.
Se o objecto motor for trocado, o objecto condutor continuará a interagir com o objecto carro exactamente da mesma forma.
Polimorfismo
Polimorfismo é a capacidade de diferentes classes disponibilizarem implementações diferentes dos mesmos interfaces públicos. Ou seja, o polimorfismo permite que métodos e propriedades de um objecto possam ser chamados, sem conhecimento particular da sua implementação.
O polimorfismo pode ser providenciado de duas formas:
- Polimorfismo de interface
- Polimorfismo de herança
Um objecto condutor pode interagir com um objecto carro através do seu interface público.
Se outro objecto, como camião ou tractor disponibilizar o mesmo interface, o objecto condutor pode interagir com eles sem se preocupar com a implementação especifica.
Polimorfismo de interface
Um interface é um contrato de comportamento. Essencialmente define quais os membros que a classe vai implementar, mas não diz nada acerca dos detalhes da implementação. Um objecto pode implementar vários interfaces diferentes , e várias classes diferentes podem implementar o mesmo interface.
Todos os objectos que implementam um mesmo interface são capazes de interagir com outros objectos através desse interface.
Utilizando o exemplo anterior, o objecto carro pode implementar o interface "IDrivable" que especifica os métodos avançar e recuar. Outras classes, como camião podem implementar também este interface e assim ser capazes de interagir com o objecto condutor.
Polimorfismo de herança
A herança permite incorporar a funcionalidade previamente definida numa classe numa nova classe, e implementar novos membros se necessário.
Uma classe que herda de outra classe é designada como classe derivada. Uma classe apenas pode herdar directamente de uma classe (classe base).
A nova classe tem os mesmos membros que a classe base, embora possa adicionar ou alterar o funcionamento dos existentes.
Se a classe carro for uma classe base, uma classe derivada pode ser o CarroDesportivo
Métodos
Os métodos podem ser implementados como:
- Procedimentos ("Sub")
- Funções ("Function")
Function CompleteName(ByRef FirstName as string, ByVal LastName as string, Optional ByVal title As String = "") As String
CompleteName = ""
' Use the title if provided.
If title <> "" Then CompleteName = title & " "
' Append first and last name.
CompleteName &= FirstName & " " & LastName
End Function
Overloading
"Overloading" é a possibilidade de criar vários métodos com o mesmo nome mas com assinaturas diferentes.
A assinatura do método é composta pelos tipos dos seus parâmetros, e pela forma como são passados (ByRef ou ByVal).
O Retorno do método não é relevante para a assinatura (não pode ser utilizado para "Overloading")
Function Item (ByVal index as Integer) as String
'Acede a elementos através do index
End Function
Function Item (ByVal Key as string) as String
'Acede aos elementos através de uma chave "String"
End Function
'Chamada através do primeiro método
Result=MyObj.Item(1)
'Chamada através do segundo método
Result=MyObj.Item("teste")
Regras para "Overloading"
- A palavra reservada "Overloads" pode ser utilizada de forma opcional. Mas se for utilizada num método tem de ser utilizada em todos os métodos da classe.
- A utilização de "overloading" é facilitada com as capacidades de "intellisence" do Visual Studio.
- O "Overloading" pode ser uma alternativa melhor para casos em que se utilizem parâmetros opcionais.
- Quando os tipos dos argumentos não correspondem exactamente à assinatura de nenhum método disponível, o compilador tenta encontrar um método através de "Widening coercion"
- Casos ambíguos:
A plataforma tem de conseguir resolver um método no momento da compilação, por isso os métodos tem de ter mais alguma diferença para além de parâmetros opcionais.
Como não é possível distinguir dois métodos apenas pelo tipo de retorno, não é permitido o "overloading" de métodos sem parâmetros.
Propriedades
As propriedades podem ser consideradas "Super Fields", pois são manipuladas como os "fields", mas tem código associado.
Public Property BirthDate() as date
'Código que implementa a propriedade
End Property
Tal como os campos e os métodos, as propriedades podem possuir um identificador de "Scope". Por defeito as propriedades são publicas.
Estrutura das propriedades:
Get … End Get -> Define o valor devolvido pela propriedade
Set … End Set -> Define como são assignados valores à propriedade
Dim m_BirthDate As Date
'...
Public Property BirthDate() As Date
Get
Return m_BirthDate
End Get
Set(ByVal value As Date)
m_BirthDate = value
End Set
End Property
A partir do Visual Basic 2005 é possível atribuir definições de "Scope" diferentes aos blocos "Set" e "Get", Esta capacidade é importante, por exemplo, quando se pretende que uma propriedade seja só de leitura se chamada fora do "Assembly" e de escrita se chamada de outras classes dentro do mesmo "Assembly":
Private m_ID As Integer
Public Property ID() As Integer
Get
Return m_ID
End Get
Friend Set(ByVal value As Integer)
If value <= 0 Then Throw New ArgumentException("Valores negativos são inválidos")
m_ID = value
End Set
End Property
Propriedades com argumentos
Dim m_Addresses(3) As String ' Até 4 linhas
Public Property Addresses(ByVal index As Integer) As String
Get
If index < 0 Or index > UBound(m_Addresses) Then
Throw New IndexOutOfRangeException("Indice inválido.")
End If
Return m_Addresses(index)
End Get
Set(ByVal value As String)
If index < 0 Or index > UBound(m_Addresses) Then
Throw New IndexOutOfRangeException("Indice inválido.")
End If
m_Addresses(index) = value
End Set
End Property
Propriedades "Default"
Um tipo pode expor uma propriedade por defeito, para que possa ser chamada sem que seja necessário utilizar o seu nome, desde que seja uma propriedade com argumentos:
Default Public Property Addresses(ByVal index As Integer) As String
'(…)
End Property
'Utilizar a propriedade:
Dim MyPerson as new person
MyPerson.name="Ana"
MyPerson(0)="Avenida da casa amarela"
MyPerson(1)="Nº149 – Lisboa"
Construtores
Um construtor é um método especial que é executado sempre que é criada uma instância de uma classe. Em VB o construtor é sempre chamado "Sub New":
Class Person
Private CreateTime as date
Public Sub New()
' Display a diagnostic message.
Console.WriteLine("A new instance of Person is being created.")
' Remember when this instance was created.
CreateTime = Now
End Sub
End Class
Quando a execução chega ao construtor, todos os campos com um iniciador já foram iniciados:
Class Person
Public Pais as string="Portugal"
Public Sub New()
Console.writeline(Pais) '->Portugal
End Sub
End Class
Um construtor pode aceitar argumentos, normalmente para obrigar à definição de valores indispensáveis para a criação e funcionamento correcto do objecto:
Class Person
Public FirstName as String
Public LastName as String
Public Sub New(ByVal FirstName as String, ByVal LastName as String)
Me.FirstName=FirstName
Me.LastName=LastName
End Sub
End Class
'Instanciação
Dim MyPerson as new Person ("João","Teixeira")
Construtores "OverLoad"
Tal como todos os restantes métodos, o "sub New" pode ser "Overloaded", o que significa que podem ser dadas várias opções para a criação de um objecto.
Public Sub New(ByVal name As String)
' chama o outro construtor, passando o default do segundo argumento .
Me.New(name, 0)
End Sub
Public Sub New(ByVal name As String, ByVal id As Integer)
Me.Name = name
Me.Id = id
End Sub
Membros estáticos ("Shared"):
São membros (Campos, Propriedades ou métodos) que pertencem à classe e não à instância.
Através da palavra reservada "Shared" um método fica marcado como estático, podendo ser chamado sem ser necessário criar uma instância do objecto:
Public Class Triangle
Dim a, b, c As Double
Public Shared Function GetPerimeter(ByVal a As Double, ByVal b As Double, ByVal c As Double) As Double
Return a + b + c
End Function
Public Shared Function GetArea(ByVal a As Double, ByVal b As Double, ByVal c As Double) As Double
Dim halfP As Double = (a + b + c) / 2
Return Math.Sqrt(halfP * (halfP - a) * (halfP - b) * (halfP - c))
End Function
End class
Regras para a implementação de membros estáticos
- O Código incluído dentro de um método estático ("Shared") apenas pode aceder a outros membros estáticos. Não pode aceder a campos, propriedades ou métodos de instância.
- Normalmente são criados dois tipos de classes que só contém membros estáticos:
- Uma classe que funciona como um contentor biblioteca de membros estáticos, como por exemplo o "System.Math".
-
Um tipo que apenas assume a forma de um objecto único (uma única instância) , como por exemplo o "System.Console" (São chamadas Singletons") .
Se uma classe apenas contém membros estáticos, não é necessário proceder à sua instanciação.
Para garantir que uma classe não é instanciada de todo, deve ser criado um construtor privado, para evitar que o VB construa um automaticamente:
Public Class Mylibrary
Private Sub New
'Nada
End Sub
End class
Uma propriedade identificada como "Shared"
Public Shared ReadOnly Property NextInvoiceID() As Long
Get
Return InstanceCount + 1
End Get
End Property
Construtores estáticos ("Shared")
- Um construtor "Shared" e sem parâmetros é chamado automaticamente antes da primeira instância da classe ser criada ("Static Constructor" ou "Type constructor").
- Um construtor estático é tipicamente utilizado para iniciar de forma correcta campos estáticos, quando é necessária uma lógica mais elaborada que um simples iniciador.
- Um construtor estático pode ser utilizado para aceder a recursos partilhados por todas as instâncias de um objecto, como por exemplo, um ficheiro.
- O construtor estático é executado antes do construtor de instância para a primeira instância.
- Um construtor estático é implicitamente Privado.
- O construtor estático é o único local onde é possível assignar um valor a um campo "Shared" e "ReadOnly".
Public Shared ReadOnly InitialDir as String
Shared Sub New
InitialDir=Directory.GetCurrentDirectory()
End Sub
Caso prático
Esta pequena aplicação pretende aplicar na prática os conhecimentos adquiridos em desenvolvimento orientado a objectos.
O objectivo será criar um modelo realista do funcionamento de um mecanismo de extracção de bolas para o jogo "EuroMilhões".
A aplicação, ao ser executada, deverá apresentar uma chave (5 números entre 50 mais 2 números entre 9), bem como a seguinte informação:
- Listagem de todas as bolas que saíram na tômbola grande (incluindo valor e cor)
- Listagem de todas as bolas que saíram na tômbola pequena (incluindo valor e cor)
- Listagem de todas as bolas que ficaram na tômbola grande (incluindo valor e cor)
- Listagem de todas as bolas que ficaram na tômbola pequena (incluindo valor e cor)
Regras para desenvolvimento
- Criar um novo projecto com o template "Console Application"
-
Criar uma estrutura "Bola" com as seguintes características:
- Propriedade "valor" do tipo "Integer" (deverá ser preenchida com um valor recebido no construtor)
- Propriedade "Cor" do tipo "String" (Deverá ser preenchida no construtor, através da formula: valor entre 1 e 10 = Amarela, entre 11 e 20 = Vermelha, entre 21 e 30 = Azul, entre 31 e 40 = Verde, entre 41 e 50 = Laranja.
-
Criar uma classe "Tômbola" com as seguintes características:
- Propriedade do tipo "Integer" chamada "Capacidade" (Capacidade máxima da tômbola)
- Propriedade só de leitura do tipo "integer" chamada "Capacidade ocupada" (Com a capacidade actualmente ocupada da tômbola. Deve ser actualizada sempre que o conteúdo da tômbola seja alterado).
- Propriedade só de leitura do tipo "Bola" com argumentos por índice, chamada "ConteúdoTombola" (Através de um índice, retorna a bola correspondente existente na tômbola).
- Método "enche", sem retornos. Recebe um "array" de bolas e coloca no conteúdo na tômbola.
- Método "BaralhaBolas", sem retornos. Baralha o conteúdo da tômbola (Implementar com um método privado chamado "BaralhaBolasArray".
- Método "ExtraiBola", que retira uma bola da tômbola e devolve esse objecto. (Implementar com um método privado chamado "RemoveBolaArray".
-
No modulo da "console Application" realizar os seguintes passos:
- Criar 50 instâncias de objectos "Bola" (Definir o valor no momento de construção. Os valores deverão ser de 1 a 50)
- Criar um objecto do tipo "Tômbola" chamado "TombolaGrande" e definir a sua capacidade.
- Colocar as bolas dentro da tômbola (método "Enche")
- Baralhar a tômbola (Método "BaralhaBolas")
- Retirar 5 bolas da tômbola (Método "ExtraiBola". A tômbola deverá ser baralhada entre cada saída de uma bola).
- Repetir os passos, com 9 bolas e um objecto "Tômbola" chamado "TombolaPequena"
- Apresentar no ecrã a informação pedida.
Solução
