Para uma introdução sintética dos principais conceitos de Orientação por Objectos (OO), seguir-se-á de muito perto [Russo 1991], um dos trabalhos pioneiros na área dos
frameworks, que inclui uma excelente apresentação desses conceitos. Refere-se ainda [Booch
1994] para uma descrição mais pormenorizada desta matéria.
Os quatro princípios fundamentais do paradigma OO, que caracterizam todas as abordagens nele baseadas, são: o encapsulamento de dados, a abstracção de dados, o polimorfismo e a herança.
4.1.1 Encapsulamento de dados
O principal objectivo da utilização de técnicas de encapsulamento de dados é o aumento da modularidade, da facilidade de manutenção e da fiabilidade do software. Tais técnicas têm como princípios básicos a agregação dos dados com as funções que sobre elas operam e a restrição do acesso aos dados a apenas essas funções. A preservação de estado entre chamadas de funções é garantida através do seu armazenamento nos dados encapsulados.
No paradigma OO, a unidade de encapsulamento de dados é o objecto. Um objecto é um encapsulamento simples, consistindo de um conjunto de variáveis e um conjunto de operações que permitem aceder a essas variáveis e alterá-las.
Uma operação é uma mensagem que o objecto aceita. Invocar uma operação de um objecto consiste no envio de uma mensagem ao objecto. O conjunto de mensagens que um objecto aceita é a sua assinatura ou protocolo.
Em OO, o envio de mensagem existe apenas numa perspectiva conceptual: o que realmente se realiza é a pesquisa ("lookup") de um método (o código que executa a operação descrita pela mensagem), com base no respectivo objecto, e a sua invocação. Enviar uma mensagem a um objecto resulta, portanto, numa pesquisa de método para encontrar o método em causa, seguido de uma invocação de método para o chamar. Idealmente, os métodos de um objecto constituem a única forma de outros objectos do sistema terem acesso ao seu estado e realizarem operações sobre ele.
As mensagens que um objecto aceita podem ser públicas ou não. Todas as mensagens classificadas como públicas são aceites pelo objecto, independentemente da sua origem. O conjunto de mensagens públicas da assinatura do objecto é o seu interface.
À semelhança de outras técnicas de encapsulamento de dados, os objectos preservam o seu estado entre sucessivos envios de mensagens, recorrendo às respectivas variáveis.
4.1.2 Abstracção de dados
Os programas de computador podem ser vistos como modelos, que utilizam, enquanto tal, abstracções de entidades, e nos quais os valores manipulados pelas expressões correspondem às entidades modeladas. No paradigma OO todos os valores são objectos. Uma vez que muitos objectos representam abstracções semelhantes e, consequentemente, partilham comportamentos idênticos, o paradigma OO introduz o conceito de classe, por forma a permitir exprimir esses aspectos comuns.
Uma classe é um padrão para a criação de um tipo de objecto: define as variáveis, a assinatura e a implementação das mensagens da assinatura para as instanciações ou instâncias da classe. Os métodos de uma classe podem referenciar as variáveis que a classe define. Em execução ("run-time"), estas variáveis são ligadas às de uma instância particular da classe. Esta ligação em execução ("run-time binding") é também um factor distintivo do paradigma OO.
Num programa, as expressões geram valores que são normalmente atribuídos a variáveis ou usados noutras expressões. A verificação de tipo ("type checking") é o mecanismo usado para determinar se esses valores são usados no contexto apropriado. Existem duas formas de verificação de tipo: estática e dinâmica.
As linguagens de tipos estáticos ("statically typed") exigem a determinação, durante a compilação, da conformidade entre valores e tipos requeridos nos pontos de utilização. Em OO, estes tipos são especificados como assinaturas que um objecto atribuído à variável deve possuir. Em muitos casos, a assinatura é especificada como uma classe, cuja assinatura implicitamente define o tipo da variável. Em alguns casos, a especificação de assinaturas pode ser independente de classes.
Em linguagens de tipos dinâmicos ("dinamically typed"), a conformidade entre um valor e um tipo particular exigido é verificada em execução. Em OO, tal exige que as variáveis não tenham tipo e possam referenciar objectos de qualquer tipo.
Estas duas formas de verificação de tipo apresentam diferentes compromissos aos níveis de engenharia de software e de desempenho: as abordagens dinâmicas conduzem habitualmente a código mais flexível e de mais simples reutilização; no entanto, são em geral menos eficientes, uma vez que não existe informação de tipo, para orientar a compilação.
4.1.3 Herança e subclasses
Da mesma forma que as classes surgem naturalmente de comportamentos idênticos de grupos de objectos, diferentes classes podem ter mensagens e métodos comuns. A herança
de classes permite a um conjunto de classes partilhar partes de um interface comum (assinatura)
e partes de uma implementação comum (métodos e variáveis).
A derivação de subclasses é o mecanismo de herança mais conhecido, que permite que uma parte ou a totalidade de assinatura, métodos e variáveis de uma classe sejam herdados de
classes base. A herança simples permite apenas uma classe base, enquanto a herança múltipla
permite diversas classes base. As relações de herança entre classes formam grafos acíclicos não-orientados, pelo que são normalmente designadas hierarquias de classes. As classes descendentes de uma dada classe numa hierarquia são normalmente designadas as suas
subclasses e dizem-se derivadas dessa classe.
A delegação, em que os objectos encaminham mensagens para outros objectos, é uma outra forma de herança de comportamento.
A herança permite realizar adaptações de classes de uma forma incremental e estruturada. Para implementar uma nova classe é possível ampliar a assinatura herdada com mensagens adicionais e/ou redefinir a implementação de métodos herdados. As classes que apenas definem uma assinatura e deixam a definição da sua implementação para outras classes são normalmente designadas classes abstractas. As classes que definem uma implementação para uma assinatura particular são designadas classes concretas. Na prática, muitas classes abstractas encontram-se algures entre o puramente abstracto e o concreto, frequentemente disponibilizando métodos para algumas, mas não todas, as mensagens nas suas assinaturas.
O mecanismo que permite a estruturação em classes abstractas e concretas é o diferimento da ligação ("delayed binding") das mensagens com os métodos que as implementam: quando uma mensagem é enviada a um objecto, o método concreto a invocar só é determinado em execução, através da classe do objecto.
A separação de interface e implementação, permitida pelo uso de classes abstractas e concretas, é importante para a portabilidade. As classes abstractas podem definir interfaces que são implementados por diferentes classes concretas em diferentes aplicações. A separação torna também mais fácil a compreensão do sistema, uma vez que as abstracções podem ser concebidas de forma separada da sua implementação.
4.1.4 Polimorfismo
Um método que pode aceitar argumentos de tipos diferentes e, de acordo com eles, comportar-se de forma distinta, é dito polimorfo em relação a esses argumentos. Em OO, tal é conseguido pelo mecanismo já descrito de diferimento da associação das mensagens com os métodos que as implementam. Este tipo de polimorfismo, em que os objectos determinam o método apropriado em execução é chamado polimorfismo dinâmico. Por outro lado, constitui também uma forma de polimorfismo de inclusão ou limitado, que restringe o polimorfismo a objectos que partilham uma representação ou assinatura comum. O polimorfismo de inclusão
baseado em assinatura é prevalecente em linguagens OO.
Cada parâmetro formal de um método tem uma assinatura implícita, S, que consiste no conjunto de mensagens que serão enviadas ao parâmetro dentro do método e de qualquer método ao qual o argumento seja passado, a partir daí. O argumento usado para satisfazer um parâmetro polimorfo deve ser capaz de aceitar as mensagens definidas pela assinatura S desse parâmetro.
As linguagens de tipos estáticos que especificam tipos de parâmetros formais como assinaturas podem implementar polimorfismo na sua forma mais essencial, através da comparação de assinaturas, em compilação, para verificar a conformidade do parâmetro concreto com o tipo especificado para os parâmetros formais. As linguagens de tipos estáticos que especificam tipos de parâmetros indirectamente com uma classe, implementam uma forma restrita de polimorfismo: apenas instâncias de uma classe específica, ou suas subclasses, constituem argumentos aceitáveis, mesmo que instâncias de outras classes possam ter a assinatura desejada. Este comportamento é designado polimorfismo de herança.
As linguagens de tipos dinâmicos implementam polimorfismo na sua forma mais essencial, uma vez que qualquer objecto argumento só será alvo de verificação de tipo em execução.
Uma outra forma de polimorfismo é o polimorfismo estático, ou sobrecarga de operadores, no qual o compilador selecciona o método com base nos tipos dos parâmetros. Esta é a forma clássica de polimorfismo que ocorre nas linguagens de programação tradicionais (um exemplo é a forma como o operador + opera sobre argumentos inteiro/inteiro, real/real, inteiro/real ou real/inteiro).
O polimorfismo permite um elevado grau de flexibilidade na concepção e reconfiguração de software OO e é crítico para o desenho de código reutilizável: permite acrescentar novos componentes e substituir componentes existentes, uma vez que novas classes
que implementam assinaturas já existentes podem ser usadas com o código que pressupõe essas assinaturas.