말의 무리가 주어지면 모든 유니콘의 평균 뿔 길이를 어떻게 찾을 수 있습니까?


30

위의 질문은 레거시 코드에서 발생하는 일반적인 문제 또는 더 정확하게는이 문제를 해결하기 위해 이전에 시도한 문제의 추상 예입니다.

방법과 같이이 문제를 해결하기위한 적어도 하나의 .NET 프레임 워크 방법을 생각할 수 있습니다 Enumerable.OfType<T>. 그러나 궁극적으로 런타임에 객체 유형을 조사한다는 사실은 나와 맞지 않습니다.

각 말에게 "유니콘인가요?" 다음과 같은 접근 방식도 떠 오릅니다.

  • 유니콘이 아닌 뿔의 길이를 얻으려고 할 때 예외를 던지십시오 (각 말에 적합하지 않은 기능을 노출시킵니다)
  • 비유 니콘의 뿔 길이에 대한 기본 또는 마법 값을 반환합니다.
  • 상속을 없애고 말에 말이 유니콘인지 아닌지를 알려주는 별도의 객체를 만듭니다 (잠재적으로 동일한 문제를 층으로 밀고 있음)

이것이 "답이 아닌"으로 가장 잘 응답 될 것 같은 느낌이 듭니다. 그러나이 문제에 어떻게 접근하고, 문제가 있다면 결정에 대한 맥락은 무엇입니까?

또한이 문제가 여전히 기능 코드에 존재하는지 또는 변경 가능성을 지원하는 기능 언어에만 존재하는지에 대한 통찰력에 관심이 있습니다.

이것은 다음 질문의 가능한 복제물로 표시되었습니다. 다운 캐스팅을 피하는 방법?

이 질문에 대한 답은 HornMeasurer모든 경적 측정을 수행해야하는 것으로 가정합니다 . 그러나 그것은 모든 사람들이 말의 뿔을 자유롭게 측정 할 수 있다는 평등주의 원칙에 따라 형성된 코드베이스에 대한 부과입니다.

a가 없으면 HornMeasurer수락 된 답변의 접근 방식이 위에 나열된 예외 기반 접근 방식을 반영합니다.

말과 유니콘이 모두 말인지 또는 유니콘이 마법의 말의 아종인지에 대한 의견에는 약간의 혼란이있었습니다. 두 가지 가능성을 모두 고려해야합니다. 아마도 하나가 다른 것보다 선호됩니까?


22
말에는 뿔이 없으므로 평균은 정의되어 있지 않습니다 (0/0).
Scott Whitlock

3
@moarboilerplate 10에서 무한대까지.
nanny

4
@StephenP :이 경우 수학적으로 작동하지 않습니다. 모든 0은 평균을 왜곡합니다.
메이슨 휠러

3
귀하의 질문에 대한 답변이없는 답변이 가장 좋은 경우 Q & A 사이트에 속하지 않습니다. reddit, quora 또는 기타 토론 기반 사이트는 대답이없는 유형의 물건을 위해 만들어졌습니다 ... 즉, @MasonWheeler가 제공 한 코드를 찾고 있다면 확실하지 않을 것이라고 생각합니다. 무엇을 물어보고 ..
지미 호파

3
@JimmyHoffa "당신이 잘못하고있다"는 "답이 맞지 않는"것으로 받아 들여지고, 종종 "잘, 여기에 당신이 할 수있는 한 가지 방법이 있습니다"-더 나은 논의가 필요하지 않습니다.
moarboilerplate

답변:


11

Unicorn특별한 종류로 취급한다고 가정하면 Horse기본적으로 두 가지 방법으로 모델링 할 수 있습니다. 보다 전통적인 방법은 서브 클래스 관계입니다. 코드를 리팩터링하여 목록을 중요한 컨텍스트에서 항상 분리하고 Unicorn특성에 신경 쓰지 않는 컨텍스트에서만 조합하여 유형을 확인하고 다운 캐스팅을 피할 수 있습니다 . 다시 말해, 말 무리에서 유니콘을 추출 할 필요가없는 상황에 빠지지 않도록 배치하는 것입니다. 처음에는 어려워 보이지만 99.99 %의 경우에 가능하며 일반적으로 실제로 코드를 훨씬 깨끗하게 만듭니다.

유니콘을 모델링하는 다른 방법은 모든 말에 선택적인 경적 길이를 부여하는 것입니다. 그런 다음 뿔 길이가 있는지 확인하여 그것이 유니콘인지 테스트하고 (스칼라로) 모든 유니콘의 평균 뿔 길이를 찾으십시오.

case class Horse(val hornLength: Option[Double])

val horse = Horse(None)
val unicorn = Horse(Some(12.0))
val anotherUnicorn = Horse(Some(6.0))

