불변 개체를 사용하는 동안 대부분의 닭과 계란 문제를 해결하기 위해 적용 할 수있는 특정 설계 전략이 있습니까?


17

OOP 배경 (자바)에서 온 저는 스칼라를 스스로 배우고 있습니다. 불변 개체를 개별적으로 사용하는 이점을 쉽게 볼 수는 있지만, 전체 응용 프로그램을 어떻게 디자인 할 수 있는지 보는 데 어려움을 겪고 있습니다. 예를 들어 보겠습니다.

물과 얼음과 같은 "재료"와 그 속성 (게임을 디자인하고 있으므로 실제로 문제가 있습니다)을 나타내는 개체가 있다고 가정 해 봅시다. 그러한 모든 재료 인스턴스를 소유하는 "관리자"가 있습니다. 한 가지 특성은 어는점과 녹는 점, 재료가 얼거나 녹는 것입니다.

[편집] 모든 머티리얼 인스턴스는 Java Enum과 같은 "단일"입니다.

나는 "물"이 0C에서 "얼음"으로 얼 었다고 말하고 "얼음"은 1C에서 "물"로 녹인다 고 말하고 싶습니다. 그러나 물과 얼음이 불변이라면, 그것들 중 하나가 먼저 생성되어야하고, 아직 존재하지 않는 다른 생성자 매개 변수에 대한 참조를 얻을 수 없기 때문에 생성자 매개 변수로서 서로에 대한 참조를 얻을 수 없습니다. 동결 / 용해 속성을 요청할 때마다 필요한 다른 재료 인스턴스를 찾기 위해 쿼리 할 수 ​​있도록 관리자에 대한 참조를 제공 하여이 문제를 해결할 수 있지만 관리자간에 동일한 문제가 발생합니다. 그리고 자료는 서로에 대한 참조가 필요하지만 그 중 하나에 대해서만 생성자에 제공 될 수 있으므로 관리자 나 자료는 변경할 수 없습니다.

이 문제를 해결할 방법이 없습니까? 아니면 "기능적"프로그래밍 기술이나 다른 패턴을 사용하여 해결해야합니까?


2
나에게, 당신이 말하는 방식에는 물도 얼음도 없습니다. 단지 거기에 h2o재료
— 모기

1
나는 이것이 "과학적으로"의미가 있다는 것을 알고 있지만, 게임에서는 문맥에 따라 "가변"속성을 갖는 하나의 재료가 아니라 "고정"속성을 갖는 두 가지 재료로 모델링하는 것이 더 쉽다.
— Sebastien Diot

싱글 톤은 멍청한 생각입니다.
— DeadMG

@DeadMG 글쎄요. 그것들은 실제 자바 싱글 톤이 아닙니다. 나는 각각 하나 이상의 인스턴스를 만들 필요가 없다는 것을 의미합니다. 실제로 실제 정적 인스턴스는 없습니다. 내 "루트"객체는 OSGi 서비스입니다.
— Sebastien Diot

이 다른 질문에 대한 답변은 상황이 immutables으로 정말 빠르게 복잡 나의 의혹을 확인하는 것 programmers.stackexchange.com/questions/68058/...
— 세바스티앙 Diot

답변:


2

해결책은 약간의 속임수입니다. 구체적으로 특별히:

  • A를 작성하되 B에 대한 참조는 아직 초기화되지 않은 상태로 두십시오 (B는 아직 존재하지 않음).

  • B를 작성하고 A를 가리 키도록하십시오.

  • B를 가리 키도록 A를 업데이트하십시오.이 후에 A 또는 B를 업데이트하지 마십시오.

이 작업은 명시 적으로 수행 할 수 있습니다 (C ++의 예).

struct List {
    int n;
    List *next;

    List(int n, List *next)
        : n(n), next(next);
};

// Return a list containing [0,1,0,1,...].
List *binary(void)
{
    List *a = new List(0, NULL);
    List *b = new List(1, a);
    a->next = b; // Evil, but necessary.
    return a;
}

또는 암시 적으로 (Haskell의 예) :

binary :: [Int]
binary = a where
    a = 0 : b
    b = 1 : a

Haskell 예제는 지연 평가를 사용하여 상호 의존적 인 불변 값의 환상을 얻습니다. 값은 다음과 같이 시작합니다.

a = 0 : <thunk>
b = 1 : a

a그리고 독립적으로 b유효한 헤드 노멀 형태 입니다. 각 변수는 다른 변수의 최종 값을 요구하지 않고 구성 할 수 있습니다. 썽크가 평가되면 동일한 데이터 b포인트를 가리 킵니다.

따라서 두 개의 불변 값이 서로를 가리 키도록하려면 두 번째를 구성한 후 첫 번째 값을 업데이트하거나 더 높은 수준의 메커니즘을 사용하여 동일하게 수행해야합니다.


귀하의 특정 예에서, 나는 Haskell에서 다음과 같이 표현할 수 있습니다.

data Material = Water {temperature :: Double}
              | Ice   {temperature :: Double}

setTemperature :: Double -> Material -> Material
setTemperature newTemp (Water _) | newTemp <= 0.0 = Ice newTemp
                                 | otherwise      = Water newTemp
setTemperature newTemp (Ice _)   | newTemp >= 1.0 = Water newTemp
                                 | otherwise      = Ice newTemp

그러나 나는 문제를 회피하고 있습니다. setTemperature메소드가 각 Material생성자 의 결과에 첨부 되는 객체 지향 접근 방식에서는 생성자가 서로를 가리켜 야한다고 생각합니다. 생성자가 변경 불가능한 값으로 처리되는 경우 위에서 설명한 방법을 사용할 수 있습니다.


