Có nguyên tắc phân tách giao diện áp dụng cho các phương pháp cụ thể?


10

Vì nguyên tắc phân tách giao diện cho thấy không nên buộc khách hàng phụ thuộc vào các phương thức mà nó không sử dụng, do đó, khách hàng không nên thực hiện một phương thức trống cho các phương thức giao diện của nó, nếu không thì nên đưa phương thức giao diện này vào giao diện khác.

Nhưng làm thế nào về phương pháp cụ thể? Tôi có nên tách các phương thức mà không phải mọi khách hàng sẽ sử dụng? Hãy xem xét các lớp sau:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

ở đoạn mã trên, CarShop hoàn toàn không sử dụng phương thức isQualityPass () trong Xe hơi, tôi nên tách isQualityPass () thành một lớp mới:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

để giảm khớp nối của CarShop? Bởi vì tôi nghĩ một lần nếu isQualityPass () cần phụ thuộc thêm, ví dụ:

public boolean isQualityPass(){
    HttpClient client=...
}

CarShop sẽ phụ thuộc vào HttpClient ngay cả khi nó không bao giờ sử dụng HttpClient. Vì vậy, câu hỏi của tôi là: theo nguyên tắc phân tách giao diện, tôi có nên tách riêng các phương thức cụ thể mà không phải mọi khách hàng sẽ sử dụng, để các phương thức đó chỉ phụ thuộc vào máy khách khi khách hàng thực sự sử dụng, để giảm khớp nối không?


2
Có phải một chiếc xe thường biết khi nó vượt qua "chất lượng"? Hoặc có thể đó là một quy tắc kinh doanh có thể được gói gọn bởi chính nó?
Laiv

2
giao diện từ trong ISP ngụ ý về giao diện . Vì vậy, nếu bạn có một phương thức trong Carlớp mà bạn không muốn (tất cả) người dùng biết về thì hãy tạo (nhiều hơn một) giao diệnCarlớp thực hiện một phương thức chỉ khai báo các phương thức hữu ích trong ngữ cảnh giao diện.
Timothy Truckle

@Laiv Tôi chắc chắn chúng ta sẽ thấy những chiếc xe biết nhiều hơn thế. ;)
bánh sandwich mô hình thống nhất

1
Một chiếc xe sẽ biết những gì nhà sản xuất của nó muốn anh ta biết. Volkswagen biết những gì tôi đang nói về :-)
Laiv

1
Bạn đề cập đến một giao diện, nhưng không có giao diện trong ví dụ của bạn. Có phải chúng ta đang nói về việc biến Xe thành một giao diện và phương pháp nào sẽ được đưa vào giao diện nói trên?
Neil

Câu trả lời:


6

Trong ví dụ của bạn, CarShopkhông phụ thuộc vào isQualityPassvà nó không bị buộc phải thực hiện trống cho một phương thức. Thậm chí không có giao diện liên quan. Vì vậy, thuật ngữ "ISP" đơn giản là không phù hợp ở đây. Và miễn là một phương thức như isQualityPasslà một phương thức phù hợp với Carđối tượng, mà không làm quá tải nó với các trách nhiệm hoặc phụ thuộc bổ sung, điều này là tốt. Không cần cấu trúc lại một phương thức công khai của một lớp vào một nơi khác chỉ vì có một khách hàng không sử dụng phương thức đó.

Tuy nhiên, làm cho một lớp miền như Carphụ thuộc trực tiếp vào một cái gì đó giống như HttpClientcó lẽ cũng không phải là một ý tưởng tốt, thiếu tôn trọng mà khách hàng sử dụng hoặc không sử dụng phương thức. Chuyển logic thành một lớp riêng biệt CheckCarQualityPasskhông được gọi là "ISP", đây được gọi là "phân tách mối quan tâm" . Mối quan tâm của một đối tượng xe hơi có thể tái sử dụng có lẽ không nên thực hiện bất kỳ cuộc gọi HTTP bên ngoài nào, ít nhất là không trực tiếp, điều này hạn chế khả năng sử dụng lại và hơn nữa là khả năng kiểm tra quá nhiều.

Nếu isQualityPasskhông thể dễ dàng chuyển sang lớp khác, phương án thay thế sẽ là thực hiện các Httpcuộc gọi thông qua giao diện trừu tượng IHttpClientđược đưa vào Cartrong thời gian xây dựng hoặc bằng cách đưa toàn bộ chiến lược kiểm tra "QualityPass" (với yêu cầu http được đóng gói) vào Carđối tượng . Nhưng đó là IMHO chỉ là giải pháp tốt thứ hai, vì nó làm tăng độ phức tạp tổng thể thay vì giảm nó.


Điều gì về mẫu Chiến lược để giải quyết phương thức isQualityPass?
Laiv

@Laiv: về mặt kỹ thuật, điều này sẽ hoạt động, chắc chắn (xem phần chỉnh sửa của tôi), nhưng nó sẽ dẫn đến một Carđối tượng phức tạp hơn . Nó sẽ không phải là lựa chọn đầu tiên của tôi cho một giải pháp (ít nhất là không phải trong bối cảnh của ví dụ giả định này). Tuy nhiên, nó có thể có ý nghĩa hơn trong mã "thực", tôi không biết.
Doc Brown

6

Vì vậy, câu hỏi của tôi là: theo nguyên tắc phân tách giao diện, tôi có nên tách riêng các phương thức cụ thể mà không phải mọi khách hàng sẽ sử dụng, để các phương thức đó chỉ phụ thuộc vào máy khách khi khách hàng thực sự sử dụng, để giảm khớp nối không?