val herd = List(horse, unicorn, anotherUnicorn)
val hornLengths = herd flatMap {_.hornLength}
val averageLength = hornLengths.sum / hornLengths.size

이 방법은 단일 클래스로 더 간단하다는 장점이 있지만 확장 성이 떨어지고 "유니콘"을 확인하는 로터리 방식의 단점이 있습니다. 이 솔루션을 사용하는 경우의 트릭은 더 유연한 아키텍처로 이동해야하는 경우가 종종 확장을 시작할 때 인식하는 것입니다. 이러한 종류의 솔루션은 항목 flatMap을 쉽게 필터링하는 것처럼 간단하고 강력한 기능을 갖춘 기능적 언어에서 훨씬 더 많이 사용 None됩니다.


7
물론 이것은 보통의 말과 유니콘의 유일한 차이점은 뿔이라고 가정합니다. 그렇지 않은 경우 상황이 훨씬 더 복잡해집니다.
메이슨 휠러

제시된 두 번째 방법에서만 @MasonWheeler.
moarboilerplate

1
유니콘에 신경 쓰지 않는 상황이 될 때까지 비유 니콘과 유니콘이 상속 시나리오에서 어떻게 연결되어서는 안되는지에 대한 의견을 살펴보십시오. 물론 .OfType ()은 문제를 해결하고 작동하게 할 수 있지만 처음에는 존재하지 않아야하는 문제를 해결합니다. 두 번째 방법은 옵션이 null을 사용하여 무언가를 암시하는 것보다 훨씬 우수하기 때문에 작동합니다. 나는 유니콘 특성을 독립형 속성에 캡슐화하고 매우 경계 적이라면 OO에서 타협으로 두 번째 접근 방식을 달성 할 수 있다고 생각합니다.
moarboilerplate

1
독립형 건물에 유니콘 특성을 캡슐화하고 극도로 경계 하는 경우 타협하십시오 -왜 스스로 인생을 힘들게합니까? typeof를 직접 사용하고 많은 미래 문제를 저장하십시오.
gbjbaanb

@ gbjbaanb 나는 그 접근 방식이 빈혈 Horse이있는 IsUnicorn속성과 어떤 종류의 UnicornStuff속성 이있는 시나리오 (질문에 언급 된 라이더 / 글 라이터를 조정할 때)에만 실제로 적절하다고 생각합니다 .
moarboilerplate

38

모든 옵션을 거의 다 다루었습니다. 특정 하위 유형에 종속 된 동작이 있고 다른 하위 유형과 혼합 된 경우 코드에서 해당 하위 유형을 인식해야합니다. 그것은 단순한 논리적 추론입니다.

개인적으로, 나는 그냥 갈 것입니다 horses.OfType<Unicorn>().Average(u => u.HornLength). 코드의 의도를 매우 명확하게 표현합니다. 이는 나중에 누군가가 나중에 코드를 유지 관리해야하기 때문에 가장 중요합니다.


람다 구문이 맞지 않으면 용서해주세요. 나는 C # 코더가 많지 않으며 결코 이런 세부적인 내용을 유지할 수 없습니다. 그래도 내가 의미하는 바가 분명해야합니다.
메이슨 휠러

1
걱정하지 마십시오. 문제는 목록에 Unicorn어쨌든 (만약 당신이 생략 할 수있는 레코드) 만 들어 있으면 거의 해결됩니다 return.
moarboilerplate

4
이것이 문제를 빨리 해결하고 싶을 때의 답입니다. 그러나 코드를 더 그럴듯하게 리팩토링하고 싶다면 대답이 아닙니다.
Andy

6
터무니없는 수준의 최적화가 필요하지 않은 한 이것이 정답입니다. 그것의 명확성과 가독성은 거의 모든 것을 무질서하게 만듭니다.
David는 Reinstate Monica가

1
@DavidGrinberg이 깨끗하고 읽기 쉬운 메소드를 작성한다면 이전에는 존재하지 않았던 상속 구조를 먼저 구현해야했을까요?
moarboilerplate

9

.NET에는 아무런 문제가 없습니다.

var unicorn = animal as Unicorn;
if(unicorn != null)
{
    sum += unicorn.HornLength;
    count++;
}

Linq를 사용하는 것도 좋습니다.

var averageUnicornHornLength = animals
    .OfType<Unicorn>()
    .Select(x => x.HornLength)
    .Average();

제목에서 질문 한 내용을 바탕으로 내가 찾게 될 코드입니다. 질문에 "뿔이있는 동물의 평균은 얼마입니까?"

var averageHornedAnimalHornLength = animals
    .OfType<IHornedAnimal>()
    .Select(x => x.HornLength)
    .Average();

Linq를 사용할 때 열거 형이 비어 있고 유형 T가 널 입력 가능하지 않은 경우 Average(및 MinMax)는 예외를 발생시킵니다. 평균이 실제로 정의되어 있지 않기 때문입니다 (0 / 0). 따라서 실제로 다음과 같은 것이 필요합니다.

