Por que o “forte acoplamento entre funções e dados” é ruim?


38

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 .


20
Acredito que o escritor simplesmente não entendeu OOP corretamente e só precisava de mais um motivo para dizer que Java é ruim e Clojure é bom. / rant
Eufórico

6
Métodos de instância (ao contrário de funções livres ou métodos de extensão) não podem ser adicionados 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 haskell, deve ser capaz de fazer isso.
CodesInChaos

4
@Euphoric Eu acredito que o escritor se entender, mas a comunidade Clojure parece gostar de fazer um homem de palha de OOP e queimá-lo como uma efígie para todos os males da programação antes tivemos uma boa coleta de lixo, muita memória, processadores rápidos, e muito espaço em disco. Eu gostaria que eles parassem de bater no OOP e visassem as causas reais: arquitetura Von Neuman, por exemplo.
precisa saber é o seguinte

4
Minha impressão é que a maioria das críticas ao OOP é na verdade críticas ao OOP implementadas em Java. Não porque seja um homem de palha deliberado, mas porque é o que eles associam à OOP. Existem problemas bastante semelhantes com pessoas reclamando sobre digitação estática. A maioria dos problemas não é inerente ao conceito, mas apenas falha na implementação popular desse conceito.
CodesInChaos

3
Seu título não corresponde ao corpo da sua pergunta. É fácil explicar por que o acoplamento apertado de funções e dados é ruim, mas o seu texto faz as perguntas "OOP faz isso?", "Se sim, por quê?" e "Isso é uma coisa ruim?". Até agora, você teve a sorte de receber respostas que tratam de uma ou mais dessas três perguntas e nenhuma assume a pergunta mais simples do título.
itsbruce

Respostas:


34

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.


13

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 :)


8
Citação do dia: "muitas vezes é necessário algum mal para realizar o trabalho"
GlenPeterson

5
Você realmente não explicou por que as coisas que chama de más são más; você está apenas chamando-os de maus. Explique por que eles são maus e você pode ter uma resposta para a pergunta do cavalheiro.
Robert Harvey

2
Seu último parágrafo salva a resposta. Pode ser o único lado positivo, de acordo com você, mas isso não é algo pequeno. Nós, os chamados "programadores médios", na verdade dão boas-vindas a uma certa quantidade de cerimônia, certamente o suficiente para nos informar o que diabos está acontecendo.
Robert Harvey

Se OO e fechamento são sinônimos, por que tantos idiomas OO falharam em fornecer suporte explícito a eles? A página wiki C2 que você cita possui ainda mais disputas (e menos consenso) do que o normal para esse site.
itsbruce 25/09

1
@itsbruce Eles são feitos em grande parte desnecessários. As variáveis ​​que seriam "fechadas" tornam-se variáveis ​​de classe passadas para o objeto.
Izkata 25/09

7

É 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.


3
Não é esse o objetivo das interfaces? Para fornecer as coisas que permitem que o tipo B e o tipo C pareçam iguais à sua função, para que ele possa operar em mais de um tipo?
usar o seguinte código

2
@ Random832 absolutamente, mas por que incorporar uma função dentro de um tipo de dados se não funcionar com esse tipo de dados? A resposta: É o único motivo para incorporar uma função em um tipo de dados. Você poderia escrever nada além de classes estáticas e fazer com que todas as suas funções não se importassem com o tipo de dados em que estão encapsuladas para torná-las completamente dissociadas do tipo de propriedade, mas por que se incomodar em colocá-las em um tipo? A abordagem funcional diz: Não se preocupe, escreva suas funções para trabalhar com interfaces de tipos e, em seguida, não há razão para encapsulá-las com seus dados.
Jimmy Hoffa

Você ainda precisa implementar as interfaces.
Random832

2
@ Random832 as interfaces são tipos de dados; eles não precisam de funções encapsuladas neles. Com funções livres, todas as interfaces precisam exaltar os dados que eles disponibilizam para as funções trabalharem.
Jimmy Hoffa 26/09

2
@ Random832 para se relacionar com objetos do mundo real, como é tão comum em OO, pense na interface de um livro: ele apresenta informações (dados), é tudo. Você tem a função gratuita de virar a página, que funciona contra a classe de tipos que têm páginas. Essa função funciona contra todos os tipos de livros, jornais, aqueles fusos de pôsteres na K-Mart, cartões comemorativos, correio, qualquer coisa grampeada no canto. Se você implementou a página de abertura como membro do livro, perdeu todas as coisas em que poderia usar a página de abertura, pois, como uma função livre, ela não é vinculada; apenas lança uma PartyFoulException na cerveja.
Jimmy Hoffa

4

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.


Você pode fazer isso facilmente no Scala. Eu não estou familiarizado com o Go, mas o AFAIK também pode ser feito lá. No Ruby, também é uma prática bastante comum adicionar métodos a objetos após o fato para fazê-los se adaptar a alguma interface. O que você descreve parece um sistema de tipo mal projetado do que qualquer coisa remotamente relacionada ao OO. Assim como um experimento mental: como sua resposta seria diferente ao falar sobre tipos abstratos de dados em vez de objetos? Não acredito que isso faça alguma diferença, o que provaria que seu argumento não está relacionado ao OO.
Jörg W Mittag

1
@ JörgWMittag Acho que você quis dizer tipos de dados algébricos. E CodesInChaos, Haskell explicitamente desencoraja o que você está sugerindo. É chamado de instância órfã e emite avisos no GHC.
precisa saber é o seguinte

3
@ JörgWMittag Minha impressão é que muitos que criticam a OOP criticam a forma da OOP usada em Java e linguagens similares com sua estrutura de classe rígida e focam nos métodos de instância. Minha impressão dessa citação é que ela critica o foco nos métodos de instância e realmente não se aplica a outros tipos de OOP, como o golang usa.
CodesInChaos

2
@CodesInChaos Então talvez esclarecer isso como "OO baseada classe estática"
Daniel Gratzer

@jozefg: Eu estou falando sobre tipos abstratos de dados. Eu nem vejo como os tipos de dados algébricos são remotamente relevantes para esta discussão.
Jörg W Mittag

3

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, Maps e Sets 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.


Eu tenho classes no OOP que alternam estruturas de dados internas, dependendo das circunstâncias, para que instâncias de objetos dessas classes possam usar representações de dados muito diferentes ao mesmo tempo. Esconder e encapsular dados básicos, eu diria? Então, como o Map in Scala é diferente de uma classe Map implementada corretamente (ocultamento e encapsulamento de dados errados) em uma linguagem OOP?
Marjan Venema

No seu exemplo, encapsular seus dados com funções de acessador em uma classe (e assim acoplá-las firmemente a esses dados) permite realmente acoplar livremente instâncias dessa classe ao restante do seu programa. Você está refutando o ponto central da citação - muito bom!
GlenPeterson

2

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.


1
Sim eu quero isso. Mas minha experiência é que, quando você envia dados para uma função não trivial que não foi explicitamente projetada para lidar, essa função tende a quebrar. Não estou me referindo apenas ao tipo de segurança, mas a qualquer condição de dados que não foi antecipada pelo (s) autor (es) da função. Se a função for antiga e frequentemente usada, qualquer alteração que permita que novos dados fluam por ela provavelmente quebrará para alguma forma antiga de dados que ainda precise funcionar. Embora a dissociação possa ser o ideal para funções versus dados, a realidade dessa dissociação pode ser difícil e perigosa.
precisa saber é o seguinte
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.