Tôi có bị lạm dụng đóng gói?


11

Tôi đã nhận thấy một cái gì đó trong mã của tôi trong các dự án khác nhau có vẻ như mã mùi đối với tôi và một cái gì đó xấu để làm, nhưng tôi không thể đối phó với nó.

Trong khi cố gắng viết "mã sạch", tôi có xu hướng sử dụng quá mức các phương thức riêng tư để làm cho mã của tôi dễ đọc hơn. Vấn đề là mã thực sự sạch hơn nhưng cũng khó kiểm tra hơn (vâng tôi biết tôi có thể kiểm tra các phương thức riêng tư ...) và nói chung nó có vẻ là một thói quen xấu đối với tôi.

Đây là một ví dụ về một lớp đọc một số dữ liệu từ tệp .csv và trả về một nhóm khách hàng (một đối tượng khác với các trường và thuộc tính khác nhau).

public class GroupOfCustomersImporter {
    //... Call fields ....
    public GroupOfCustomersImporter(String filePath) {
        this.filePath = filePath;
        customers = new HashSet<Customer>();
        createCSVReader();
        read();
        constructTTRP_Instance();
    }

    private void createCSVReader() {
        //....
    }

    private void read() {
        //.... Reades the file and initializes the class attributes
    }

    private void readFirstLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readSecondLine(String[] inputLine) {
        //.... Method used by the read() method
    }

    private void readCustomerLine(String[] inputLine) { 
        //.... Method used by the read() method
    }

    private void constructGroupOfCustomers() {
        //this.groupOfCustomers = new GroupOfCustomers(**attributes of the class**);
    }

    public GroupOfCustomers getConstructedGroupOfCustomers() {
        return this.GroupOfCustomers;
    }
}

Như bạn có thể thấy lớp chỉ có một hàm tạo gọi một số phương thức riêng để hoàn thành công việc, tôi biết rằng nói chung đó không phải là một cách thực hành tốt nhưng tôi thích gói gọn tất cả các chức năng trong lớp thay vì công khai các phương thức trong trường hợp đó một khách hàng nên làm việc theo cách này:

GroupOfCustomersImporter importer = new GroupOfCustomersImporter(filepath)
importer.createCSVReader();
read();
GroupOfCustomer group = constructGoupOfCustomerInstance();

Tôi thích điều này bởi vì tôi không muốn đặt các dòng mã vô dụng vào mã bên của máy khách làm phiền lớp khách hàng với các chi tiết triển khai.

Vì vậy, đây thực sự là một thói quen xấu? Nếu có, làm thế nào tôi có thể tránh nó? Xin lưu ý rằng ở trên chỉ là một ví dụ đơn giản. Hãy tưởng tượng tình huống tương tự xảy ra trong một cái gì đó phức tạp hơn một chút.

Câu trả lời:


17

Tôi nghĩ rằng bạn đang thực sự đi đúng hướng khi muốn ẩn chi tiết triển khai từ khách hàng. Bạn muốn thiết kế lớp của mình sao cho giao diện mà khách hàng nhìn thấy là API đơn giản và ngắn gọn nhất mà bạn có thể nghĩ ra. Không chỉ khách hàng sẽ không "bị làm phiền" với các chi tiết triển khai, mà bạn cũng có thể tự do cấu trúc lại mã cơ bản mà không phải lo lắng rằng người gọi mã đó phải được sửa đổi. Giảm khớp nối giữa các mô-đun khác nhau có một số lợi ích thực sự và bạn chắc chắn nên phấn đấu cho điều đó.

Vì vậy, nếu bạn làm theo lời khuyên tôi vừa cung cấp, bạn sẽ kết thúc với một thứ bạn đã nhận thấy trong mã của mình, cả đống logic vẫn ẩn sau giao diện công cộng và không dễ truy cập.

Lý tưởng nhất là bạn chỉ có thể kiểm tra lớp của mình với giao diện chung của nó và nếu nó có các phụ thuộc bên ngoài, bạn có thể cần phải giới thiệu các đối tượng fake / mock / stub để tách mã đang được kiểm tra.

Tuy nhiên, nếu bạn làm điều này và bạn vẫn cảm thấy rằng bạn không thể dễ dàng kiểm tra mọi phần của lớp, thì rất có khả năng một lớp của bạn đang làm quá nhiều. Theo tinh thần của nguyên tắc SRP , bạn có thể làm theo những gì Michael Feathers gọi là "Mô hình lớp mầm" và trích xuất một đoạn của lớp ban đầu của bạn thành một lớp mới.