var hornedAnimals = animals
    .OfType<IHornedAnimal>()
    .ToList();
if(hornedAnimals.Count > 0)
{
    var averageHornLengthOfHornedAnimals = hornedAnimals
        .Average(x => x.HornLength);
}
else
{
    // deal with it in your own way...
}

편집하다

나는 이것이 추가가 필요하다고 생각합니다 ... 이와 같은 질문이 객체 지향 프로그래머와 잘 어울리지 않는 이유 중 하나는 클래스와 객체를 사용하여 데이터 구조를 모델링한다고 가정하기 때문입니다. 스몰 토크 고유의 객체 지향 아이디어는 객체로 인스턴스화되고 메시지를 보낼 때 서비스를 수행 한 모듈로 프로그램을 구성하는 것이 었습니다. 클래스와 객체를 사용하여 데이터 구조를 모델링 할 수 있다는 사실은 (유용한) 부작용이지만 두 가지가 다릅니다. 당신 때문에 난, 후자는 객체 지향 프로그래밍을 고려해야한다고 생각하지 않습니다 과 같은 일을 struct하지만, 그것은 단지 꽤으로하지 않을 것입니다.

객체 지향 프로그래밍을 사용하여 작업을 수행하는 서비스를 작성하는 경우 해당 서비스가 실제로 다른 서비스인지 구체적인 구현인지에 대한 쿼리는 일반적으로 타당한 이유가 있습니다. 인터페이스 (일반적으로 종속성 주입을 통해)가 제공되었으며 해당 인터페이스 / 계약에 코딩해야합니다.

반면에, 클래스 / 객체 / 인터페이스 아이디어를 사용하여 데이터 구조 또는 데이터 모델을 작성하는 경우 개인적으로 is-a 아이디어를 최대한 사용하는 데 아무런 문제가 없습니다. 유니콘이 말의 하위 유형이며 도메인 내에서 완전히 의미가 있다고 정의한 경우 절대로 무리의 말을 쿼리하여 유니콘을 찾으십시오. 결국 이와 같은 경우 우리는 일반적으로 우리가 해결해야 할 문제의 해결책을 더 잘 표현하기 위해 도메인 특정 언어를 만들려고합니다. 그런 의미에서 .OfType<Unicorn>()등에는 아무런 문제가 없습니다 .

궁극적으로 항목 모음을 가져 와서 유형별로 필터링하는 것은 실제로 객체 지향 프로그래밍이 아니라 기능적인 프로그래밍입니다. 고맙게도 C #과 같은 언어는 이제 두 패러다임을 모두 편안하게 처리합니다.


7
당신은 이미 알고 animal A는 Unicorn ; 그냥 사용하지 않고 캐스팅 as하거나 잠재적으로 더 잘 사용 as 하고 null을 확인하십시오.
Philip Kendall 2016 년

3

그러나 궁극적으로 런타임에 객체 유형을 조사한다는 사실은 나와 맞지 않습니다.

이 문장의 문제점은 사용하는 메커니즘에 관계없이 항상 객체에 어떤 유형인지 알려주는 것입니다. 그것은 RTTI 일 수도 있고 여러분이 요청하는 공용체 또는 일반 데이터 구조 일 수도 있습니다 if horn > 0. 정확한 세부 사항은 약간 변경되지만 의도는 동일합니다. 더 심문해야하는지 여부를 확인하기 위해 어떤 식 으로든 객체 자체에 대해 요청합니다.

따라서 언어 지원을 사용하여이를 수행하는 것이 좋습니다. .NET에서는 typeof예를 들어 사용 합니다.

이렇게하는 이유는 언어를 잘 사용하는 것 이상입니다. 다른 물체처럼 보이지만 약간의 변화가있는 물체가 있다면 시간이 지남에 따라 더 많은 차이를 발견 할 가능성이 있습니다. 유니콘 / 말의 예에서 경적 길이가 있다고 말할 수 있지만 내일은 잠재적 인 라이더가 처녀인지, 똥이 반짝이는지 확인합니다. 고전적인 실제 예는 공통 기반에서 파생되는 GUI 위젯이며 확인란과 목록 상자를 다르게 찾아야합니다. 차이의 수는 데이터의 모든 가능한 순열을 보유하는 단일 슈퍼 오브젝트를 작성하기에는 너무 클 수 있습니다. ).

런타임에 객체 유형을 확인하는 것이 잘 유지되지 않으면 유니콘 / 말의 단일 무리를 저장하는 대신 시작부터 다른 객체를 분할하는 것이 대안입니다. 말 모음, 유니콘 모음 . 특수 컨테이너 (예 : 키가 객체 유형 인 멀티 맵)에 저장하더라도 매우 잘 작동 할 수 있습니다. 그러나 2 개의 그룹으로 저장하더라도 객체 유형에 대해 다시 조사합니다. !)

