2008-10-28 87 views
16

我創建了一系列的構建器來清理爲我的模擬創建領域類的語法,作爲改進我們的整體單元測試的一部分。我的構建者實質上填充了一個域類(例如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我不能在BaseBuilderWithId(int)方法使用return this

是暴露子類型的屬性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() 
); 

回答

11

我可以說的是,如果有做這件事的方式,我想了解一下它也是 - 我使用正好這個模式在我的Protocol Buffers port。事實上,我很高興看到有人採用了它 - 這意味着我們至少有可能是對的!

+0

YUP看起來像我們堅持「抽象的T這{get;}」我會接受和關閉,因爲我認爲解決方案是所有可用的。 – cfeduke 2008-10-29 14:50:11

+0

我試圖找到一個相對清晰的方式來創建建設者,並發現文章(http://www.codeproject.com/Articles/240756/Hierarchically-Implementing-theBolchs-Builder-Pat)。它提供了一次投射(受保護的BLDR _this; _this =(BLDR)this; return _this;),但我不明白爲什麼作者添加了類WindowBuilder:WindowBuilder {}文章的底部)。在這篇文章中,我看到幾乎相同的模式,但cfeduke使用類ScheduleIntervalBuilder: BaseBuilder ... – 2012-06-22 21:59:49

2

這是一個很好的C#實施策略。

其他一些語言(無法想到我見過的研究語言的名稱)有類型系統,直接支持協變「自我」/「這個」,或者有其他聰明的方式來表達這種模式,但是對於C#的類型系統,這是一個很好的(唯一的)解決方案。

3

我知道這是一個老問題,但我認爲你可以用一個簡單的鑄件,以避免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; 
    } 
}