Tôi nên sử dụng một phương thức nhà máy thay vì một nhà xây dựng. Tôi có thể thay đổi điều đó mà vẫn tương thích ngược không?


15

Vấn đề

Hãy nói rằng tôi có một lớp được gọi là DataSource cung cấp một ReadDataphương thức (và có thể là các phương thức khác, nhưng hãy giữ mọi thứ đơn giản) để đọc dữ liệu từ một .mdbtệp:

var source = new DataSource("myFile.mdb");
var data = source.ReadData();

Vài năm sau, tôi quyết định rằng tôi muốn có thể hỗ trợ .xmlcác tệp ngoài.mdb các tệp dưới dạng nguồn dữ liệu. Việc thực hiện "đọc dữ liệu" khá khác nhau đối với các tệp .xml.mdbtệp; do đó, nếu tôi thiết kế hệ thống từ đầu, tôi sẽ định nghĩa nó như thế này:

abstract class DataSource {
    abstract Data ReadData();
    static DataSource OpenDataSource(string fileName) {
        // return MdbDataSource or XmlDataSource, as appropriate
    }
}

class MdbDataSource : DataSource {
    override Data ReadData() { /* implementation 1 */ }
}

class XmlDataSource : DataSource {
    override Data ReadData() { /* implementation 2 */ }
}

Tuyệt vời, một triển khai hoàn hảo của mẫu phương thức Factory. Thật không may, DataSourcenằm trong một thư viện và tái cấu trúc mã như thế này sẽ phá vỡ tất cả các cuộc gọi hiện có của

var source = new DataSource("myFile.mdb");

trong các khách hàng khác nhau sử dụng thư viện. Khốn nạn cho tôi, tại sao tôi không sử dụng phương pháp nhà máy ngay từ đầu?


Các giải pháp

Đây là những giải pháp tôi có thể đưa ra:

  1. Làm cho hàm tạo DataSource trả về một kiểu con ( MdbDataSourcehoặc XmlDataSource). Điều đó sẽ giải quyết tất cả các vấn đề của tôi. Thật không may, C # không hỗ trợ điều đó.

  2. Sử dụng các tên khác nhau:

    abstract class DataSourceBase { ... }    // corresponds to DataSource in the example above
    
    class DataSource : DataSourceBase {      // corresponds to MdbDataSource in the example above
        [Obsolete("New code should use DataSourceBase.OpenDataSource instead")]
        DataSource(string fileName) { ... }
        ...
    }
    
    class XmlDataSource : DataSourceBase { ... }

    Đó là những gì tôi đã kết thúc bằng cách sử dụng vì nó giữ cho mã tương thích ngược (nghĩa là các cuộc gọi new DataSource("myFile.mdb")vẫn hoạt động). Nhược điểm: Các tên không được mô tả như họ cần phải có.

  3. Tạo DataSourcemột "trình bao bọc" để thực hiện thực tế:

    class DataSource {
        private DataSourceImpl impl;
    
        DataSource(string fileName) {
            impl = ... ? new MdbDataSourceImpl(fileName) : new XmlDataSourceImpl(fileName);
        }
    
        Data ReadData() {
            return impl.ReadData();
        }
    
        abstract private class DataSourceImpl { ... }
        private class MdbDataSourceImpl : DataSourceImpl { ... }
        private class XmlDataSourceImpl : DataSourceImpl { ... }
    }

    Nhược điểm: Mọi phương thức nguồn dữ liệu (như ReadData) phải được định tuyến bằng mã soạn sẵn. Tôi không thích mã soạn sẵn. Nó dư thừa và làm lộn xộn mã.

Có bất kỳ giải pháp thanh lịch mà tôi đã bỏ lỡ?


5
Bạn có thể giải thích vấn đề của bạn với # 3 chi tiết hơn một chút không? Điều đó có vẻ thanh lịch với tôi. (Hoặc thanh lịch như bạn nhận được trong khi duy trì khả năng tương thích ngược.)
pdr