Nguyên tắc phân tách giao diện không phải là về việc không cho phép truy cập vào những gì bạn không cần. Đó là về việc không khăng khăng truy cập vào những gì bạn không cần.

Các giao diện không thuộc sở hữu của lớp thực hiện chúng. Chúng thuộc sở hữu của các đối tượng sử dụng chúng.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Những gì được sử dụng ở đây là getTax()getCost(). Những gì đang được nhấn mạnh là tất cả mọi thứ có thể truy cập thông qua Car. Vấn đề là khăng khăng Carcó nghĩa là nó khăng khăng truy cập vào isQualityPass()những thứ không cần thiết.

Điều này có thể được sửa chữa. Bạn hỏi nếu nó có thể được sửa chữa cụ thể. Nó có thể.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Không ai trong số mã đó thậm chí biết nếu CarLiabilitylà một giao diện hoặc một lớp cụ thể. Đó là một điều tốt. Nó không muốn biết.

Nếu đó là một giao diện Carcó thể thực hiện nó. Điều này sẽ không vi phạm ISP vì mặc dù isQuality()là trong Car CarShopkhông nhấn mạnh vào nó. Điều này là tốt

Nếu nó cụ thể thì có thể isQuality()là nó không tồn tại hoặc đã được chuyển đi nơi khác. Điều này là tốt

Nó cũng có thể CarLiabilitylà một bọc cụ thể xung quanh Carđang ủy thác công việc cho nó. Vì vậy, miễn là CarLiabilitykhông vạch trần isQuality()sau đó CarShoplà tốt. Tất nhiên, điều này chỉ khiến cái lon rơi xuống đường và CarLiabilityphải tìm ra cách theo ISP với Carcách tương tự CarShopphải làm.

Nói tóm lại, isQuality()không cần phải loại bỏ Carvì ISP. Nhu cầu ngụ ý cho isQuality()nhu cầu cần được loại bỏ CarShopCarShopkhông cần nó, vì vậy không nên yêu cầu.


4

Có nguyên tắc phân tách giao diện áp dụng cho các phương pháp cụ thể?

Nhưng làm thế nào về phương pháp cụ thể? Tôi có nên tách các phương thức mà không phải mọi khách hàng sẽ sử dụng?

Không hẳn vậy. Có nhiều cách khác nhau để ẩn Car.isQualityPasstừ CarShop.

1. Sửa đổi truy cập

Từ quan điểm của Luật Demeter , chúng tôi có thể xem xét CarCardShopkhông kết bạn . Nó hợp pháp hóa chúng tôi để làm tiếp theo.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Hãy nhận biết cả hai thành phần là trong các gói khác nhau. Bây giờ CarShopkhông có tầm nhìn về Car các hành vi được bảo vệ . (Xin lỗi trước nếu ví dụ trên có vẻ đơn giản).

2. Phân chia giao diện

Các ISP làm việc từ tiền đề rằng chúng tôi làm việc với trừu tượng, không phải với lớp bê tông. Tôi sẽ giả định rằng bạn đã quen với việc triển khai ISP và với các giao diện vai trò .

Mặc dù thực Carhiện thực tế , không có gì ngăn cản chúng tôi thực hành ISP.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

Những gì tôi đã làm ở đây. Tôi đã thu hẹp sự tương tác giữa CarCarShopthông qua giao diện vai trò Billable . Hãy nhận biết sự thay đổi trên getPricechữ ký. Tôi đã sửa đổi chủ ý đối số. Tôi muốn làm rõ rằng CarShopchỉ "gắn / ràng buộc" với một trong các giao diện vai trò có sẵn. Tôi có thể đã theo dõi việc triển khai thực tế nhưng tôi không biết chi tiết triển khai thực sự và tôi sợ thực tế getPrice(String carId)có quyền truy cập (khả năng hiển thị) trên lớp cụ thể. Nếu có, tất cả công việc được thực hiện với ISP sẽ trở nên vô dụng vì nó nằm trong tay nhà phát triển để thực hiện truyền và chỉ hoạt động với giao diện Billable . Cho dù chúng ta có phương pháp như thế nào, sự cám dỗ sẽ luôn ở đó.

3. Trách nhiệm duy nhất

Tôi e rằng tôi không ở vị trí để nói nếu sự phụ thuộc giữa CarHttpClientlà đủ, nhưng tôi đồng ý với @DocBrown, nó đưa ra một số cảnh báo đáng để xem xét thiết kế. Cả Luật pháp và ISP của Demeter đều không làm cho thiết kế của bạn "tốt hơn" vào thời điểm này. Họ sẽ chỉ che giấu vấn đề, không sửa nó.

Tôi đã đề xuất DocBrown Mô hình Chiến lược như một giải pháp khả thi. Tôi đồng ý với anh ta rằng mô hình thêm phức tạp, nhưng tôi cũng nghĩ rằng bất kỳ thiết kế lại sẽ. Đó là một sự đánh đổi, chúng ta càng muốn tách rời, chúng ta càng có nhiều bộ phận chuyển động (thường). Dù sao, tôi nghĩ rằng cả hai đồng ý với một thiết kế lại là rất khuyến khích.

Tổng hợp

Không, bạn không cần phải di chuyển các phương thức cụ thể đến các lớp bên ngoài vì không thể truy cập chúng. Có thể có vô số người tiêu dùng. Bạn có muốn chuyển tất cả các phương thức cụ thể sang các lớp bên ngoài mỗi khi một người tiêu dùng mới tham gia không? Tôi hy vọng bạn không.

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.