문제

전체 단위 테스트 개선의 일부로 모의 도메인 클래스를 생성하는 구문을 정리하기위한 일련의 빌더를 만들고 있습니다. 내 빌더는 본질적으로 도메인 클래스를 채 웁니다 (예 : Schedule) 적절한 것을 호출하여 결정된 일부 값 WithXXX 그리고 그들을 함께 정리합니다.

나는 내 건축업자들 사이에서 공통점을 발견했으며 코드 재사용을 증가시키기 위해 기본 클래스로이를 추상화하고 싶습니다. 불행히도 내가 끝나는 것은 다음과 같습니다.

public abstract class BaseBuilder<T,BLDR> where BLDR : BaseBuilder<T,BLDR> 
                                          where T : new()
{
    public abstract T Build();

    protected int Id { get; private set; }
    protected abstract BLDR This { get; }

    public BLDR WithId(int id)
    {
        Id = id;
        return This;
    }
}

특별히 주목하십시오 protected abstract BLDR This { get; }.

도메인 클래스 빌더의 샘플 구현은 다음과 같습니다.

public class ScheduleIntervalBuilder :
    BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder>
{
    private int _scheduleId;
    // ...

    // UG! here's the problem:
    protected override ScheduleIntervalBuilder This
    {
        get { return this; }
    }

    public override ScheduleInterval Build()
    {
        return new ScheduleInterval
        {
            Id = base.Id,
            ScheduleId = _scheduleId
                    // ...
        };
    }

    public ScheduleIntervalBuilder WithScheduleId(int scheduleId)
    {
        _scheduleId = scheduleId;
        return this;
    }

    // ...
}

BLDR은 유형 BaseBuilder가 아니기 때문에 사용할 수 없습니다. return this 에서 WithId(int) 의 방법 BaseBuilder.

자녀 유형을 속성과 함께 노출하고 있습니다 abstract BLDR This { get; } 여기에 나의 유일한 옵션, 아니면 구문 트릭이 누락 되었습니까?

업데이트 (내가 왜 이것을 좀 더 명확하게하는지 보여줄 수 있기 때문에) :

최종 결과는 [프로그래머] 읽기 가능한 형식으로 데이터베이스에서 검색 할 것으로 예상되는 프로파일 링 도메인 클래스를 구축하는 빌더가있는 것입니다. 아무 문제가 없습니다 ...

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new Schedule
    {
        ScheduleId = 1
        // ...
    }
);

이미 읽을 수 있습니다. 대체 빌더 구문은 다음과 같습니다.

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new ScheduleBuilder()
        .WithId(1)
        // ...
        .Build()
);

제가 빌더를 사용하여 찾는 장점 (그리고이 모든 것을 구현합니다. WithXXX 방법)은 복잡한 속성 생성을 추상화하는 것입니다 (데이터베이스 조회 값을 올바른 상태로 자동 확장 Lookup.KnownValues 데이터베이스를 분명히 누르지 않고) 빌더가 도메인 클래스에 일반적으로 재사용 가능한 테스트 프로파일을 제공하는 ...

mock.Expect(m => m.Select(It.IsAny<int>())).Returns(
    new ScheduleBuilder()
        .AsOneDay()
        .Build()
);
도움이 되었습니까?

해결책

내가 말할 수있는 것은 거기 있다면 ~이다 그것을하는 방법, 나는 그것에 대해 알고 싶다 - 나는 사용한다. 바로 그거죠 내 패턴 프로토콜 버퍼 포트. 사실, 나는 다른 사람이 그것에 의지했다는 것을 알게되어 기쁩니다. 그것은 우리가 적어도 어느 정도 옳을 가능성이 있다는 것을 의미합니다!

다른 팁

나는 이것이 오래된 질문이라는 것을 알고 있지만, 당신은 간단한 캐스트를 사용하여 피할 수 있다고 생각합니다. abstract BLDR This { get; }

결과 코드는 다음과 같습니다.

public abstract class BaseBuilder<T, BLDR> where BLDR : BaseBuilder<T, BLDR>
                                           where T : new()
{
    public abstract T Build();

    protected int Id { get; private set; }

    public BLDR WithId(int id)
    {
        _id = id;
        return (BLDR)this;
    }
}

public class ScheduleIntervalBuilder :
    BaseBuilder<ScheduleInterval,ScheduleIntervalBuilder>
{
    private int _scheduleId;
    // ...

    public override ScheduleInterval Build()
    {
        return new ScheduleInterval
        {
                Id = base.Id,
                ScheduleId = _scheduleId
                    // ...
        };
    }

    public ScheduleIntervalBuilder WithScheduleId(int scheduleId)
    {
        _scheduleId = scheduleId;
        return this;
    }

    // ...
}

물론 빌더를 캡슐화 할 수 있습니다

protected BLDR This
{
    get
    {
        return (BLDR)this;
    }
}

이것은 C#에 대한 좋은 구현 전략입니다.

다른 언어 (내가 본 연구 언어의 이름을 생각할 수 없음)는 공분산 "self"/"this"를 직접 지원 하거나이 패턴을 표현하는 다른 영리한 방법이 있지만 C#의 S로 유형 시스템이 있습니다. 유형 시스템, 이것은 좋은 솔루션입니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top