Tôi xác định một giao diện xuất bản API và sau đó sử dụng lại phương thức hiện có bằng cách nhà máy tạo một trình bao bọc xung quanh mã cũ của bạn và mã mới bằng cách nhà máy tạo ra các thể hiện tương ứng của các lớp thực hiện giao diện.
Thomas

@pdr: 1. Mọi thay đổi đối với chữ ký phương thức phải được thực hiện ở một nơi nữa. 2. Tôi có thể đặt các lớp Impl bên trong và riêng tư, điều này bất tiện nếu khách hàng muốn truy cập chức năng cụ thể chỉ có sẵn trong, ví dụ: nguồn dữ liệu Xml. Hoặc tôi có thể công khai chúng, điều đó có nghĩa là các khách hàng hiện có hai cách khác nhau để làm cùng một việc.
Heinzi

2
@Heinzi: Tôi thích tùy chọn 3. Đây là mẫu "Mặt tiền" tiêu chuẩn. Bạn nên kiểm tra xem bạn có thực sự ủy quyền mọi phương thức nguồn dữ liệu cho việc triển khai hay chỉ một số. Có lẽ vẫn còn một số mã chung tồn tại trong "DataSource"?
Doc Brown

Thật xấu hổ khi đó newkhông phải là một phương thức của đối tượng lớp (để bạn có thể phân lớp chính nó - một kỹ thuật được gọi là siêu dữ liệu - và kiểm soát những gì newthực sự làm) nhưng đó không phải là cách C # (hoặc Java hoặc C ++) hoạt động.
Donal Fellows

Câu trả lời:


12

Tôi sẽ chọn một biến thể cho tùy chọn thứ hai của bạn cho phép bạn loại bỏ tên cũ, quá chung chung DataSource:

abstract class AbstractDataSource { ... } // corresponds to the abstract DataSource in the ideal solution

class XmlDataSource : AbstractDataSource { ... }
class MdbDataSource : AbstractDataSource { ... } // contains all the code of the existing DataSource class

[Obsolete("New code should use AbstractDataSource instead")]
class DataSource : MdbDataSource { // an 'empty shell' to keep old code working.
    DataSource(string fileName) { ... }
}

Hạn chế duy nhất ở đây là lớp cơ sở mới không thể có tên rõ ràng nhất, vì tên đó đã được yêu cầu cho lớp gốc và cần duy trì như thế để tương thích ngược. Tất cả các lớp khác có tên mô tả của họ.


1
+1, đó chính xác là những gì tôi nghĩ đến khi tôi đọc câu hỏi. Mặc dù tôi thích tùy chọn 3 của OP hơn.
Doc Brown

Lớp cơ sở có thể có tên rõ ràng nhất nếu đặt tất cả mã mới vào một không gian tên mới. Nhưng tôi không chắc đó là một ý tưởng tốt.
Svick

Lớp cơ sở nên có hậu tố "Base". lớp DataSourceBase
Stephen

6

Giải pháp tốt nhất sẽ là một cái gì đó gần với lựa chọn số 3 của bạn. Giữ DataSourcephần lớn như hiện tại và trích xuất phần độc giả vào lớp riêng của nó.

class DataSource {
    private Reader reader;

    DataSource(string fileName) {
        reader = ... ? new MdbReader(fileName) : new XmlReader(fileName);
    }

    Data ReadData() {
        return reader.next();
    }

    abstract private class Reader { ... }
    private class MdbReader : Reader { ... }
    private class XmlReader : Reader { ... }
}

Bằng cách này, bạn tránh được mã trùng lặp và được mở cho các tiện ích mở rộng hơn nữa.


+1, tùy chọn đẹp. Tuy nhiên, tôi vẫn thích tùy chọn 2 hơn vì nó cho phép tôi truy cập chức năng dành riêng cho XmlDataSource bằng cách khởi tạo rõ ràng XmlDataSource.
Heinzi
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.