Programação Orientada a Sujeitos

Na década de 90, após a descoberta de que a Engenharia de Software orientada a objetos, por si só, não era capaz de resolver todos os problemas encontrados durante o design de Softwares, surgiram várias abordagens de desenvolvimento que tentam resolver este problema, apresentando uma visão mais ampla.

Uma destas abordagens é a Programação Orientada a Sujeitos (SOP - Subject-Oriented Programming), apresentada por Harrison e Ossher, do IBM T.J. Watson Research Center, em 1993. Parte do princípio que devemos reconhecer não um objeto puro, mas diferentes visões subjetivas de cada objeto.

A primeira parte deste trabalho irá introduzir o conceito da Programação Orientada a Sujeitos, explicando rapidamente quais são seus objetivos e que vantagens apresenta em relação a abordagem Orientada a Objetos clássica. As partes seguintes irão descrever mais a fundo a abordagem e a tecnologia utilizadas na SOP, trazendo exemplos práticos para facilitar o entendimento.


Sumário:

1. Introdução
2. Objetivos
3. Conceitos
4. Exemplo
5. Ferramentas
6. Como funciona
    6.1. Definição do Sujeito
    6.2. Arquivo de Especificação do Sujeito
    6.3. Subject Labels
    6.4. Arquivo de Regras de Composição
    6.5. Regras de Composição
    6.6. Exemplo
7. Conclusões
8. Novas áreas
9. Maiores informações


1. Introdução

O surgimento da Programação Orientada a Sujeitos parte do princípio de que a Orientação a Objetos é insuficiente para a construção de grandes conjuntos de aplicações que manipulam os mesmos objetos. Isto ocorre porque cada aplicação tem uma visão ou abstração diferente para o mesmo objeto. Ou seja, cada um dos atributos e métodos de uma das abstrações sobre um objeto deve ser acessado por apenas uma ou algumas das aplicações, mas não por todas.


Figura 1: Exemplo de um conjunto de aplicações OO sobre um Objeto

Utilizando Orientação a Objetos, somos obrigados a escolher uma das duas saídas. A primeira seria não encapsular os atributos e métodos das diferentes aplicações em uma única classe, fazendo com que cada aplicação tenha sua própria classe que representará o seu objeto. Na segunda o designer do objeto deverá conhecer todas as possíveis aplicações do mesmo para que possa modelar uma abstração completa única que possa atender bem a todas as aplicações.

Nenhuma das duas alternativas parece uma boa solução para gerenciar esta complexidade. A Orientação a Sujeitos surgiu como uma proposta de modelo mais poderoso que a Orientação a Objetos para o desenvolvimento de conjuntos de aplicações.


2. Objetivos

O objetivo principal da Orientação a Sujeitos é facilitar o desenvolvimento e a evolução de conjuntos de aplicações cooperativas, ou seja, aplicações que compartilham os mesmos objetos e agem em conjunto na execução de operações

Para isto, será necessário:


3. Conceitos

Nesta seção, descreveremos os conceitos básicos utilizados na abordagem Orientada a Sujeitos. Mais tarde, iremos exemplificar a utilização destes conceitos e relacioná-los com a Orientação a Objetos.

Sujeito (Subject): percepção do mundo sob algum ponto de vista. Um Sujeito define também estados e comportamentos associados, de acordo com o ponto de vista. Exemplo: Bird.Tree, Assessor.Tree.

Ativação de sujeito (Subject Activation): uma instância de um sujeito, criada em tempo de execução, com seu estado definido.

Composição (Composition): um conjunto de sujeitos combinados, formando um grupo cooperativo. Exemplo: podemos combinar o sujeito Bird.Tree com o sujeito Woodsman.Tree para formar uma Composição Tree.

Regra de Composição (Composition rule): regra que especifica como os sujeitos serão compostos, por exemplo, como serão tratados comportamentos iguais de sujeitos diferentes e como compartilhar estados.

Identificador de Objeto (Object-identifier [oid]): a identificação única utilizada por um objeto para ser reconhecido pelos diferentes sujeitos. Exemplo: Existe um objeto Tree. Sua identificação é, por exemplo, “árvore do jardim”. Esta árvore é vista pelo pássaro através do sujeito Bird.Tree e pelo lenhador através do sujeito Woodsman.Tree. Ambos os sujeitos a conhecem, entretanto, como “árvore do jardim”.


4. Exemplo

Voltando ao exemplo da Árvore proposto na Introdução, vamos ver como pensaríamos em uma implementação Orientada a Sujeitos do mesmo:


Figura 2: Voltamos ao nosso exemplo

Utilizando Orientação a Sujeitos, cada uma das visões sobre a árvore seria um Sujeito. A junção destes criaria uma Composição, que seria a representação completa da árvore.

A Regra de Composição irá especificar como os estados e comportamentos de cada um dos Sujeitos separados deverão interagir quando estiverem juntos na Composição. Exemplos:


