“함수와 데이터 간의 긴밀한 결합”이 나쁜 이유는 무엇입니까?


38

나는이 인용문을 " The Clojure의 기쁨 "에서 찾을 수있다 . 32, 그러나 누군가 지난 주 저녁에 나에게 똑같은 것을 말했고 나는 다른 곳에서도 들었습니다.

[A] 객체 지향 프로그래밍의 단점은 함수와 데이터 사이의 긴밀한 연결입니다.

응용 프로그램에서 불필요한 결합이 나쁜 이유를 이해합니다. 또한 객체 지향 프로그래밍에서도 가변 상태와 상속을 피해야한다는 것이 편안합니다. 그러나 클래스에서 함수를 고수하는 것이 본질적으로 나쁜 이유를 알지 못합니다.

클래스에 함수를 추가하면 Gmail에서 메일에 태그를 지정하거나 폴더에 파일을 고정시키는 것처럼 보입니다. 다시 찾는 데 도움이되는 조직 기술입니다. 당신은 몇 가지 기준을 고른 다음에 같은 것을 합치십시오. OOP 이전에, 우리 프로그램은 파일에 많은 방법이있었습니다. 내 말은, 당신은 어딘가에 기능을 넣어야한다는 것을 의미합니다. 왜 정리하지 않습니까?

이것이 유형에 대한 가려진 공격이라면 입력 및 출력 유형을 함수로 제한하는 것이 잘못되었다고 말하는 이유는 무엇입니까? 나는 그것에 동의 할 수 있는지 확실하지 않지만 적어도 찬반론과 안전 유형 논쟁에 익숙합니다. 이것은 주로 별개의 문제처럼 들립니다.

물론, 때때로 사람들은 그것을 잘못 이해하고 잘못된 수업에 기능을 추가합니다. 그러나 다른 실수와 비교할 때 이것은 약간의 불편 함처럼 보입니다.

따라서 Clojure에는 네임 스페이스가 있습니다. OOP의 클래스에서 함수를 고수하는 것은 Clojure의 네임 스페이스에서 함수를 고수하는 것과 어떻게 다르며 왜 그렇게 나쁩니 까? 클래스의 함수가 반드시 해당 클래스의 멤버에서만 작동하는 것은 아닙니다. java.lang.StringBuilder를보십시오-모든 참조 유형 또는 자동 상자를 통해 모든 유형에서 작동합니다.

PS이 인용문은 내가 읽지 않은 책을 참조한다 : Leda : Timothy Budd, 1995의 Multiparadigm Programming .


20
필자는 작가가 OOP를 제대로 이해하지 못했으며 Java가 나쁘고 Clojure가 좋다고 말하는 또 다른 이유가 필요하다고 생각합니다. / rant
행복감

6
다른 모듈에서는 인스턴스 메소드 (자유 기능 또는 확장 메소드와 달리)를 추가 할 수 없습니다. 인스턴스 메소드로만 구현할 수있는 인터페이스를 고려할 때 이는 더 많은 제한 사항이됩니다. 다른 모듈에서 인터페이스와 클래스를 정의한 다음 세 번째 모듈의 코드를 사용하여 서로 바인딩 할 수 없습니다. 하스켈의 타입 클래스와 같은보다 유연한 접근 방식이 가능합니다.
코드 InChaos

4
@Euphoric 필자는 작가 이해 했다고 생각 하지만 Clojure 커뮤니티는 OOP의 밀짚 꾼 을 만들고 쓰레기를 잘 모으고 메모리가 많으며 프로세서가 빠르기 전에 프로그래밍의 모든 악의에 대한 타협 으로 만들고 싶어합니다. 많은 디스크 공간. 나는 그들이 OOP에 박동을 멈추고 실제 원인, 즉 Von Neuman 아키텍처를 목표로 삼기를 바랍니다.
GlenPeterson

4
필자는 OOP에 대한 대부분의 비판이 실제로 Java로 구현 된 OOP에 대한 비판이라고 생각합니다. 고의적 인 짚맨이 아니라 OOP와 관련이 있기 때문입니다. 정적 타이핑에 대해 불평하는 사람들과 비슷한 문제가 있습니다. 대부분의 문제는 개념에 내재 된 것이 아니라 그 개념의 대중적인 구현에 결함이 있습니다.
코드 InChaos

3
제목이 질문의 본문과 일치하지 않습니다. 함수와 데이터의 긴밀한 결합이 나쁜 이유를 쉽게 설명 할 수 있지만 텍스트에 "OOP가 그렇게합니까?", "그렇다면 왜 그렇습니까?" "이것이 나쁜가요?" 지금까지, 당신은이 세 가지 질문 중 하나 이상을 다루는 답변을받을만큼 운이 좋았으며 제목에서 더 간단한 질문을 가정하지 않았습니다.
itsbruce

