모든 주요 OOP 기능을 잃지 않고 OOP에서 불변성을 실제로 사용할 수 있습니까?


11

프로그램의 객체를 변경할 수없는 이점이 있습니다. 내 응용 프로그램에 대한 좋은 디자인에 대해 깊이 생각할 때 종종 불변의 많은 객체에 자연스럽게 도달합니다. 종종 모든 객체를 불변 으로 만들고 싶습니다 .

이 질문 은 동일한 아이디어를 다루지 만 불변성에 대한 좋은 접근 방법과 실제로 사용하는 시점에 대한 대답은 없습니다. 좋은 불변 디자인 패턴이 있습니까? 일반적인 아이디어는 실제로는 쓸모없는 "변경할 필요가 없다면 객체를 불변으로 만드는 것"입니다.

내 경험에 따르면 불변성은 코드를 점점 더 기능적인 패러다임으로 이끌고이 진행은 항상 발생합니다.

  1. 목록, 맵 등과 같은 지속적인 (기능적 의미에서) 데이터 구조가 필요합니다.
  2. 상호 참조 (예 : 자식이 부모를 참조하는 동안 자식을 참조하는 트리 노드)를 사용하는 것은 매우 불편하므로 상호 참조를 전혀 사용하지 않으므로 데이터 구조와 코드의 기능이 향상됩니다.
  3. 상속은 의미가 없어지고 대신 작곡을 사용하기 시작합니다.
  4. 캡슐화와 같은 OOP의 전체 기본 아이디어가 무너지기 시작하고 객체가 함수처럼 보이기 시작합니다.

이 시점에서 나는 실제로 더 이상 OOP 패러다임의 아무것도 사용하지 않고 순전히 기능적인 언어로 전환 할 수 있습니다. 따라서 내 질문 : 좋은 불변의 OOP 디자인에 대한 일관된 접근 방법이 있습니까, 아니면 불변의 아이디어를 최대한으로 활용할 때 항상 더 이상 OOP 세계에서 아무것도 필요로하지 않는 기능적 언어로 프로그래밍을 끝내는 경우입니까? OOP가 분리되지 않도록 변경할 클래스와 변경 불가능한 클래스를 결정하는 좋은 지침이 있습니까?

편의상 예제를 제공하겠습니다. ChessBoard불변 체스 조각의 불변 컬렉션으로 보자 (확장 추상 클래스)Piece). OOP 관점에서, 피스는 보드의 위치에서 유효한 움직임을 생성합니다. 그러나 움직임을 생성하려면 보드에 대한 참조가 필요하지만 보드는 해당 참조에 대한 참조가 필요합니다. 글쎄, OOP 언어에 따라 이러한 불변 상호 참조를 만드는 몇 가지 트릭이 있지만 관리하기가 쉽지 않습니다. 그러나 보드의 상태를 알 수 없으므로 조각이 동작을 생성 할 수 없습니다. 그러면 조각은 조각 유형과 위치를 보유하는 데이터 구조가됩니다. 그런 다음 다형성 함수를 사용하여 모든 종류의 조각에 대한 움직임을 생성 할 수 있습니다. 이것은 함수형 프로그래밍에서는 완벽하게 달성 할 수 있지만 런타임 유형 검사 및 기타 잘못된 OOP 관행이 없으면 OOP에서는 거의 불가능합니다 ...


3
나는 기본 질문을 좋아하지만 세부 사항에 어려움을 겪습니다. 예를 들어 불변성을 극대화 할 때 상속이 이해되지 않는 이유는 무엇입니까?
Martin Ba

1
객체에 메소드가 없습니까?
Monica


4
"OOP 관점에서, 한 조각은 보드의 해당 위치에서 유효한 움직임을 생성하는 역할을합니다." -SRP에 가장 큰 영향을 줄만한 조각을 디자인하는 것은 아닙니다.
Doc Brown