확실히 예외 기반 접근 방식이 잘못되었습니다. 정상적인 프로그램 흐름으로 예외를 사용하는 것은 코드 냄새입니다 (유니콘 무리와 조개 껍질이 머리에 테이프로 묶인 당나귀가 있다면 예외 기반 접근법은 괜찮다고 말하지만 유니콘 무리가 있다면 말이 유니콘 상태를 점검하는 것은 예상치 못한 일이 아닙니다. 예외적 인 상황은 예외이며 복잡한 if진술은 아닙니다 . 어쨌든이 문제에 대한 예외를 사용하는 것은 런타임에 단순히 객체 유형을 조사하는 것입니다. 여기서는 언어 기능을 잘못 사용하여 유니콘이 아닌 객체를 확인합니다. 당신은뿐만 아니라if horn > 0 최소한 적은 코드 줄을 사용하고 다른 예외가 발생할 때 발생하는 문제 (예 : 빈 모음 또는 당나귀의 조개 껍질 측정)


레거시 컨텍스트 if horn > 0에서이 문제가 처음에 해결되는 방식과 거의 같습니다. 그런 다음 일반적으로 발생하는 문제는 라이더와 반짝이를 확인하려고 할 때 horn > 0관련이없는 코드로 어디에나 묻혀 있습니다 (또한 혼이 0 일 때 확인이 없기 때문에 코드에 미스터리 버그가 있습니다). 또한 사실 이후에 서브 클래 싱하는 말은 일반적으로 가장 비싼 제안이므로 리 팩터의 끝에서 여전히 펜으로 묶여 있으면 그렇게하지 않을 것입니다. 따라서 그것은 "얼마나 못생긴 대안"이
되었는가

@ moarboilerplate 당신이 직접 말하고 싸고 쉬운 해결책을 찾으면 혼란으로 변할 것입니다. 이것이 바로 이런 종류의 문제에 대한 해결책으로 OO 언어가 발명 된 이유입니다. 서브 클래 싱 말은 처음에는 비싸 보이지만 곧 비용을 지불합니다. 단순하지만 진흙 투성이 인 솔루션을 계속 사용하면 시간이 지남에 따라 점점 더 많은 비용이 듭니다.
gbjbaanb

3

질문에 functional-programming태그가 있으므로 합계 유형을 사용하여 말의 두 가지 맛과 패턴 일치를 반영하여 명확하게 표현할 수 있습니다. 예를 들어, F #에서 :

type Equine =
| Horse
| Unicorn of hornLength: float

module equines =

  let averageHornLength (equines : Equine list) =
    equines 
    |> List.choose (fun x -> 
      match x with
      | Unicorn u -> Some(u)
      | _ -> None)
    |> List.average

let herd = [ Horse ; Horse ; Unicorn(35.0) ; Horse ; Unicorn(50.0) ]

printfn "Average horn length in herd : %f" (equines.averageHornLength herd) // prints 42.5

OOP에 비해 FP는 데이터 / 함수 분리의 이점을 가지므로 수퍼 타입의 오브젝트 목록에서 특정 서브 타입으로 다운 캐스트 할 때 추상화 레벨을 위반하는 (정의되지 않은?) "유죄 양심"에서 벗어날 수 있습니다.

다른 답변에서 제안 된 OO 솔루션과 달리 패턴 일치는 다른 Horned 종이 Equine언젠가 나타날 경우 더 쉬운 확장 점을 제공합니다 .


2

같은 형식의 짧은 대답은 책이나 웹 기사를 읽어야합니다.

방문자 패턴

이 문제에는 말과 유니콘이 섞여 있습니다. Liskov 대체 원칙을 위반하는 것은 레거시 코드베이스에서 일반적인 문제입니다.

말과 모든 서브 클래스에 메소드 추가

Horse.visit(EquineVisitor v)

말 방문자 인터페이스는 java / c #에서 다음과 같이 보입니다.

interface EquineVisitor {
  void visitHorse(Horse z);
  void visitUnicorn(Unicorn z);
}

Unicorn.visit(EquineVisitor v){
   v.visitUnicorn(this);
}

Horse.visit(EquineVisitor v){
   v.visitHorse(this);
}

뿔을 측정하기 위해 우리는 지금 씁니다 ....

class HornMeasurer implements EquineVistor {
    void visitHorse(Horse h){} // ignore horses
    void visitUnicorn(Unicorn u){
         double len = u.getHornLength();
         totalLength+=len;
         unicornCount++;
    }

    double getAverageLength(){
          return totalLength/unicornCount;
    }

    double totalLength=0;
    int unicornCount=0;
}

방문객 패턴은 리팩토링과 성장을 더욱 어렵게 만든다는 비판을받습니다.

짧은 답변 : 디자인 패턴 방문자를 사용하여 이중 발송을 받으십시오.

또한 참조 https://en.wikipedia.org/wiki/Visitor_pattern

방문자에 대한 설명은 http://c2.com/cgi/wiki?VisitorPattern 을 참조하십시오 .

Gamma et al.의 Design Patterns도 참조하십시오.


나는 방문객 패턴으로 스스로 대답하려고했다. 누군가 이미 언급했는지 확인하는 놀라운 방법을 아래로 스크롤해야했습니다!
벤 서리

0

당신의 건축에서 유니콘이 말의 아종이라고 가정하고 당신 Horse이 그들 중 일부가있는 곳을 모을 장소를 만난 Unicorn다면, 나는 첫 번째 방법 ( .OfType<Unicorn>()...)을 개인적으로 갈 것입니다. . 나중에 (3 개월 안에 자신을 포함하여) 오는 사람이라면, 그 코드로 무엇을 성취하려고하는지 분명하게 알 수 있습니다 : 말 중에서 유니콘을 골라 내십시오.

나열된 다른 방법은 "유니콘입니까?"라는 질문을하는 또 다른 방법 인 것 같습니다. 예를 들어, 혼을 측정하는 일종의 예외 기반 방법을 사용하는 경우 다음과 같은 코드가있을 수 있습니다.

foreach (var horse in horses)
{
    try
    {
        var length = horse.MeasureHorn();
        //...
    }
    catch (NoHornException e)
    {
        continue;
    }
}

이제 예외는 어떤 것이 유니콘이 아니라는 표시가됩니다. 그리고 지금 이것은 더 이상 예외적 인 상황이 아니지만 정상적인 프로그램 흐름의 일부입니다. 그리고 if유형 검사를 수행 하는 것보다 예외를 사용하는 것이 더 더러운 것처럼 보입니다.

말의 뿔을 확인하기위한 마법의 가치 경로를 간다고합시다. 이제 수업은 다음과 같습니다.

class Horse
{
    public double MeasureHorn() { return -1; }
    //...
}

class Unicorn : Horse
{
    public override double MeasureHorn { return _hornLength; }
    //...
}

이제 여러분의 Horse수업은 수업에 대해 알아야하고, Unicorn신경 쓰지 않는 것을 처리 할 수있는 추가 방법이 있어야합니다. 이제 에서 상속하는 Pegasuss 및 Zebras 가 있다고 상상해보십시오 Horse. 이제 Horse필요 Fly뿐만 아니라 방법을 MeasureWings, CountStripes등, 그리고, Unicorn클래스도 이러한 방법을 가져옵니다. 이제 여러분의 수업은 서로에 대해 알아야하고, 타입 시스템에 "이게 유니콘인가요?"

그렇다면 어떤 경적 측정 값을 처리하고 모든 경적 측정을 처리 Horse할 수있는 것을 추가하는 것은 Unicorn어떻습니까? 자, 이제이 객체가 있는지 확인하여 어떤 것이 유니콘인지 확인해야합니다 (유일한 것을 다른 것으로 대체 함). 그것은 또한 지금 당신이 가지고있을 수있는 물을 조금 진흙List<Horse> unicorns실제로 모든 유니콘을 보유하고 있지만 유형 시스템과 디버거는 쉽게 말할 수 없습니다. "하지만 나는 그것이 모든 유니콘이라는 것을 알고 있습니다." 이름이 잘못되면 어떻게해야합니까? 또는 실제로 모든 유니콘이 될 것이라는 가정을 가지고 무언가를 썼지 만 요구 사항이 변경되어 이제 페 가시가 섞여있을 수 있습니까? (특히 레거시 소프트웨어 / sarcasm에서 이와 같은 일은 발생하지 않기 때문에) 이제 타입 시스템은 유니콘과 함께 페 가시를 행복하게 만듭니다. 변수가 List<Unicorn>컴파일러 (또는 런타임 환경) 로 선언 된 경우 페가시 또는 말을 혼합하려고하면 적합합니다.

마지막으로 이러한 모든 방법은 유형 시스템 검사를 대체합니다. 개인적으로, 나는 바퀴를 재발 명하지 않고 내 코드가 내장되어 있으며 수천 명의 다른 코더가 수천 번 테스트 한 것뿐만 아니라 작동하기를 바랍니다.

궁극적으로 코드는 이해할 수 있어야 합니다 . 컴퓨터는 어떻게 쓰는지에 관계없이 알아낼 것입니다. 당신은 그것을 디버깅하고 그것에 대해 추론 할 수있는 사람입니다. 작업을보다 쉽게하는 선택을하십시오. 어떤 이유로 든 다른 방법 중 하나가 표시되는 몇 지점에서보다 명확한 코드보다 큰 이점을 제공하는 경우 이점을 찾으십시오. 그러나 그것은 코드 기반에 달려 있습니다.


침묵의 예외는 분명히 나쁘다. 나의 제안은 if(horse.IsUnicorn) horse.MeasureHorn();예외였으며 예외는 발견되지 않았다. 그것들은 !horse.IsUnicorn당신이 유니콘 측정 상황에 있거나 MeasureHorn비 유니콘 에있을 때 트리거 될 것이다 . 그렇게하면 예외가 발생했을 때 오류를 가리지 않고 완전히 터져서 뭔가 수정해야 할 신호입니다. 분명히 특정 시나리오에만 적합하지만 실행 경로를 결정하기 위해 예외 발생을 사용하지 않는 구현입니다.
moarboilerplate

0

시맨틱 도메인이 IS-A 관계인 것처럼 들리지만, 특히 런타임 유형 반영으로 인해 하위 유형 / 상속을 사용하여이를 모델링하는 데 약간 조심해야합니다. 그러나 나는 당신이 틀린 것에 대해 무서워한다고 생각합니다. 서브 타이핑에는 실제로 위험이 따르지만 런타임에 객체를 쿼리한다는 사실은 문제가 아닙니다. 당신은 내가 무슨 뜻인지 알 수 있습니다.

객체 지향 프로그래밍은 IS-A 관계라는 개념에 크게 의존하고 있으며, 너무 많이 의존하여 두 가지 중요한 개념을 이끌어 냈습니다.

그러나 저는 이러한 어려움이없는 IS-A 관계를 살펴볼 수있는보다 기능적인 프로그래밍 기반의 다른 방법이 있다고 생각합니다. 먼저, 프로그램에서 말과 유니콘을 모델링하고 싶습니다. 그래서 우리는 a HorseUnicorntype 을 가질 것 입니다. 이 유형의 가치는 무엇입니까? 글쎄, 나는 이것을 말할 것이다.

  1. 이 유형의 값은 말과 유니콘에 대한 표현 또는 설명 입니다 (각각).
  2. 그것들은 스키마 화 된 표현 또는 설명입니다. 그것들은 자유 형식이 아니며 매우 엄격한 규칙에 따라 구성됩니다.

그것은 분명하게 들릴지 모르지만, 사람들이 원 타원 문제와 같은 문제를 일으키는 방법 중 하나는 그 점을 충분히 신경 쓰지 않는 것입니다. 모든 원이 타원이지만 원에 대한 모든 스키마 화 된 설명이 다른 스키마에 따라 자동으로 타원에 대한 스키마 화 된 설명임을 의미하지는 않습니다. 즉, 원이다해서 타원은이 것을 의미하지 않는다 Circle입니다 Ellipse말하자면,. 그러나 그것은 다음을 의미합니다.

  1. 모든 (도식화 된 원 설명)을 동일한 원을 설명 하는 (다른 유형의 설명) 으로 변환 하는 총 함수 가 있습니다 .CircleEllipse
  2. 그리고 원을 설명하는 경우 해당을 반환 하는 부분 함수 가 있습니다.EllipseCircle

따라서 함수형 프로그래밍 용어에서 Unicorn유형은 전혀 하위 유형일 필요가 없으며 Horse다음과 같은 작업 만하면됩니다.

-- Convert any unicorn-description of into a horse-description that
-- describes the same unicorns.
toHorse :: Unicorn -> Horse

-- If the horse described by the given horse-description is a unicorn,
-- then return a unicorn-description of that unicorn, otherwise return
-- nothing.
toUnicorn :: Horse -> Maybe Unicorn

그리고 toUnicorn올바른 역수 여야합니다 toHorse.

toUnicorn (toHorse x) = Just x

하스켈 Maybe타입은 다른 언어를 "옵션"타입이라고 부릅니다. 예를 들어, Java 8 Optional<Unicorn>유형은 Unicorn아무것도 아니거나 아무것도 아닙니다. 예외를 던지거나 "기본 또는 마법 값"을 반환하는 두 가지 대안은 옵션 유형과 매우 유사합니다.

따라서 기본적으로 내가 한 것은 하위 유형이나 상속을 사용하지 않고 IS-A 관계 개념을 유형과 기능 측면에서 재구성하는 것입니다. 내가 이것에서 빼앗아 갈 것은 :

  1. 모델에는 Horse유형 이 있어야합니다 .
  2. Horse유형은 어떤 값이 유니콘을 묘사하는지 명확하게 결정하기에 충분한 정보를 인코딩해야합니다.
  3. Horse유형 의 일부 작업은 해당 유형의 클라이언트가 주어진 정보 Horse가 유니콘 인지 여부를 관찰 할 수 있도록 해당 정보를 노출해야합니다 .
  4. Horse유형 의 클라이언트는 유니콘과 말을 구별하기 위해 런타임에 후자의 작업을 사용해야합니다.

따라서 이것은 기본적으로 " Horse유니콘이든 모든 것을 묻습니다 "모델입니다. 당신은 그 모델을 조심하지만, 나는 그렇게 잘못 생각합니다. Horses 목록을 제공 할 경우 , 유형이 보증하는 것은 목록의 항목이 설명하는 것이 말이므로 불가피하게 런타임 중에 어떤 것이 유니콘인지 알려야합니다. 따라서이 문제를 해결할 수는 없습니다. 귀하를 대신 할 작업을 구현해야합니다.

객체 지향 프로그래밍에서 익숙한 방법은 다음과 같습니다.

  • Horse유형;
  • Unicorn의 하위 유형으로 Horse;
  • 주어진 유형 Horse이 인지 여부를 식별하는 클라이언트 액세스 가능 조작으로 런타임 유형 반영을 사용하십시오 Unicorn.

위에서 설명한 "thing vs. description"각도에서 볼 때 이것은 큰 약점을 가지고 있습니다.

  • 당신은 어떤 경우 Horse유니콘을 설명하지만,없는 경우 Unicorn인스턴스를?

처음으로 돌아가서, 이것은 IS-A 관계를 모델링하기 위해 서브 타이핑 및 다운 캐스트를 사용하는 데있어 정말 무서운 부분이라고 생각합니다. 런타임 검사를 수행 할 필요는 없습니다. 묻는 타이포그래피를 조금 남용 Horse그것이 여부 Unicorn예를하는 것은 요구와 동의어가 아니다 Horse그것이 유니콘 (그것이인지 여부 Horse도 유니콘하는 말의 -description). Horses클라이언트가 Horse유니콘을 설명하는 코드를 만들 려고 할 때마다 Unicorn클래스가 인스턴스화 되도록 구성하는 코드를 캡슐화하기 위해 프로그램이 많은 시간을 투자하지 않았다면 . 내 경험상 프로그래머가이 일을 신중하게하는 일은 거의 없다.

따라서 Horses를 Unicorns 로 변환하는 명시적이고 비 다운 캐스트 작업이있는 접근법 을 사용 합니다. 이것은 다음 Horse유형 의 메소드 일 수 있습니다 .

interface Horse {
    // ...
    Optional<Unicorn> toUnicorn();
}

또는 외부 대상 일 수도 있습니다 ( "말이 유니콘인지 아닌지를 알려주는 말의 분리 된 개체").

class HorseToUnicornCoercion {
    Optional<Unicorn> convert(Horse horse) {
       // ...
    }
}

이들 사이의 선택은 당신의 프로그램이 편성 된 방법 두 경우 모두, 당신이 내에 해당이의 문제입니다 Horse -> Maybe Unicorn운영 위에서, 당신은 단지 (다른 방법으로 그것을 포장하고 그 인정 하듯이 어떤 작업상의 파급 효과있을 것이다 Horse형의 요구를 고객에게 노출).


-1

다른 답변에 대한 OP의 의견은 질문을 명확히했습니다.

그것은 또한 질문이 요구하는 것의 일부입니다. 내가 말의 무리를 가지고 있고 그들 중 일부가 개념적으로 유니콘이라면, 너무 많은 부정적인 영향없이 문제를 깨끗하게 해결할 수 있도록 어떻게 존재해야합니까?

그런 식으로 말하면 더 많은 정보가 필요하다고 생각합니다. 대답은 아마도 여러 가지에 달려 있습니다.

  • 우리의 언어 시설. 예를 들어, 아마도 루비와 자바 스크립트 및 Java에서 다르게 접근 할 것입니다.
  • 개념 자체 : 무엇 이다 말은 무엇 이고 유니콘은? 어떤 데이터가 각각 관련되어 있습니까? 경적을 제외하고는 정확히 동일합니까, 아니면 다른 차이점이 있습니까?
  • 혼 길이 평균을 제외하고 어떻게 다른 방법으로 사용합니까? 그리고 무리는 어떻습니까? 어쩌면 우리도 그것들을 모델링해야합니까? 우리는 다른 곳에서 사용합니까? herd.averageHornLength()우리의 개념적 모델과 일치하는 것 같습니다.
  • 말과 유니콘 개체는 어떻게 만들어 집니까? 리팩토링 범위 내에서 코드를 변경하고 있습니까?

그러나 일반적으로 상속 및 하위 유형에 대해서는 생각조차하지 않습니다. 개체 목록이 있습니다. 이러한 개체 중 일부는 hornLength()방법 이 있기 때문에 유니콘으로 식별 될 수 있습니다 . 이 유니콘 고유 속성을 기준으로 목록을 필터링하십시오. 이제 문제는 유니콘 목록의 뿔 길이를 평균화하는 것으로 축소되었습니다.

OP, 여전히 오해하고 있는지 알려주세요.


1
공정한 포인트. 문제가 더욱 추상적이되지 않도록하기 위해서는 다음과 같은 합리적인 가정을해야합니다. 1) 강력하게 형식화 된 언어 2) 무리는 말로 인해 말을 한 가지 유형으로 제한합니다. 3) 오리 타자 같은 기술은 피해야합니다. . 무엇을 바꿀 수 있는지에 관해서는 반드시 제한이있는 것은 아니지만, 각 유형의 변경에는 고유 한 결과가 있습니다 ...
moarboilerplate