(하스켈의 구문을 이해했다고 가정) 현재 솔루션이 실제로 매우 비슷하다고 생각하지만 그것이 "올바른"것인지 또는 더 나은 것이 있는지 궁금합니다. 먼저 (아직 생성되지 않은) 모든 개체에 대해 "핸들"(참조)을 만든 다음 모든 개체를 만들어 필요한 핸들을 제공하고 마지막으로 개체에 대한 핸들을 초기화합니다. 객체 자체는 불변이지만 핸들은 아닙니다.
— Sebastien Diot

6

귀하의 예에서는 객체에 변형을 적용 하여 현재 객체를 변경하려고 ApplyTransform()하는 BlockBase대신 반환 하는 메소드 와 같은 것을 사용 합니다.

예를 들어, 약간의 열을 가하여 IceBlock을 WaterBlock으로 변경하려면 다음과 같이 호출합니다.

BlockBase currentBlock = new IceBlock();
currentBlock = currentBlock.ApplyTemperature(1); 
// currentBlock is now a WaterBlock 

및 IceBlock.ApplyTemperature()방법은 다음과 같을 것이다 :

public class IceBlock() : BlockBase
{
    public BlockBase ApplyTemperature(int temp)
    {
        return (temp > 0 ? new WaterBlock((BlockBase)this) : this);
    }
}

이것은 좋은 대답이지만 불행히도 내 "재료", 실제로 "블록"은 모두 싱글 톤이므로 새로운 WaterBlock ()은 옵션이 아닙니다. 그것은 불변의 주요 이점입니다. 당신은 그것들을 무한정 재사용 할 수 있습니다. 램에 500,000 개의 블록이있는 대신 100 개의 블록에 대한 500,000 개의 참조가 있습니다. 훨씬 저렴합니다!
— Sebastien Diot

그렇다면 BlockList.WaterBlock새로운 블록을 만드는 대신 돌아 오는 것은 어떻습니까?
— Rachel

예, 이것이 내가하는 일이지만 어떻게 차단 목록을 얻습니까? 분명히, 블록은 블록 목록보다 먼저 생성되어야하므로 블록이 실제로 변경 불가능한 경우 블록 목록을 매개 변수로 수신 할 수 없습니다. 그렇다면 어디에서 목록을 얻습니까? 내 일반적인 요점은 코드를 더 복잡하게 만들어서 닭과 계란 문제를 한 수준에서 해결하고 다음 단계에서 다시 얻는 것입니다. 기본적으로 불변성에 따라 전체 응용 프로그램을 만들 수있는 방법이 없습니다. "작은 물체"에만 적용 할 수 있지만 컨테이너에는 적용되지 않는 것 같습니다.
— Sebastien Diot

@Sebastien 저는 각 블록의 단일 인스턴스를 담당 BlockList하는 static클래스 일 뿐이라고 생각합니다 . 따라서 BlockListC #에 익숙합니다.
— Rachel

@Sebastien : Singeltons를 사용하면 가격을 지불합니다.
— DeadMG

6

주기를 깨는 또 다른 방법은 일부 언어로 재료와 변형에 대한 우려를 분리하는 것입니다.

water = new Block("water");
ice = new Block("ice");

transitions = new Transitions([
    new transitions.temperature.Below(0.0, water, ice),
    new transitions.temperature.Above(0.0, ice, water),
]);

허, 처음에는이 책을 읽기가 어려웠지만 필자가 주장한 것과 같은 방식이라고 생각합니다.
— Aidan Cully

1

기능적 언어를 사용하려고하고 불변성의 이점을 실현하려면 문제를 염두에 두어야합니다. 다양한 온도를 지원할 수있는 "얼음"또는 "물"이라는 객체 유형을 정의하려고합니다. 불변성을 지원하려면 온도가 변할 때마다 새 객체를 만들어야하므로 낭비입니다. 따라서 블록 유형과 온도 개념을보다 독립적으로 만드십시오. 나는 Scala를 알지 못하지만 (내 학습 목록에 있습니다 :-)) Haskell의 Joey Adams Answer에서 빌리면 다음과 같이 제안합니다.

data Material = Water | Ice

blockForTemperature :: Double -> Material
blockForTemperature x = 
  if x < 0 then Ice else Water

또는 아마도 :

transitionForTemperature :: Material -> Double -> Material
transitionForTemperature oldMaterial newTemp = 
  case (oldMaterial, newTemp) of
    (Ice, _) | newTemp > 0 -> Water
    (Water, _) | newTemp <= 0 -> Ice

(참고 :이 작업을 시도하지 않았으며 내 Haskell은 약간 녹슬 었습니다.) 이제 전환 논리는 재료 유형과 분리되어 있으므로 많은 메모리를 낭비하지 않으며 (제 의견으로는) 상당히 좋습니다. 좀 더 기능 지향적입니다.


실제로는 "기능 언어"를 사용하려고하지 않습니다. 내가 사소한 기능적 프로그래밍 예제에서 일반적으로 유지하는 유일한 것은 "아, 나는 더 영리했다!" 이것이 어떻게 다른 사람에게 의미가 있는지 이해할 수 없습니다. 학생들의 시절부터 Prolog (로직 기반), Occam (기본적으로 병렬로 실행) 및 어셈블러조차도 이해가되었지만 Lisp는 정말 미친 일이었습니다. 그러나 "상태 변경"을 일으키는 코드를 "상태"외부로 이동시키는 방법을 알려 드리겠습니다.
— Sebastien Diot
당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.