No artigo anterior - Programação Orientada a Objectos (I), vimos um pequeno resumo do paradigma da POO. Vamos agora aprofundar um pouco os conceitos relacionados com este paradigma.
Conteúdo
Conceitos Fundamentais
Passamos a fazer uma breve descrição dos conceitos fundamentais da POO.
Objecto
Trata-se de uma entidade composta por atributos (os dados) e comportamentos (conjunto de operações). O objecto é uma entidade autónoma que armazena dados e todas as funcionalidades necessárias à manipulação e processamento desses dados. É uma unidade dinâmica, com “vida”, derivada das acções que pode realizar – antropomorfismo.
Um objecto tem um estado, correspondente ao conjunto dos valores dos seus atributos, num dado momento. O objecto tem a capacidade de responder a perguntas sobre os seus dados e a realizar operações sobre esses dados, alterando, assim, o seu estado.
Apesar de existirem muitos objectos iguais, cada um deles tem identidade única. Por exemplo, todas as viaturas de um dado modelo são iguais, mas cada uma delas, por si é única. Devemos pensar num objecto como tendo sempre uma existência física, mesmo que seja apenas uma dada voltagem na memória RAM do computador.
Finalmente, os objectos comunicam entre si, através de mensagens.
Classe
Uma classe é a representação abstracta de um conjunto de objectos, que têm características comuns. A classe representa um conceito e descreve formalmente as características e funcionalidades dos objectos. Podemos pensar na classe como a matriz genética do objecto, ou a planta de um prédio, ou a fórmula química de um medicamento.
Em termos práticos, uma linguagem orientada por objectos, baseia-se em classes. A elaboração de um programa baseado na POO, não é mais que a criação das classes que o compõem.
Uma classe deve descrever os dados (normalmente chamados propriedades) e os comportamentos, (normalmente chamados métodos) dos objectos. A classe deve descrever, ainda, a sintaxe das mensagens (normalmente chamadas eventos) que os objectos podem enviar.
O paradigma da POO faz uma aproximação da resolução dos problemas através do computador, com o funcionamento natural do nosso cérebro. Desde que nascemos, quando observamos a realidade que nos rodeia, vamos classificando os vários objectos. À medida que crescemos e aumentamos a nossa experiência, altera-se a forma como classificamos os objectos, mais que não seja, pela quantidade de novas classes que vamos apreendendo.
A classificação natural dos objectos é influenciada por diversos factores subjetivos, como a experiência, educação, religião, cultura, ambiente, etc. Assim, podemos ter classes muito semelhantes, com o mesmo nome até, mas que representam conceitos diferentes. Por exemplo, o conceito de mesa é completamente diferente entre um ocidental e um oriental.
Na POO, também podemos ir construindo uma biblioteca e uma hierarquia de classes, que vai aumentando ao longo do tempo, permitindo-nos evoluir na forma de elaborar os programas.
A classe tem a definição abstracta do conceito. O objecto é uma realização desse conceito.
Se pensarmos na classe “SerHumano”, existem 7 biliões de pessoas que partilham as características definidas para esta classe. No entanto são todas distintas entre si. Tal como as pessoas, vários objectos da mesma classe, partilham o mesmo código genético, mas são diferentes entre si. A distinção entre dois objectos da mesma classe, faz-se através do conteúdo dos seus dados.
Dois objectos da mesma classe, que estejam exactamente no mesmo estado, isto é, que tenham exactamente os mesmos valores para todos os dados, são, por definição, o mesmo objecto.
Abstração
A Abstração é considerada como a capacidade de modelar os objectos do mundo real, no problema que o programador esteja a tentar resolver. Neste contexto, podemos considerá-la como um mecanismo pelo qual restringimos o nosso universo de análise e as variáveis e constantes que compõem esse universo, desprezando os dados que não nos interessam.
Por exemplo, se o programador estiver interessado em controlar dados dos funcionários de uma empresa, não vai criar objectos com todas as características das pessoas (seria possível?), mas apenas as que interessam para o problema: nome, morada, data nascimento, BI, salário, etc.
Princípio da Abstração
Em termos práticos, este princípio diz-nos que não necessitamos de conhecer a implementação de um objecto (o seu código), para o utilizar, mas apenas as suas características – Interface. |
Encapsulamento
O Encapsulamento é essencial à POO. É a base de toda a abordagem da POO; isto porque contribui fundamentalmente para diminuir os malefícios causados pela interferência externa sobre os dados. Deste ponto de vista, o acesso aos dados só pode ser feito pelo próprio objecto, através de rotinas e procedimentos colocados "dentro" do objecto, que são chamadas pelo envio de mensagens.
Princípio do Encapsulamento
Em termos práticos, este princípio diz-nos que devemos proteger os dados dos objectos, tornando-os privados, permitindo apenas o acesso indireto aos mesmos, através de métodos públicos. |
Herança
A Herança é um mecanismo que permite definir uma classe, a partir de outras, acrescentando a si própria as funcionalidades destas, o que permite altos graus de reutilização de código.
Do ponto de vista prático, consiste num mecanismo simples (mas muito poderoso) para definir novas classes a partir de uma já existente. Assim sendo, dizemos que essas novas classes herdam todos os membros (propriedades+métodos) da classe original; isto torna o mecanismo de herança uma técnica muito eficiente para construir, organizar e reutilizar código.
A herança possibilita a criação de uma nova classe de modo a que essa classe (denominada subclasse ou classe derivada) herda TODAS as características da classe base (denominada super classe, ou classe primitiva). A classe derivada pode ter propriedades e métodos próprios e/ou pode reimplementar propriedades e métodos da classe base.
Uma classe pode ser derivada a partir de uma ou de mais do que uma classe – Herança Múltipla. Neste caso, a classe derivada herda todas as características das várias classes base que lhe dão origem. Apesar deste conceito de herança múltipla ser bastante corrente no plano teórico, poucas linguagens de programação o implementam, pelo que, muitas vezes, é necessário fazer algumas adaptações e criar um novo nível de abstração, para se poder implementar.
Polimorfismo
O termo Polimorfismo, etimologicamente, quer dizer "várias formas". Todavia, no universo da POO, é definido como sendo código que possui "vários comportamentos" ou que produz "vários comportamentos"; em outras palavras, é uma porção de código que pode ser aplicado a várias classes de objectos.
O polimorfismo é um conceito relacionado com a teoria dos tipos, segundo o qual, um nome (como a declaração de uma variável) pode referenciar objectos de várias classes diferentes, relacionadas entre si por uma classe base comum (Booch).
Assim, a implementação dos métodos é determinada pelo tipo do objecto e não pelo tipo da sua declaração (dynamic binding).
Conceitos Complementares
Com a evolução das linguagens orientadas a objectos, foram surgindo novas funcionalidades, que deram origem a alguns conceitos complementares aos iniciais, mas também importantes.
Interface
Corresponde à definição do nome de um conjunto de operações que caracterizam o comportamento de um elemento (UML). Também podemos pensar num Interface como um “contrato” que um dado objecto deve satisfazer.
Em termos práticos, a programação de um Interface consiste na definição das Assinaturas (nome + argumentos) dos métodos. Dependendo de cada linguagem, podem existir outras funcionalidades, mas nunca é feita nenhuma implementação (código propriamente dito).
De um modo geral, as diferentes linguagens orientadas a objectos, apesar de não permitirem herança múltipla de classes, permitem herança múltipla de interfaces. Neste contexto, entende-se que uma classe quando derivada de um interface, herda a obrigatoriedade de implementar os métodos definidos neste.
Classes Abstractas / Classes Seladas
As Classes Abstractas são aquelas que não permitem instanciação, ou seja, não permitem a criação de objectos a partir delas, somente permitem derivar outras classes.
As Classes Seladas são aquelas que não permitem derivar outras classes, só permitem a instanciação de objectos.
Por uma questão de lógica, uma classe não pode ser, simultaneamente abstracta e selada.
Membros Abstractos / Membros Virtuais
Um membro (propriedade ou método) declarado como abstracto numa classe base, obriga a que ele seja implementado nas classes derivadas.
Um membro declarado como virtual, permite (facultativo) a sua redefinição nas classes derivadas.
Sobreposição (Override) / Sobrecarga (Overload)
O override e overload de membros são conceitos relacionados com o polimorfismo.
Estamos a fazer override, quando criamos uma nova implementação para um dado membro, numa classe derivada.
Estamos a fazer overload, quando criamos implementações diferentes para o mesmo método, na mesma classe. As linguagens orientadas a objectos permitem fazer isto, variando os argumentos das várias implementações do mesmo método.
Podemos fazer, simultaneamente, override e overload ao mesmo método.
Construtores / Destrutores
O Construtor é um método especial que, se existir, é executado automaticamente sempre que é instanciado um novo objecto. Na implementação deste método, é normal colocar a inicialização dos dados do objecto.
As classes podem ter, também, um método Destrutor, que é chamado automaticamente, no momento em que o objecto é destruído (apagado da memória do computador). Na implementação deste método, podemos colocar código de validação ou fazer a persistência (guardar permanentemente) dos dados.
Membros Estáticos / Membros de Instância
Os membros Estáticos, são partilhados por todas as instâncias da classe. Por outro lado, os membros de instância, são próprios de cada objecto, não sendo, portanto, partilhados.
Classes Estáticas
São classes que só possuem membros estáticos. Como tal, não podem ser instanciadas. Estas classes são usadas, normalmente, como um repositório ou biblioteca de funções.
Componentes de um Objecto
DADOS: definem o estado actual do objecto;
COMPORTAMENTOS: acções que o objecto pode executar;
INTERFACE: tudo aquilo que, num objecto, é visível do exterior. Permite aceder aos dados do objecto e executar as acções disponibilizadas;
IMPLEMENTAÇÃO: conjunto de todo o código existente na classe de um objecto.
Relações entre Objectos / Módulos
Associação / Agregação / Composição
A associação representa uma relação directa ou indirecta entre objectos. É uma relação que denota uma conexão semântica entre duas classes (Booch).
Na POO, associação tem um significado semelhante a união. Numa associação, um objecto é formado a partir da união de dois ou mais objectos (relação do tipo todo-parte). Na POO existem dois tipos de associação: a agregação e a composição.
Na composição, o todo tem propriedade sobre as partes, isto é, quando um objecto é criado por composição de outros objectos, ele tem o poder de criar ou destruir esses objectos.
A agregação é a uma forma mais geral de associação. Um objecto criado por agregação não tem o poder de criar ou destruir as partes que o compõem. Tem apenas uma referência (apontador) para os outros objectos.
Os objectos complexos podem ser criados por herança ou por associação (normalmente composição).
Coesão (Cohesion)
A coesão é a medida do grau com que um módulo (método ou classe) implementa uma função ou objectivo único, num sistema.
Os módulos que implementam muitas funções, dizem-se como tendo Baixa Coesão (Low Cohesion). É desejável que os módulos tenham Alta Coesão (High Cohesion).
Acoplamento (Coupling)
O acoplamento é a medida do grau de interdependência entre módulos, num sistema. É desejável que um sistema tenha Baixo Acoplamento (Loose Coupling), o que minimiza o perigo de alterações num módulo implicarem a alteração de outros, para não ocorrerem erros.
A coesão é uma medida aplicada a um único módulo, enquanto que o acoplamento se aplica a conjuntos de módulos