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


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.