Tôi nên xử lý các cấu hình không tương thích với mẫu Builder như thế nào?


9

Điều này được thúc đẩy bởi câu trả lời này cho một câu hỏi riêng biệt .

Mẫu trình xây dựng được sử dụng để đơn giản hóa việc khởi tạo phức tạp, đặc biệt là với các tham số khởi tạo tùy chọn). Nhưng tôi không biết cách quản lý đúng cấu hình loại trừ lẫn nhau.

Đây là một Imagelớp học. Imagecó thể được khởi tạo từ một tập tin hoặc từ một kích thước, nhưng không phải cả hai . Sử dụng các hàm tạo để thực thi loại trừ lẫn nhau này là hiển nhiên khi lớp đủ đơn giản:

public class Image
{
    public Image(Size size, Thing stuff, int range)
    {
    // ... initialize empty with size
    }

    public Image(string filename, Thing stuff, int range)
    {
        // ... initialize from file
    }
}

Bây giờ giả sử Imagethực sự đủ cấu hình để mẫu xây dựng trở nên hữu ích, đột nhiên điều này có thể xảy ra:

Image image = new ImageBuilder()
                  .setStuff(stuff)
                  .setRange(range)
                  .setSize(size)           // <----------  NOT
                  .setFilename(filename)   // <----------  COMPATIBLE
                  .build();

Những vấn đề này phải được nắm bắt trong thời gian chạy thay vì tại thời gian biên dịch, đó không phải là điều tồi tệ nhất. Vấn đề là việc phát hiện một cách nhất quán và toàn diện những vấn đề này trong ImageBuilderlớp có thể trở nên phức tạp, đặc biệt là về mặt bảo trì.

Làm thế nào tôi nên đối phó với các cấu hình không tương thích trong mẫu xây dựng?

Câu trả lời:


12

Bạn đã có Builder của bạn. Tuy nhiên, tại thời điểm này bạn cần một số giao diện.

Có một giao diện FileBuilder xác định một tập hợp con các phương thức (không setSize) và giao diện SizeBuilder xác định một tập hợp con khác của các phương thức (không setFilename). Bạn có thể muốn có giao diện GenericBuilder mở rộng FileBuilder và SizeBuilder - không cần thiết mặc dù một số người có thể thích cách tiếp cận đó.

Phương thức setSize()trả về SizeBuilder. Phương thức setFilename()trả về một FileBuilder.

ImageBuilder có tất cả logic cho cả hai setSize()setFileName(). Tuy nhiên, kiểu trả về cho những thứ này sẽ chỉ định giao diện tập hợp con thích hợp.

class ImageBulder implements FileBuilder, SizeBuilder {
    ImageBuilder() {
        doInitThings;
    }

    ImageBuilder setStuff(Thing) {
        doStuff;
        return this;
    }

    ImageBuilder setRange(int range) {
        rangeStuff;
        return this;
    }

    SizeBuilder setSize(Size size) {
        stuff;
        return this;
    }

    FileBuilder setFilename(String filename) {
        otherStuff;
        return this;
    }

    Image build() {
        return new Image(...);
    }
}

Một điều đặc biệt ở đây là một khi bạn có SizeBuilder, tất cả lợi nhuận cần phải là SizeBuilders. Giao diện cho nó trông giống như:

interface SizeBuilder {
    SizeBuilder setRange(int range);
    SizeBuilder setSize(Size size);
    SizeBuilder setStuff(Thing stuff);
    Image build();
}

interface FileBuilder {
    FileBuilder setRange(int range);
    FileBuilder setFilename(String filename);
    FileBuilder setStuff(Thing stuff);
    Image build();
}

Do đó, một khi bạn gọi một trong các phương thức đó, bây giờ bạn không thể gọi phương thức kia và tạo một đối tượng có trạng thái không hợp lệ.


Thực sự thú vị, cảm ơn bạn. Tôi hơi bối rối về cách những thứ này sẽ được sử dụng mặc dù. Cụ thể, tôi không thể tìm ra các kiểu khai báo và khởi tạo sẽ là gì. Có lẽ tôi chỉ đang tưởng tượng mọi thứ phức tạp hơn mức cần thiết. Bạn có thể bao gồm một ví dụ sử dụng?
kdbanman

Trình xây dựng hình ảnh trả về giao diện tương ứng với thay đổi trạng thái mà phương thức đó gọi. Tuy nhiên, khi bạn lấy lại một giao diện cụ thể từ ImageBuilder, các cuộc gọi trong tương lai với đối tượng đó được thực hiện trên giao diện đó nhằm hạn chế khả năng gọi các phương thức không tương thích.

1
@rwong trong khi tôi thừa nhận không nhìn sâu vào nó, vấn đề mà tôi nghĩ rằng tôi gặp phải với cách tiếp cận như vậy là 'trạng thái' của người xây dựng có thể được đặt lại. Người ta sẽ cần đảm bảo rằng một khi setSize () được gọi, tất cả các lệnh xây dựng tiếp theo đều có trên SizeBuilder. Nếu loại setRange () không phải là SizeBuilder hoặc thứ gì đó mở rộng / thực hiện người ta có thể sử dụng để gọi lại setFilename trên nó. Bạn cũng có tình huống (không được mô tả ở đây) trong đó thay vì kích thước bạn có int width và int height sao cho cả hai cần phải được gọi.

1
@MichaelT Với các vấn đề phức tạp về lách luật, tôi nghi ngờ rằng việc thực thi một thứ tự nghiêm ngặt về khởi tạo tham số (dẫn đến một cây tiền tố của các mục tham số) có thể là một điều tốt khi sử dụng mẫu trình xây dựng. Kết quả là, các mục tham số phổ biến như RangeStuffphải được khởi tạo lúc đầu, không phải vào các thời điểm tùy ý.
rwong 11/07/2015

1
@MichaelT: tại thời điểm đó, LSP đi vào hoạt động. Bạn có thể chắc chắn các phương thức của loại rõ ràng ( RangeAndStuffBuilder) có thể được gọi trên loại thực tế. Các hạn chế hơn nữa có thể được thực hiện bằng cách trả về các loại cơ bản hơn cho một số phương thức (mặc dù điều này sẽ gây ra sự gia tăng theo cấp số nhân của các loại), loại bỏ hiệu quả các hoạt động. Miễn là kết quả phương thức không quay trở lại cấu trúc phân cấp, bạn sẽ không gặp lỗi loại. Các setHeight/ setWidthkịch bản có thể được thực hiện với một hệ thống phân cấp anh chị em mà không có một buildphương pháp.
outis
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.