OOP 배경 (자바)에서 온 저는 스칼라를 스스로 배우고 있습니다. 불변 개체를 개별적으로 사용하는 이점을 쉽게 볼 수는 있지만, 전체 응용 프로그램을 어떻게 디자인 할 수 있는지 보는 데 어려움을 겪고 있습니다. 예를 들어 보겠습니다.
물과 얼음과 같은 “재료”와 그 속성 (게임을 디자인하고 있으므로 실제로 문제가 있습니다)을 나타내는 개체가 있다고 가정 해 봅시다. 그러한 모든 재료 인스턴스를 소유하는 “관리자”가 있습니다. 한 가지 특성은 어는점과 녹는 점, 재료가 얼거나 녹는 것입니다.
[편집] 모든 머티리얼 인스턴스는 Java Enum과 같은 “단일”입니다.
나는 “물”이 0C에서 “얼음”으로 얼 었다고 말하고 “얼음”은 1C에서 “물”로 녹인다 고 말하고 싶습니다. 그러나 물과 얼음이 불변이라면, 그것들 중 하나가 먼저 생성되어야하고, 아직 존재하지 않는 다른 생성자 매개 변수에 대한 참조를 얻을 수 없기 때문에 생성자 매개 변수로서 서로에 대한 참조를 얻을 수 없습니다. 동결 / 용해 속성을 요청할 때마다 필요한 다른 재료 인스턴스를 찾기 위해 쿼리 할 수 있도록 관리자에 대한 참조를 제공 하여이 문제를 해결할 수 있지만 관리자간에 동일한 문제가 발생합니다. 그리고 자료는 서로에 대한 참조가 필요하지만 그 중 하나에 대해서만 생성자에 제공 될 수 있으므로 관리자 나 자료는 변경할 수 없습니다.
이 문제를 해결할 방법이 없습니까? 아니면 “기능적”프로그래밍 기술이나 다른 패턴을 사용하여 해결해야합니까?
답변
해결책은 약간의 속임수입니다. 구체적으로 특별히:
-
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
생성자 의 결과에 첨부 되는 객체 지향 접근 방식에서는 생성자가 서로를 가리켜 야한다고 생각합니다. 생성자가 변경 불가능한 값으로 처리되는 경우 위에서 설명한 방법을 사용할 수 있습니다.
답변
귀하의 예에서는 객체에 변형을 적용 하여 현재 객체를 변경하려고 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);
}
}
답변
주기를 깨는 또 다른 방법은 일부 언어로 재료와 변형에 대한 우려를 분리하는 것입니다.
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),
]);
답변
기능적 언어를 사용하려고하고 불변성의 이점을 실현하려면 문제를 염두에 두어야합니다. 다양한 온도를 지원할 수있는 “얼음”또는 “물”이라는 객체 유형을 정의하려고합니다. 불변성을 지원하려면 온도가 변할 때마다 새 객체를 만들어야하므로 낭비입니다. 따라서 블록 유형과 온도 개념을보다 독립적으로 만드십시오. 나는 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은 약간 녹슬 었습니다.) 이제 전환 논리는 재료 유형과 분리되어 있으므로 많은 메모리를 낭비하지 않으며 (제 의견으로는) 상당히 좋습니다. 좀 더 기능 지향적입니다.