2.4 Múltiplas Representações para Dados Abstratos
Introduzimos a abstração de dados, uma metodologia para estruturar sistemas de tal forma que grande parte de um programa possa ser especificada independentemente das escolhas envolvidas na implementação dos objetos de dados que o programa manipula. A ideia chave foi erguer uma barreira de abstração—neste caso, os seletores e construtores para números racionais (make_rat, numer, denom)—que isola a maneira como os números racionais são usados de sua representação subjacente em termos de estrutura de lista.
Essas barreiras de abstração de dados são ferramentas poderosas para controlar a complexidade. Ao isolar as representações subjacentes dos objetos de dados, podemos dividir a tarefa de projetar um programa grande em tarefas menores que podem ser executadas separadamente. Mas esse tipo de abstração de dados ainda não é poderoso o suficiente, porque nem sempre faz sentido falar de "a representação subjacente" para um objeto de dados.
Por uma coisa, pode haver mais de uma representação útil para um objeto de dados, e podemos gostar de projetar sistemas que possam lidar com múltiplas representações. Para tomar um exemplo simples, números complexos podem ser representados de duas maneiras quase equivalentes: na forma retangular (partes real e imaginária) e na forma polar (magnitude e ângulo). Às vezes a forma retangular é mais apropriada e às vezes a forma polar é mais apropriada. Na verdade, é perfeitamente plausível imaginar um sistema no qual os números complexos são representados de ambas as maneiras, e no qual as funções para manipular números complexos funcionam com qualquer representação.
Mais importante ainda, sistemas de programação são frequentemente projetados por muitas pessoas trabalhando por períodos de tempo estendidos, sujeitos a requisitos que mudam com o tempo. Em tal ambiente, simplesmente não é possível que todos concordem antecipadamente com escolhas de representação de dados. Então, além das barreiras de abstração de dados que isolam a representação do uso, precisamos de barreiras de abstração que isolem diferentes escolhas de design umas das outras e permitam que diferentes escolhas coexistam em um único programa. Além disso, como programas grandes são frequentemente criados pela combinação de módulos preexistentes que foram projetados isoladamente, precisamos de convenções que permitam aos programadores incorporar módulos em sistemas maiores aditivamente, ou seja, sem ter que reprojetar ou reimplementar esses módulos.
Nesta seção, aprenderemos como lidar com dados que podem ser representados de maneiras diferentes por diferentes partes de um programa. Isso requer a construção de funções genéricas—funções que podem operar em dados que podem ser representados de mais de uma maneira. Nossa principal técnica para construir funções genéricas será trabalhar em termos de objetos de dados que têm type tags (etiquetas de tipo), ou seja, objetos de dados que incluem informações explícitas sobre como devem ser processados. Também discutiremos programação orientada a dados, uma estratégia de implementação poderosa e conveniente para montagem aditiva de sistemas com operações genéricas.
Começamos com o exemplo simples de números complexos. Veremos como type tags e estilo orientado a dados nos permitem projetar representações retangular e polar separadas para números complexos, mantendo a noção de um objeto de dados abstrato de "número complexo". Conseguiremos isso definindo funções aritméticas para números complexos (add_complex, sub_complex, mul_complex, div_complex) em termos das operações genéricas add, sub, mul e div. A estratégia orientada a dados resultante é ilustrada por meio de um pacote para realizar aritmética simbólica em polinômios.