Figura 3: Uma visão Orientada a Sujeitos da modelagem da Árvore

A ativação destes Sujeitos (instanciação) irá gerar diversos objetos TREE, cada um com um identificador (oid) único:


Figura 4: Várias instâncias do Objeto TREE com oid's únicos


5. Ferramentas

A equipe de pesquisa da IBM que criou o conceito de Programação Orientada a Sujeitos está trabalhando em três ferramentas que permitem a programação utilizando esta abordagem:

Consulte o site da IBM sobre Programação Orientada a Sujeitos para maiores informações e links que indicam como obter estas ferramentas.


6. Como funciona

Vamos ver, rapidamente, como funciona o suporte à SOP para C++ desenvolvido pela IBM.

Nossa intenção aqui não é ensinar ninguém a programar utilizando Orientação a Sujeitos, mas apenas trazer uma noção a respeito do funcionamento da mesma. No site da IBM os autores disponibilizaram explicações detalhadas e exemplos com código completo em C++ para ensinar como utilizar seu suporte à SOP. Portanto, vamos dar uma olhada nos detalhes de cada um dos pontos descritos acima.

6.1. Definição do Sujeito

No namespace que define um Sujeito, devem ser descritas todas as Classes, atributos e métodos que este sujeito tem acesso, ou seja, a visão completa que este tem do mundo. A estrutura de classes criada dentro de um Sujeito poderá utilizar todas as vantagens da herança e encapsulamento de OO. Nem todas as classes que fazem parte de um Sujeito serão publicadas para o mundo. Além disso, os métodos podem ser apenas declarados, mas não implementados, em um Sujeito. Neste caso, sua implementação será criada na Composição.

6.2. Arquivo de Especificação de Sujeito

No arquivo de especificação de Sujeito são definidos que código pertence a qual sujeito. Isto é necessário porque nem tudo que está em um namespace deve virar sujeito. Além disso, as declarações fora de um namespace também podem virar um sujeito (não podem existir dois ou mais sujeitos sem namespace definido).

Este arquivo tem estrutura semelhante a um arquivo .INI do Windows, com Seções definidas entre colchetes e chaves que definem os atributos desta seção. Tem as seguintes partes:

O nome da Seção, entre colchetes, define o nome do Sujeito:
[ExampleSubject]

Escopo: define se um Sujeito está definido em um namespace, ou não. Neste caso, o escopo será definido como global, e todas as declarações de classes fora de namespaces serão consideradas como pertencentes a este sujeito.
scope=global   ou   scope=namespace

Quando o escopo é um namespace, o padrão é que este tenha o mesmo nome do Sujeito. Caso isto não ocorra, o nome do namespace pode ser explicitamente declarado na forma:
namespace=nome

Devem ser definidas quais classes em um Sujeito podem ser compostas:
composableclasses=x
onde x seria uma lista de classes separada por espaços ou * para todas.

Importação: um Sujeito pode importar declarações de outros, na forma
imports=x
onde x seria o nome de um Sujeito.
Também poderia ser utilizado global como nome do Sujeito, para importar declarações globais (por enquanto, só esta opção está sendo suportada pelo compilador).

Exemplo:

[originalPayroll]
scope=global
composableClasses=employee sales_person manager regular_emp sales_mgr

[affirmativeAction]
imports=global
composableClasses=employee

6.3. Subject Labels

A Composição dos Sujeitos não opera diretamente sobre o código em C++, e sim sobre uma descrição abstrata do programa, chamada de Subject Label (Rótulo do Sujeito). Estes labels são gerados no momento da compilação, antes da composição.

O Label de um Sujeito irá realizar a descrição do mesmo em termos de Operações, Classes e Mapeamentos, na forma:

Subject
    Operations
    Classes
        Instance-Variables
    Mapping
        (Class, Operation)
        Realization poset
            Realizations

As seções do Label têm o seguinte significado:

Exemplo:

Subject: PAYROLL
        Operations: Print()
        Classes: Employee
            with Instance Variables: _emplName;
        Mapping:
            Class Employee, Operation Print() implemented by:
            Realization poset with realization(s):
        
        &Employee::Print()

6.4. Arquivo de Regras de Composição

Este arquivo irá definir as Regras de Composição que dizem como a Composição deve ocorrer, lembrando-se que os nomes de Classes e Métodos utilizados nas Regras referem-se aos nomes gerados no Label.

Tipicamente, as regras serão do tipo:
“Componha todas as classes, seus atributos e métodos, quando tiverem nomes iguais”.

Também podem haver exceções, do tipo:
“No entanto, componha o método Employee.Print() do Sujeito Payroll com o método Employee.OutputEmployeeId() do Sujeito Personnel.

Geralmente, a sintaxe para escrever uma Regra de Composição é a seguinte:
NomeDaRegra (Resultado, <entrada1, entrada2, ...>)

Onde Resultado será o Sujeito Composto que será criado a partir da Composição das entradas.