답변:


34

이론적으로 함수 데이터 결합이 느슨하면 동일한 데이터를 처리하기 위해 더 많은 함수를 쉽게 추가 할 수 있습니다. 단점은 데이터 구조 자체를 변경하기가 더 어렵 기 때문에 실제로 잘 설계된 기능 코드와 잘 설계된 OOP 코드는 매우 유사한 수준의 커플 링을 갖기 때문입니다.

데이터 구조의 예로 DAG (directed acyclic graph)를 사용하십시오. 함수형 프로그래밍에서는 여전히 반복하지 않기 위해 추상화가 필요하므로 노드와 에지를 추가 및 삭제하고 지정된 노드에서 도달 가능한 노드를 찾고 토폴로지 정렬을 만드는 기능이있는 모듈을 만들어야합니다. 컴파일러가 데이터를 적용하지 않더라도 효과적으로 데이터에 밀접하게 연결됩니다. 어려운 방법으로 노드를 추가 할 수 있지만 왜 원하는가? 하나의 모듈 내에서 응집력은 시스템 전체에서 긴밀한 결합을 방지합니다.

반대로 OOP 측에서는 기본 DAG 작업 이외의 기능은 별도의 "보기"클래스에서 수행되며 DAG 개체는 매개 변수로 전달됩니다. DAG 데이터에서 작동하는 뷰를 원하는만큼 추가하여 기능 프로그램에서와 동일한 수준의 기능 데이터 분리를 생성 할 수 있습니다. 컴파일러는 모든 것을 하나의 클래스로 만들지 못하게하지만 동료는 그렇게합니다.

프로그래밍 패러다임을 변경해도 추상화, 응집력 및 결합에 대한 모범 사례는 변경되지 않으며 컴파일러가 적용하는 데 도움이되는 방식 만 변경됩니다. 함수형 프로그래밍에서 함수-데이터 결합을 원할 때는 컴파일러가 아니라 신사의 동의하에 시행됩니다. OOP에서 모델 뷰 분리는 컴파일러가 아니라 신사 동의에 의해 시행됩니다.


13

이미이 통찰력이 필요하다는 것을 모르는 경우 : 객체 지향 및 클로저 의 개념은 동일한 동전의 양면입니다. 즉, 폐쇄는 무엇입니까? 주변 범위에서 변수 또는 데이터를 가져 와서 함수 내에서 바인딩하거나 OO 관점에서 예를 들어 생성자에 무언가를 전달하여 나중에 사용할 수 있도록 동일한 일을 효과적으로 수행 할 때 해당 인스턴스의 멤버 함수에있는 데이터 조각 그러나 주변 범위에서 물건을 가져가는 것은 좋은 일이 아닙니다. 주변 범위가 클수록이 작업을 수행하는 것이 더 나쁩니다 (실용적으로 작업을 수행하려면 일부 악이 필요합니다). 전역 변수의 사용은 이것을 프로그램의 함수가 프로그램 범위에서 변수를 사용하는 극단적으로 가져옵니다. 있다전역 변수가 악한 이유에 대한 다른 설명 .

OO 기술을 따르는 경우 기본적으로 프로그램의 모든 모듈에 최소한의 악의가 있음을 이미 동의합니다. 프로그래밍에 대한 기능적 접근 방식을 취한다면 프로그램에 모듈에 클로저 악이 포함되지 않지만 이상이 있지만 OO보다 훨씬 적은 이상적인 것을 목표로 삼고 있습니다.

그것은 OO의 단점입니다. 클로저를 표준 ( 프로그래밍 된 깨진 창 이론) 로 만들어서 이런 종류의 악한 데이터를 결합하여 기능을 수행하도록 장려합니다 .

유일한 장점은, 처음에 많은 클로저를 사용할 것이라는 것을 알고 있다면 최소한 OO는 일반적인 프로그래머가 이해할 수 있도록 접근 방식을 구성하는 데 도움이되는 아이디어 프레임 워크를 제공한다는 것입니다. 특히 닫히는 변수는 함수 클로저에서 암시 적으로 취하는 것이 아니라 생성자에서 명시 적입니다. 클로저를 많이 사용하는 기능적 프로그램은 종종 동등하지 않은 OO 프로그램보다 더 복잡합니다.


8
오늘의 인용문 : "일을하기 위해서는 종종 악이 필요하다"
GlenPeterson

5
당신은 악이라고 부르는 것이 악한 설명하지 않았습니다 . 당신은 단지 그들을 악이라고 부릅니다. 왜 그들이 악한 지 설명하고 신사의 질문에 대한 답을 얻을 수 있습니다.
Robert Harvey

2
마지막 단락은 답을 저장합니다. 당신에 따르면 유일한 장점 일지 모르지만 그것은 작은 일이 아닙니다. 소위 "평균 프로그래머"라고 불리는 우리는 실제로 어느 정도의 의식을 환영합니다.
Robert Harvey