무리가 말을 한 유형으로 제한한다면, 우리가 유일한 선택 상속 (그 옵션을 좋아하지 않음) 또는 HerdMember말 또는 유니콘으로 초기화 하는 래퍼 객체 (예 : 말과 유니콘이 하위 유형 관계를 필요로 하지 않음 ) ). HerdMember그렇다면 자유롭게 구현할 수 isUnicorn()있지만 적합하다고 생각되는 필터 솔루션은 다음과 같습니다.
요나

일부 언어에서는 hornLength ()를 혼합하여 사용할 수 있으며 유효한 경우 유효한 솔루션이 될 수 있습니다. 그러나 타이핑이 덜 유연한 언어에서는 동일한 작업을 수행하기 위해 일부 해킹 기술에 의존하거나 말에 혼 길이를 두는 것과 같은 말을해야 코드에서 혼동을 일으킬 수 있습니다. t 개념적으로 뿔이 있습니다. 또한, 기본값을 포함하여 수학적 계산을 수행하는 경우 결과가 왜곡 될 수 있습니다 (원래 질문의 설명 참조)
moarboilerplate

그러나 믹스 인은 런타임이 아닌 한 다른 이름으로 상속됩니다. 귀하의 의견 "말에는 개념적으로 뿔이 없습니다"는 말과 유니콘을 모델링하는 방법과 서로의 관계가 무엇인지를 포함해야하는 경우, 말의 내용에 대해 더 알아야 할 필요성에 대한 나의 의견과 관련이 있습니다. 기본값을 포함하는 모든 솔루션은 잘못된 imo입니다.
요나