Exemplo:

Equate(SALARY_REPORT, <PAYROLL, PERSONNEL>);
Correspond(operation SALARY_REPORT.Print, <PAYROLL.Print,
PERSONNEL.OutputEmployeeId>, <PAYROLL.Print>);

6.5. Regras de Composição

Regras de Correspondência: especificam correspondência entre elementos, mas não como devem ser combinados.

Regras de Combinação: especificam como combinar os elementos cuja correspondência já foi definida.

Regras de Correspondência/Combinação: definem ao mesmo tempo Correspondência e Combinação entre os elementos.

6.6. Exemplo

Iremos mostrar um Exemplo, descrito no site da IBM, de como duas equipes podem desenvolver funções complementares para os mesmos Objetos, de uma maneira independente.

Neste exemplo, cada equipe desenvolve uma aplicação completa por si só, mas que também pode ser combinada numa aplicação agregada.

A primeira equipe irá desenvolver um aplicativo de Folha de Pagamento (Payroll), que irá trabalhar sobre Empregados.
A segunda equipe irá desenvolver um aplicativo que será um Localizador de Empregados (Employee Locator).

Para simplificar, iremos assumir que as duas equipes compartilharam suas especificações durante o projeto e evitaram criar diferenças nas definições de atributos e operações. O compilador utilizado será o IBM VAC++ 4 com suporte a SOP.

A primeira equipe desenvolve seu aplicativo Payroll com as seguintes Classes:

E a segunda equipe desenvolve EmployeeLocator com as Classes:

Quem compõe as aplicações neste exemplo não são as equipes que desenvolveram as aplicações, e sim uma terceira Equipe que decide combiná-las. Analisando as partes, esta percebe que uma simples regra ByName e Merge irá trazer alguns resultados indesejáveis para a aplicação composta:

Primeiro, como existe um método Insert em cada Sujeito, a invocação da operação Insert na Composição iria resultar em execução deste método nos dois Sujeitos. Ou seja, o Empregado seria inserido na lista duas vezes.
O mesmo aconteceria com o método Print. A lista seria percorrida duas vezes, imprimindo empregados. Como cada Empregado existe em dobro, seria impresso quatro vezes.

A melhor solução para esta situação seria utilizar uma regra Override, mantendo apenas uma das implementações.

Neste caso, como a implementação de Employee_list da aplicação EmployeeLocator é mais completa, ela será mantida, sobrepondo (overriding) a implementação de Payroll.

Além dos arquivos normais necessários para um programa em C++ (os headers (.h) das classes e suas implementações (.cpp), assim como o arquivo de configuração do Projeto), devem ser criados um arquivo de definição de Sujeitos e um arquivo de Regras de Composição.

O arquivo de definição de Sujeitos (composed.sub) irá apenas definir os dois Sujeitos e declarar todas as suas Classes como Composable (podem ser compostas):

[payroll]
imports=global composableClasses=*
[locator]
imports=global composableClasses=*

O arquivo de Regras de Composição (composed.rul) irá trazer, como descrito acima, a correspondência por nome e a combinação Override:

ByNameMerge(composed, <locator, payroll>); Override(realizationset composed.insert.employee_list, <locator.insert.employee_list, payroll.insert.employee_list>);
Override(realizationset composed.print.employee_list , <locator.print.employee_list, payroll.print.employee_list >);

6.6.1. Resultados

A implementação descrita permite conseguir os seguintes objetivos:


7. Conclusões

7.1. Vantagens

7.2. Dificuldades

7.3. Conclusão

A Programação Orientada a Sujeitos traz muitas vantagens com relação a Orientação a Objetos no desenvolvimento de conjuntos de aplicações. No entanto, suas promessas de possibilidade de desenvolvimento de aplicações completamente separadas nos pareceram um tanto maiores do que o que realmente se obtém na aplicação desta abordagem.

Na prática, percebemos que o ideal é que haja um prévio acordo entre as partes que irão desenvolver os Sujeitos, seguindo alguns padrões comuns de nomenclatura, interfaces, semânticas, etc. Caso isto não ocorra, ainda assim a Composição é possível, mas o esforço poupado no desenvolvimento das partes pode resultar em um excesso de esforço na compreensão das mesmas para que seja possível criar as Regras de Composição para combinar Sujeitos muito diferentes. Este problema pode vir a inviabilizar economicamente ou tecnicamente a utilização da Programação Orientada a Sujeitos em alguns ambientes.


8. Novas Áreas

A equipe de pesquisas da IBM tem prosseguido em seus trabalhos, e já tem em mente duas novas abordagens, que têm o propósito de estender ainda mais a Orientação a Sujeitos.


9. Maiores informações


Autores da Pesquisa: Emeline Büchele Regis, Gustavo Fortes Tondello e Ronnie Fagundes de Brito
Universidade Federal de Santa Catarina - UFSC - Graduação em Sistemas de Informação - março/2002