Trong ví dụ của bạn, bạn có một lớp trình nhập cũng chịu trách nhiệm đọc tệp văn bản. Một trong những lựa chọn của bạn là trích xuất một đoạn mã đọc tệp vào một lớp InputFileParser riêng biệt. Bây giờ tất cả các chức năng riêng tư đó trở nên công khai và do đó dễ dàng kiểm tra. Đồng thời, lớp trình phân tích cú pháp không phải là thứ hiển thị cho các máy khách bên ngoài (đánh dấu là "nội bộ", không xuất bản các tệp tiêu đề hoặc đơn giản là không quảng cáo nó như một phần của API của bạn) vì chúng sẽ tiếp tục sử dụng bản gốc nhà nhập khẩu có giao diện sẽ tiếp tục ngắn và ngọt ngào.


1

Thay thế cho việc đưa nhiều lệnh gọi phương thức vào hàm tạo của lớp - mà tôi đồng ý là thói quen thường tránh - thay vào đó, bạn có thể tạo một phương thức xuất xưởng để xử lý tất cả các công cụ khởi tạo bổ sung mà bạn không muốn làm phiền khách hàng bên với. Điều này có nghĩa là bạn sẽ đưa ra một phương thức để trông giống như constructGroupOfCustomers()phương thức của bạn là công khai và sử dụng nó như một phương thức tĩnh của lớp của bạn:

GroupOfCustomersImporter importer = 
    new GroupOfCustomersImporter.CreateInstance();

hoặc như một phương thức của một lớp nhà máy riêng biệt có lẽ:

ImporterFactory factory = new ImporterFactory();
GroupOfCustomersImporter importer = factory.CreateGroupOfCustomersImporter();

Đó chỉ là vài lựa chọn đầu tiên nghĩ ra khỏi đỉnh đầu tôi.

Cũng đáng để xem xét rằng bản năng của bạn đang cố nói với bạn điều gì đó. Khi ruột của bạn cho bạn biết mã bắt đầu có mùi, nó có thể có mùi, và tốt hơn là nên làm gì đó trước khi nó xuất hiện! Chắc chắn trên bề mặt có thể không có gì sai về bản chất với mã của bạn, tuy nhiên việc kiểm tra lại nhanh và trình tái cấu trúc sẽ giúp giải quyết suy nghĩ của bạn về vấn đề này để bạn có thể tự tin chuyển sang các công cụ khác mà không phải lo lắng nếu bạn đang xây dựng ngôi nhà tiềm năng của thẻ. Trong trường hợp cụ thể này, ủy thác một số trách nhiệm của nhà xây dựng cho - hoặc được gọi bởi - một nhà máy đảm bảo bạn có thể thực hiện mức độ kiểm soát cao hơn đối với việc khởi tạo lớp của bạn và con cháu tiềm năng trong tương lai.


1

Thêm câu trả lời của riêng tôi, vì nó đã kết thúc quá lâu cho một bình luận.

Bạn hoàn toàn đúng khi đóng gói và kiểm tra là hoàn toàn trái ngược - nếu bạn che giấu hầu hết mọi thứ, thật khó để kiểm tra.

Tuy nhiên, bạn có thể làm cho ví dụ dễ kiểm tra hơn bằng cách đưa ra một luồng thay vì tên tệp - theo cách đó bạn chỉ cần cung cấp một csv đã biết từ bộ nhớ hoặc một tệp nếu bạn muốn. Nó cũng sẽ làm cho lớp của bạn mạnh mẽ hơn khi bạn cần thêm hỗ trợ http;)

Ngoài ra, xem xét bằng cách sử dụng lazy-init:

public class Csv
{
  private CustomerList list;

  public CustomerList get()
  {
    if(list == null)
        load();
     return list;
  }
}

1

Hàm tạo không nên làm quá nhiều ngoại trừ khởi tạo một số biến hoặc trạng thái của đối tượng. Làm quá nhiều trong constructor có thể tăng ngoại lệ thời gian chạy. Tôi sẽ cố gắng tránh khách hàng làm điều gì đó như thế này:

MyClass a;
try {
   a = new MyClass();
} catch (MyException e) {
   //do something
}

Thay vì làm:

MyClass a = new MyClass(); // Or a factory method
try {
   a.doSomething();
} catch (MyException e) {
   //do something
}
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.