OO와 클로저가 동의어 인 경우 왜 그렇게 많은 OO 언어가 명시 적으로 지원하지 않았습니까? 당신이 인용 한 C2 위키 페이지는 그 사이트에서 일반적인 것보다 훨씬 더 많은 논쟁을 불러 일으켰습니다.
itsbruce

1
@itsbruce 크게 불필요합니다. "닫힌"변수는 대신 객체로 전달되는 클래스 변수가됩니다.
이즈 카타

7

타입 커플 링 에 관한 것입니다 .

해당 객체에서 작동하도록 객체에 내장 된 기능은 다른 유형의 객체에서 사용할 수 없습니다.

Haskell에서는 타입 클래스 에 대해 작동하는 함수를 작성합니다. 따라서 함수가 작동하는 주어진 클래스 의 유형이라면 주어진 함수가 작동 할 수있는 다양한 유형의 객체가 있습니다 .

독립형 함수를 사용하면 유형 A 내부에서 작동하도록 함수를 작성하는 데 집중할 때 얻을 수없는 분리가 가능하므로 유형 A 인스턴스가없는 경우에는 함수가 그렇지 않으면 유형 B 인스턴스 또는 유형 C 인스턴스에서 사용하기에 충분히 일반적입니다.


3
인터페이스의 요점이 아닌가? 유형 B와 유형 C가 함수와 동일하게 보이는 것을 제공하여 둘 이상의 유형에서 작동 할 수 있습니까?
Random832

2
@ Random832 절대적으로, 왜 해당 데이터 유형으로 작동하지 않는 경우 데이터 유형 안에 함수를 포함합니까? 답 : 데이터 유형에 함수 포함시키는 유일한 이유 입니다. 정적 클래스 만 작성 하고 캡슐화 된 데이터 유형에 신경 쓰지 않고 모든 유형 의 함수를 소유 유형에서 완전히 분리 할 수는 있지만 왜 유형에 넣는 것을 귀찮게 할 수 있습니까? 기능적 접근 방식은 다음과 같이 말합니다. 귀찮게하지 말고, 일종의 인터페이스로 작동하도록 함수를 작성하십시오. 그런 다음 데이터로 캡슐화 할 이유가 없습니다.
Jimmy Hoffa

여전히 인터페이스를 구현해야합니다.
Random832

2
@ Random832 인터페이스는 데이터 유형입니다. 그것들은 그 안에 캡슐화 된 함수가 필요하지 않습니다. 무료 기능을 사용하면 기능을 작동시키기 위해 사용할 수있는 데이터가 무엇인지 추출해야합니다.
Jimmy Hoffa

2
@ Random832는 실제 객체와 관련이 있기 때문에 OO에서 흔히 볼 수있는 책의 인터페이스를 생각해보십시오. 정보 (데이터)를 제시하는 것뿐입니다. 당신은 페이지가있는 유형의 클래스에 대해 작동하는 무료 회전 페이지 기능을 가지고 있습니다.이 기능은 모든 종류의 책, 신문, K-Mart의 포스터 스핀들, 인사말 카드, 우편물, 모서리. 당신이 책의 일원으로서 턴 페이지를 구현했다면, 당신은 당신이 할 수있는 모든 것을 놓치지 않는 자유 함수로서 놓칩니다; 맥주에 PartyFoulException을 던집니다.
Jimmy Hoffa

4

Java 및 이와 유사한 OOP의 구현에서는 다른 모듈에서 인스턴스 메소드 (자유 함수 또는 확장 메소드와 달리)를 추가 할 수 없습니다.

인스턴스 메소드로만 구현할 수있는 인터페이스를 고려할 때 이는 더 많은 제한 사항이됩니다. 다른 모듈에서 인터페이스와 클래스를 정의한 다음 세 번째 모듈의 코드를 사용하여 서로 바인딩 할 수 없습니다. Haskell의 유형 클래스와 같은보다 유연한 접근 방식이 가능합니다.


스칼라에서 쉽게 할 수 있습니다. Go에 익숙하지는 않지만 AFAIK를 사용하면 거기서 할 수도 있습니다. 루비에서는 객체를 인터페이스에 맞추기 위해 객체에 메소드를 추가하는 것이 일반적입니다. 당신이 설명하는 것은 OO와 원격으로 관련된 것보다 잘못 설계된 유형 시스템처럼 보입니다. 생각 실험과 마찬가지로 객체 대신 추상 데이터 유형에 대해 이야기 할 때 어떻게 답이 달라 집니까? 나는 그것이 당신의 주장이 OO와 관련이 없다는 것을 증명할 어떤 차이를 만들 것이라고 믿지 않습니다.
Jörg W Mittag