1
@lishaak 당신은 문제가 아니기 때문에 "간단한 용어로 상속 문제를 설명하는 데 어려움을 겪고 있습니다" ; 열악한 디자인은 문제입니다. 상속 OOP의 본질의이다 사인 ...로서 비 당신이 경우. 소위 "상속 문제"는 실제로 명시적인 재정의 구문이없는 언어의 문제이며, 디자인이 잘 된 언어에서는 문제가 훨씬 적습니다. 가상 방법과 다형성이 없으면 OOP가 없습니다. 재미있는 객체 구문으로 절차 적 프로그래밍이 있습니다. 따라서 OO를 피한다면 OO의 이점을 보지 못하는 것은 놀라운 일이 아닙니다!
메이슨 휠러

답변:


24

모든 주요 OOP 기능을 잃지 않고 OOP에서 불변성을 실제로 사용할 수 있습니까?

왜 그런지 모르겠다. 어쨌든 Java 8이 모든 기능을 갖추기 전에 몇 년 동안 해왔습니다. 현에 대해 들어 본 적이 있습니까? 시작 이후로 좋고 불변합니다.

  1. 목록, 맵 등과 같은 지속적인 (기능적 의미에서) 데이터 구조가 필요합니다.

모두 함께 필요했습니다. 내가 읽는 동안 컬렉션을 변경했기 때문에 반복자를 무효화하는 것은 무례합니다.

  1. 상호 참조 (예 : 자식이 부모를 참조하는 동안 자식을 참조하는 트리 노드)를 사용하는 것은 매우 불편하므로 상호 참조를 전혀 사용하지 않으므로 데이터 구조와 코드의 기능이 향상됩니다.

순환 참조는 특별한 종류의 지옥입니다. 불변성은 당신을 구하지 못합니다.

  1. 상속은 의미가 없어지고 대신 작곡을 사용하기 시작합니다.

글쎄, 나는 너와 함께 있지만 불변성과 어떤 관계가 있는지 보지 못합니다. 내가 작곡을 좋아하는 이유는 동적 전략 패턴을 좋아하기 때문이 아니라 추상화 수준을 변경할 수 있기 때문입니다.

  1. 캡슐화와 같은 OOP의 전체 기본 아이디어가 무너지기 시작하고 객체가 함수처럼 보이기 시작합니다.

"캡슐화와 같은 OOP"에 대한 당신의 생각이 무엇인지 생각하기 위해 떨었습니다. 게터와 세터와 관련이 있다면 캡슐화가 아닌 캡슐화를 중지하십시오. 그것은 결코 없었다. 수동 Aspect 지향 프로그래밍입니다. 유효성을 검사하고 중단 점을 놓을 수있는 기회는 좋지만 캡슐화는 아닙니다. 캡슐화는 내부에서 무슨 일이 일어나고 있는지 알거나 돌보지 않을 권리를 보존합니다.

객체는 함수처럼 보일 것입니다. 그들은 기능의 가방입니다. 그것들은 함께 움직여서 서로를 재정의하는 기능의 가방입니다.

현재 함수형 프로그래밍이 유행하고 있으며 사람들은 OOP에 대해 약간의 오해를 쏟고있다. 이것이 OOP의 끝이라고 믿도록 혼동하지 마십시오. 기능과 OOP는 아주 잘 어울릴 수 있습니다.

  • 함수형 프로그래밍은 과제에 대해 공식적입니다.

  • OOP는 함수 포인터에 대해 공식적입니다.

정말 그렇습니다. Dykstra는 우리 goto에게 유해하다고 말했기 때문에 공식적으로 구조화 된 프로그래밍을 만들었습니다. 마찬가지로,이 두 패러다임은 이러한 귀찮은 일을 자연스럽게하는 데 따른 함정을 피하면서 일을 수행 할 수있는 방법을 찾는 것입니다.

당신에게 뭔가를 보여 드리겠습니다 :

F N (X)

그것은 기능입니다. 실제로는 연속 함수입니다.

