2008-10-28 35 views
16

Tôi đang tạo một chuỗi các nhà xây dựng để làm sạch cú pháp tạo các lớp miền cho các mocks của tôi như là một phần của việc cải thiện các thử nghiệm đơn vị tổng thể của chúng tôi. Các nhà xây dựng của tôi về cơ bản có một lớp miền (chẳng hạn như một Schedule) với một số giá trị được xác định bằng cách gọi thích hợp WithXXX và ghép chúng lại với nhau.Mẫu thiết kế Builder với thừa kế: có cách nào tốt hơn không?

Tôi đã gặp phải một số điểm chung giữa các nhà xây dựng của mình và tôi muốn trừu tượng hóa thành lớp cơ sở để tăng cường sử dụng lại mã. Thật không may những gì tôi kết thúc với hình như:

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; 
    } 
} 

Ghi chú đặc biệt của protected abstract BLDR This { get; }.

Một thi mẫu của một người thợ xây lớp miền là:

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; 
    } 

    // ... 
} 

Vì BLDR không phải là loại BaseBuilder tôi không thể sử dụng return this trong phương pháp WithId(int) của BaseBuilder.

Đang hiển thị loại con với thuộc tính abstract BLDR This { get; } tùy chọn duy nhất của tôi ở đây hoặc tôi thiếu một số mẹo cú pháp?

Cập nhật (kể từ khi tôi có thể hiển thị do tại sao tôi đang làm điều này rõ ràng hơn một chút):

Kết quả cuối cùng là phải có nhà xây dựng mà xây dựng cấu hình các lớp miền mà người ta mong đợi để lấy từ cơ sở dữ liệu trong một [ lập trình viên] định dạng có thể đọc được. Không có gì sai với ...

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

vì điều này khá dễ đọc. Cú pháp xây dựng thay thế là:

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

lợi thế tôi đang tìm ra của việc sử dụng các nhà xây dựng (và thực hiện tất cả các phương pháp WithXXX) là để tạo tài sản đi phức tạp trừu tượng (tự động mở rộng giá trị tra cứu cơ sở dữ liệu của chúng tôi với đúng Lookup.KnownValues mà không va vào cơ sở dữ liệu rõ ràng) và có người xây dựng cung cấp hồ sơ kiểm tra thường tái sử dụng cho các lớp miền ...

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

Trả lời

11

Tất cả tôi có thể nói là nếu có một cách để làm việc đó, tôi muốn biết về nó cũng vậy - tôi sử dụng chính xác mẫu này trong số Protocol Buffers port của tôi. Trong thực tế, tôi vui mừng khi thấy rằng một người nào khác đã sử dụng nó - điều đó có nghĩa là chúng ta ít nhất có khả năng là đúng!

+0

Có vẻ như chúng tôi đang mắc kẹt với "tóm tắt T This {get;}" Tôi sẽ chấp nhận và đóng vì tôi nghĩ giải pháp là tất cả những gì có sẵn. – cfeduke

+0

Tôi đang cố gắng tìm một cách tương đối rõ ràng để tạo các nhà xây dựng và tìm thấy bài viết đó (http://www.codeproject.com/Articles/240756/Hierarchically-Implementing-the-Bolchs-Builder-Pat). Nó cung cấp để đúc nó một lần. (Bảo vệ BLDR _this; _this = (BLDR) này; return _this;), nhưng tôi không thể hiểu tại sao tác giả đã thêm lớp WindowBuilder: WindowBuilder {} (Xem ví dụ hoàn chỉnh tại cuối bài viết). Trong bài này tôi thấy hầu như cùng một khuôn mẫu, nhưng cfeduke sử dụng class ScheduleIntervalBuilder: BaseBuilder ... –

+0

Personaly Tôi có lẽ sẽ thực hiện mỗi Builder mà không có lớp cơ sở. Tôi luôn cố gắng không sử dụng mẫu * tự chung này *: http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx – Pellared

2

Đây là chiến lược triển khai tốt cho C#.

Một số ngôn ngữ khác (không thể nghĩ tên ngôn ngữ nghiên cứu mà tôi đã thấy) có hệ thống kiểu hỗ trợ trực tiếp "tự"/"này" hoặc có các cách thông minh khác để thể hiện mẫu này , nhưng với hệ thống kiểu C#, đây là giải pháp tốt (chỉ?).

+0

Cảm ơn bạn đã nhập, tôi nghĩ rằng "tóm tắt T This {get;}" không phải là một mức giá cao phải trả. :) – cfeduke

3

Tôi biết đây là một câu hỏi cũ, nhưng tôi nghĩ rằng bạn có thể sử dụng một dàn diễn viên đơn giản để tránh những abstract BLDR This { get; }

Các mã kết quả sau đó sẽ là:

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; 
    } 

    // ... 
} 

Tất nhiên bạn có thể gói gọn trong xây dựng với

protected BLDR This 
{ 
    get 
    { 
     return (BLDR)this; 
    } 
} 
Các vấn đề liên quan