1
@ JörgWMittag 나는 당신이 대수 데이터 타입을 의미한다고 생각합니다. 코드 InChaos에서 Haskell은 귀하의 제안을 명시 적으로 권장하지 않습니다. 분리 된 인스턴스라고하며 GHC에 경고를 발행합니다.
jozefg

3
@ JörgWMittag 저의 인상은 OOP를 비판하는 많은 사람들이 강건한 클래스 구조와 인스턴스 메소드에 중점을 둔 Java 및 유사한 언어에서 사용되는 OOP의 형태를 비판한다는 것입니다. 이 인용문에 대한 인상은 인스턴스 메소드에 중점을 두며 골 랑이 사용하는 것과 같은 다른 OOP 풍미에는 실제로 적용되지 않는다는 것입니다.
코드 InChaos

2
@CodesInChaos은 아마도 "정적 클래스를 기반으로 OO"로이 문제를 명확히
jozefg

@ jozefg : 추상 데이터 형식에 대해 이야기하고 있습니다. 대수 데이터 형식이이 토론과 원격으로 어떻게 관련되어 있는지조차 알지 못합니다.
Jörg W Mittag

3

객체 지향은 기본적으로 절차 데이터 추상화 (또는 직교 문제인 부작용을 제거하는 경우 기능적 데이터 추상화)에 관한 것입니다. 어떤 의미에서, Lambda Calculus는 함수형 데이터 추상화 제공하기 때문에 가장 오래되고 가장 순수한 객체 지향 언어입니다 (함수 이외의 구문이 없기 때문에).

단일 객체 의 조작 만 해당 객체의 데이터 표현을 검사 할 수 있습니다. 같은 유형 의 다른 객체조차 그렇게 할 수 없습니다. (이것은 객체 지향 데이터 추상화와 추상 데이터 유형의 주요 차이점입니다. ADT를 사용하면 동일한 유형의 객체가 서로의 데이터 표현을 검사 할 수 있으며 다른 유형 의 객체 표현 만 숨겨집니다.)

이것은 동일한 유형의 여러 객체가 다른 데이터 표현을 가질 수 있음을 의미합니다. 동일한 객체라도 다른 시간에 다른 데이터 표현을 가질 수 있습니다. (예를 들어, 스칼라에서 Maps와 Sets는 요소의 수에 따라 배열과 해시 트리 간을 전환합니다. 매우 작은 수의 경우 배열의 선형 검색이 매우 작은 상수 요인으로 인해 검색 트리의 로그 검색보다 빠르기 때문에 .)

객체 외부에서 데이터 표현을 알 수 없습니다 . 그것은 타이트한 커플 링 의 반대 입니다.


상황에 따라 내부 데이터 구조를 전환하는 OOP 클래스가 있으므로 이러한 클래스의 객체 인스턴스가 매우 다른 데이터 표현을 동시에 사용할 수 있습니다. 내가 말할 기본 데이터 숨기기 및 캡슐화? 그렇다면 Scala의 Map은 OOP 언어로 올바르게 구현 된 (wrt 데이터 숨기기 및 캡슐화) Map 클래스와 어떻게 다릅니 까?
Marjan Venema

예를 들어, 클래스의 접근 자 함수로 데이터를 캡슐화하면 (따라서 해당 함수를 해당 데이터에 밀접하게 연결) 실제로 해당 클래스의 인스턴스를 나머지 프로그램과 느슨하게 연결할 수 있습니다. 당신은 견적의 중심점을 반박하고 있습니다-아주 좋습니다!
GlenPeterson

2

서로 독립적으로 변경할 수 있기를 원하기 때문에 데이터와 기능 사이의 긴밀한 결합은 나쁘고, 긴밀한 결합은이를 알지 못하고 다른 것으로 변경할 수 없기 때문에이를 어렵게 만듭니다.

함수에 다른 데이터를 표시하여 함수를 변경할 필요가 없으며, 기능 변경을 지원하기 위해 작동중인 데이터를 변경할 필요없이 함수를 변경할 수 있습니다.


1
그래, 난 원해 그러나 내 경험에 따르면 명시 적으로 처리하도록 설계되지 않은 사소한 기능으로 데이터를 보낼 때 해당 기능이 중단되는 경향이 있습니다. 형식 안전성뿐만 아니라 함수 작성자가 예상하지 않은 데이터 조건을 언급하고 있습니다. 함수가 오래되고 자주 사용되는 경우 새 데이터가 흐를 수 있도록 변경하면 여전히 작동해야하는 일부 오래된 데이터 형식으로 인해 데이터가 손상 될 수 있습니다. 디커플링이 기능 대 데이터에 이상적 일 수 있지만 그 디커플링의 현실은 어렵고 위험 할 수 있습니다.
GlenPeterson
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.