f 1 (x)
f 2 (x)
...
f n (x)

우리가 OOP 언어로 어떻게 그것을 표현하는지 맞춰보세요?

n.f(x)

n구현은 어떤 구현 f이 사용 되는지를 선택 하지 않으며 그 함수에 사용 된 상수 중 일부가 무엇인지 결정합니다 (솔직히 같은 것을 의미합니다). 예를 들면 다음과 같습니다.

f 1 (x) = x + 1
f 2 (x) = x + 2

클로저가 제공하는 것과 같은 것입니다. 클로저가 둘러싸는 범위를 참조하는 경우 객체 메서드는 인스턴스 상태를 나타냅니다. 객체는 클로저를 더 잘 수행 할 수 있습니다. 클로저는 다른 함수에서 반환 된 단일 함수입니다. 생성자는 전체 함수 백에 대한 참조를 리턴합니다.

g 1 (x) = x 2 + 1
g 2 (x) = x 2 + 2

그렇습니다.

n.g(x)

f와 g는 함께 바뀌고 함께 움직이는 함수입니다. 그래서 우리는 그것들을 같은 가방에 넣습니다. 이것이 바로 물체입니다. n상수 (불변)를 유지 하면 호출 할 때 수행 할 작업을보다 쉽게 ​​예측할 수 있습니다.

이제는 구조 일뿐입니다. 내가 OOP에 대해 생각하는 방식은 다른 작은 것들과 대화하는 작은 것들입니다. 희망적으로 작은 것들의 작은 선택된 그룹에. 코딩 할 때 나는 그 자체를 객체라고 생각합니다. 나는 물건의 관점에서 사물을 본다. 그리고 나는 게 으르려고 노력하여 물건을 과도하게 사용하지 않습니다. 나는 간단한 메시지를 받고 약간의 작업을하며 나의 가장 친한 친구에게 간단한 메시지를 보냅니다. 내가 그 물체를 다 마치면 다른 물체로 뛰어 들어 그것의 관점에서 사물을 본다.

수업 책임 카드는 저에게 그런 식으로 생각하도록 가르치는 최초의 사람이었습니다. 남자 나는 그때 그들에 대해 혼란 스러웠지만 오늘날에도 여전히 관련이 없다면 망할 것입니다.

ChessBoard를 불변의 체스 조각 (확장 추상 클래스 Piece)의 불변 컬렉션으로합시다. OOP 관점에서, 피스는 보드의 위치에서 유효한 움직임을 생성합니다. 그러나 움직임을 생성하려면 보드에 대한 참조가 필요하지만 보드는 해당 참조에 대한 참조가 필요합니다.

아가! 불필요한 순환 참조로 다시.

어떻습니까 : A ChessBoardDataStructure는 xy 코드를 조각 참조로 바꿉니다. 그 조각들은 x, y 및 특정 방법을 사용 ChessBoardDataStructure하여 새로운 sking spanking 브랜드 모음으로 ChessBoardDataStructure만듭니다. 그런 다음 최선의 움직임을 선택할 수있는 것으로 밀어 넣습니다. 이제는 ChessBoardDataStructure불변이 될 수 있으며 조각도 마찬가지입니다. 이런 식으로 메모리에 하나의 흰색 폰이 있습니다. 올바른 xy 위치에는 여러 참조가 있습니다. 객체 지향, 기능 및 불변.

잠깐, 이미 체스 얘기 안 했어?


6
모두 읽어야합니다. 그런 다음 William R. Cook이 검토데이터 이해 이해에 대해 읽으십시오 . 그리고 이것을 다시 읽으십시오. 그런 다음 "Object"및 "Object Oriented"에 대한 단순화 된 최신 정의에 대한 Cook의 제안서를 읽으십시오 . Alan Kay의 " 큰 아이디어는 '메시징 " 입니다. OO에 대한 그의 정의
Jörg W Mittag