이 문제의 특정 징후에 대한 정확한 해결책을 얻으려면 많은 맥락이 필요하다는 것이 정확합니다. 뿔이 달린 말에 대한 귀하의 질문에 대답하고 믹스 인에 다시 묶기 위해 나는 뿔 길이가 유니콘이 아닌 말에 섞인 시나리오가 오류라고 생각했습니다. 예외를 발생시키는 hornLength에 대한 기본 구현이있는 스칼라 특성을 고려하십시오. 유니콘 유형은 해당 구현을 무시할 수 있으며, 말이 hornLength를 평가하는 컨텍스트로 만들면 예외입니다.
moarboilerplate

-2

IEnumerable을 반환하는 GetUnicorns () 메서드는 나에게 가장 우아하고 유연하며 보편적 인 솔루션 인 것 같습니다. 이 방법으로 말이 클래스 유형이나 특정 속성의 가치뿐만 아니라 유니콘으로 전달 될지 여부를 결정하는 모든 특성을 처리 할 수 ​​있습니다.


동의합니다. Mason Wheeler는 그의 대답에 좋은 해결책을 가지고 있지만, 다른 장소에서 여러 가지 이유로 유니콘을 골라 내야 할 경우 코드에는 많은 horses.ofType<Unicorn>...구조가 있습니다. GetUnicorns기능 을 갖는 것은 하나의 라이너이지만, 발신자의 관점에서 말 / 유니콘 관계의 변화에 ​​더 저항 할 것입니다.
Shaz

@Ryan을 반환하면 IEnumerable<Horse>유니콘 기준이 한곳에 있지만 캡슐화되어 있으므로 발신자가 유니콘이 필요한 이유에 대해 가정해야합니다 (오늘날 수프를 주문하여 조개 스프를 얻을 수는 있지만 t 내일 같은 것을 수행하여 얻을 수 있음을 의미). 또한의 경적에 대한 기본값을 표시해야합니다 Horse. 경우 Unicorn자신의 유형, 당신은 오버 헤드를 도입 할 수 타입 매핑을, 새로운 유형을 생성하고 유지해야합니다.
moarboilerplate

1
@ moarboilerplate : 우리는 솔루션을지지하는 모든 것을 고려합니다. 아름다움 부분은 유니콘의 구현 세부 사항과 독립적이라는 것입니다. 데이터 멤버, 수업 또는 시간을 기준으로 차별하든 (달이 내가 아는 모든 것이 옳다면 자정에 말들이 모두 유니콘으로 변할 수 있습니다), 해결책은 그대로 있습니다. 인터페이스는 동일하게 유지됩니다.
Martin Maat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.