Encontrei essa citação em " A alegria de Clojure " na p. 32, mas alguém me disse a mesma coisa durante o jantar na semana passada e também ouvi outros lugares:
[A] desvantagem da programação orientada a objetos é o forte acoplamento entre função e dados.
Entendo por que o acoplamento desnecessário é ruim em um aplicativo. Também me sinto confortável em dizer que o estado mutável e a herança devem ser evitados, mesmo na Programação Orientada a Objetos. Mas não vejo por que manter funções em classes é inerentemente ruim.
Quero dizer, adicionar uma função a uma classe parece marcar um email no Gmail ou colar um arquivo em uma pasta. É uma técnica organizacional que ajuda a encontrá-lo novamente. Você escolhe alguns critérios e depois junta as coisas. Antes da OOP, nossos programas eram praticamente grandes pacotes de métodos em arquivos. Quero dizer, você tem que colocar funções em algum lugar. Por que não organizá-los?
Se este é um ataque velado aos tipos, por que eles simplesmente não dizem que restringir o tipo de entrada e saída a uma função está errado? Não tenho certeza se posso concordar com isso, mas pelo menos estou familiarizado com os argumentos a favor e contra o tipo segurança. Isso me parece uma preocupação principalmente separada.
Claro, às vezes as pessoas entendem errado e colocam a funcionalidade na classe errada. Mas comparado a outros erros, isso parece um inconveniente muito pequeno.
Portanto, Clojure tem namespaces. Como a colocação de uma função em uma classe no OOP é diferente da colocação de uma função em um espaço para nome no Clojure e por que é tão ruim? Lembre-se de que funções em uma classe não necessariamente operam apenas nos membros dessa classe. Veja java.lang.StringBuilder - ele opera em qualquer tipo de referência, ou através de boxing automático, em qualquer tipo.
PS Esta citação faz referência a um livro que eu não li: Programação Multiparadigmática em Leda: Timothy Budd, 1995 .
Respostas:
Em teoria, o acoplamento flexível de dados de função facilita a adição de mais funções para trabalhar com os mesmos dados. O lado negativo é que torna mais difícil alterar a estrutura de dados, razão pela qual, na prática, o código funcional bem projetado e o código OOP bem projetado têm níveis muito semelhantes de acoplamento.
Tome um gráfico acíclico direcionado (DAG) como uma estrutura de dados de exemplo. Na programação funcional, você ainda precisa de alguma abstração para evitar se repetir; portanto, você criará um módulo com funções para adicionar e excluir nós e arestas, encontrar nós acessíveis a partir de um determinado nó, criar uma classificação topológica etc. são efetivamente acoplados aos dados, mesmo que o compilador não os imponha. Você pode adicionar um nó da maneira mais difícil, mas por que você deseja? A coesão em um módulo impede um acoplamento rígido em todo o sistema.
Por outro lado, no lado da OOP, quaisquer funções que não sejam as operações básicas do DAG serão executadas em classes "view" separadas, com o objeto DAG passado como parâmetro. É tão fácil adicionar quantas visualizações você deseja que operem nos dados do DAG, criando o mesmo nível de dissociação de dados de função que você encontraria no programa funcional. O compilador não impedirá que você coloque tudo em uma classe, mas seus colegas o farão.
A mudança de paradigmas de programação não altera as práticas recomendadas de abstração, coesão e acoplamento, apenas altera as práticas que o compilador ajuda a impor. Na programação funcional, quando você deseja o acoplamento de dados de função, ele é imposto pelo acordo de cavalheiros e não pelo compilador. No OOP, a separação de exibição de modelo é imposta pelo acordo de cavalheiros e não pelo compilador.
fonte
Caso você ainda não o conhecesse, siga essa percepção: Os conceitos de orientação a objeto e fechamento são dois lados da mesma moeda. Dito isto, o que é um fechamento? Ele pega variáveis ou dados do escopo circundante e se liga a ele dentro da função, ou de uma perspectiva de OO, você efetivamente faz a mesma coisa quando, por exemplo, passa algo para um construtor para que mais tarde você possa usá-lo. dados em uma função membro dessa instância. Mas tirar coisas do escopo circundante não é uma coisa agradável a se fazer - quanto maior o escopo ao redor, mais mal é fazer isso (embora, pragmaticamente, muitas vezes seja necessário algum mal para realizar o trabalho). O uso de variáveis globais está levando isso ao extremo, onde funções em um programa estão usando variáveis no escopo do programa - realmente muito mal. temboas descrições em outros lugares sobre por que as variáveis globais são más.
Se você segue as técnicas de OO, basicamente já aceita que todos os módulos do seu programa terão um certo nível mínimo de mal. Se você adotar uma abordagem funcional da programação, está buscando um ideal em que nenhum módulo em seu programa contenha mal de fechamento, embora você ainda possa ter algum, mas será muito menor que OO.
Essa é a desvantagem do OO - ele encoraja esse tipo de mal, acoplamento de dados a funcionar através da padronização de fechamentos (um tipo de teoria da programação de janelas quebradas ).
O único lado positivo é que, se você sabia que iria usar muitos fechamentos para começar, o OO ao menos fornece uma estrutura idealógica para ajudar a organizar essa abordagem para que o programador médio possa entendê-la. Em particular, as variáveis que estão sendo encerradas são explícitas no construtor, e não apenas consideradas implicitamente no fechamento de uma função. Programas funcionais que usam muitos fechamentos geralmente são mais enigmáticos que o programa OO equivalente, embora não necessariamente menos elegantes :)
fonte
É sobre acoplamento de tipo :
Uma função incorporada a um objeto para trabalhar nesse objeto não pode ser usada em outros tipos de objetos.
No Haskell, você escreve funções para trabalhar com classes de tipos - portanto, existem muitos tipos diferentes de objetos com os quais qualquer função pode funcionar, desde que seja um tipo da classe em que a função funciona.
As funções independentes permitem que esse dissociação, que você não obtém ao se concentrar em escrever suas funções, funcione dentro do tipo A, porque você não poderá usá-las se não tiver uma instância do tipo A, mesmo que a função possa caso contrário, seja geral o suficiente para ser usado em uma instância do tipo B ou do tipo C.
fonte
Em Java e encarnações semelhantes do OOP, os métodos de instância (ao contrário das funções livres ou dos métodos de extensão) não podem ser adicionados a partir de outros módulos.
Isso se torna mais uma restrição quando você considera interfaces que só podem ser implementadas pelos métodos da instância. Você não pode definir uma interface e uma classe em módulos diferentes e, em seguida, usar o código de um terceiro módulo para uni-los. Uma abordagem mais flexível, como as classes de tipo de Haskell, deve ser capaz de fazer isso.
fonte
A Orientação a Objetos é fundamentalmente sobre Abstração de Dados Procedimentais (ou Abstração de Dados Funcionais, se você remover efeitos colaterais que são uma questão ortogonal). De certa forma, o Lambda Calculus é a linguagem orientada a objetos mais antiga e mais pura, pois fornece apenas a Abstração de Dados Funcionais (porque não possui nenhuma construção além das funções).
Somente as operações de um único objeto podem inspecionar a representação de dados desse objeto. Nem mesmo outros objetos do mesmo tipo podem fazer isso. (Essa é a principal diferença entre abstração de dados orientada a objeto e tipos de dados abstratos: com ADTs, objetos do mesmo tipo podem inspecionar a representação de dados um do outro, apenas a representação de objetos de outros tipos está oculta.)
O que isso significa é que vários objetos do mesmo tipo podem ter diferentes representações de dados. Mesmo o mesmo objeto pode ter representações de dados diferentes em momentos diferentes. (Por exemplo, em Scala,
Map
s eSet
s alternam entre uma matriz e um hash trie, dependendo do número de elementos, porque, para números muito pequenos, a pesquisa linear em uma matriz é mais rápida que a pesquisa logarítmica em uma árvore de pesquisa devido aos fatores constantes muito pequenos .)Do lado de fora de um objeto, você não deveria, não pode conhecer sua representação de dados. Esse é o oposto do acoplamento rígido.
fonte
O acoplamento rígido entre dados e funções é ruim porque você deseja alterar um independentemente do outro e o acoplamento rígido torna isso difícil, porque você não pode alterar um sem o conhecimento e, possivelmente, mudar para o outro.
Você deseja que dados diferentes apresentados à função não exijam alterações na função e da mesma forma que você deseja fazer alterações na função sem precisar de alterações nos dados nos quais está operando para suportar essas alterações.
fonte