1
@Euphoric : "함수 프로그래밍", "제한 프로그래밍", "논리 프로그래밍"등에 대한 표준 정의와 일치하는 상당히 표준적인 공식이라고 생각합니다. 그렇지 않으면 C는 FP를 인코딩 할 수 있기 때문에 논리 언어는 논리 프로그래밍을 동적으로 인코딩 할 수 있기 때문입니다. 동적 언어는 동적 타이핑을 입력 할 수 있기 때문입니다. 액터 언어는 액터 시스템을 인코딩 할 수 있기 때문입니다. Haskell은 실제로 부작용의 이름을 지정하고 변수에 저장하고 다음과 같이 전달할 수 있기 때문에 세계에서 가장 큰 명령 언어입니다.
Jörg W Mittag

1
언어 표현력에 관한 Matthias Felleisen의 천재 논문과 비슷한 아이디어를 사용할 수 있다고 생각합니다. OO 프로그램을 Java에서 C♯로 옮기려면 로컬 변환만으로 수행 할 수 있지만 C로 옮기려면 전역 구조 조정이 필요합니다 (기본적으로 메시지 디스패치 메커니즘을 도입하고 모든 함수 호출을 경로 재지 정해야 함). Java와 C♯는 OO를 표현할 수 있으며 C는 "OO"만 인코딩 할 수 있습니다.
Jörg W Mittag 2016 년

1
@lishaak 다른 것으로 지정하기 때문입니다. 이를 통해 한 수준의 추상화를 유지하고 일치하지 않는 위치 정보 저장소가 중복되는 것을 방지 할 수 있습니다. 여분의 타이핑을 다시 보내면 메소드에 입력하여 한 번만 입력하십시오. 이제 조각은 색상, 움직임 및 이미지 / 약어와 같은 정보 만 저장하며 항상 사실이며 변경 불가능합니다.
candied_orange

2

OOP가 주류에 도입 한 가장 유용한 개념은 다음과 같습니다.

  • 적절한 모듈화.
  • 데이터 캡슐화.
  • 개인 인터페이스와 공용 인터페이스를 명확하게 분리합니다.
  • 코드 확장 성을위한 명확한 메커니즘.

상속이나 클래스와 같은 전통적인 구현 세부 사항 없이도 이러한 모든 이점을 실현할 수 있습니다. Alan Kay의 "객체 지향 시스템"에 대한 원래 아이디어는 "메소드"대신 "메시지"를 사용했으며 C ++보다 Erlang에 더 가깝습니다. 많은 전통적인 OOP 구현 세부 사항을 제거하지만 여전히 합리적인 객체 지향을 느끼는 Go를보십시오.

불변 개체를 사용하는 경우 인터페이스, 동적 디스패치, 캡슐화와 같은 대부분의 기존 OOP 기능을 계속 사용할 수 있습니다. 세터가 필요하지 않으며 더 간단한 객체를위한 게터도 필요하지 않은 경우가 많습니다. 또한 불변성의 이점을 누릴 수 있습니다. 그 동안 객체가 변경되지 않았으며, 방어 적 복사, 데이터 경쟁이 없으며, 메소드를 순수하게 만들기가 쉽다는 것을 항상 확신합니다.

Scala가 어떻게 불변성과 FP 접근 방식을 OOP와 결합하려고하는지 살펴보십시오. 분명히 가장 단순하고 우아한 언어는 아닙니다. 그러나 실제로는 실질적으로 성공합니다. 또한 유사한 믹스에 대한 많은 도구와 접근 방식을 제공하는 Kotlin을 살펴보십시오.

N 년 전 언어 제작자들이 생각했던 것과는 다른 접근법을 시도 할 때의 일반적인 문제 는 표준 라이브러리와의 '임피던스 불일치'입니다. Java 및 .NET 에코 시스템 모두 OTOH는 현재 불변 데이터 구조에 대해 합리적인 표준 라이브러리 지원을 제공합니다. 물론 타사 라이브러리도 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.