Là việc thực hiện một giao diện được xác định trong một gói con là một mô hình chống?


9

Hãy nói rằng tôi có những điều sau đây:

package me.my.pkg;
public interface Something {
    /* ... couple of methods go here ... */
}

và:

package me.my;
import me.my.pkg.Something;
public class SomeClass implements Something {
    /* ... implementation of Something goes here ... */
    /* ... some more method implementations go here too ... */
}

Đó là, lớp thực hiện một giao diện sống gần với gốc phân cấp gói hơn là giao diện mà nó thực hiện nhưng cả hai đều thuộc cùng một cấu trúc phân cấp gói.

Lý do cho điều này trong trường hợp cụ thể mà tôi nghĩ đến là có một gói đã tồn tại trước đó, nhóm chức năng mà Somethinggiao diện thuộc về logic và logic (như trong cả "cái bạn mong đợi" và "cái mà bạn mong đợi" nơi mà nó cần phải đi theo kiến ​​trúc hiện tại ") lớp thực hiện tồn tại trước đó và sống một cấp" lên "từ vị trí logic của giao diện. Lớp triển khai không hợp lý thuộc về bất cứ nơi nào dưới tôi.my.pkg.

Trong trường hợp cụ thể của tôi, lớp trong câu hỏi thực hiện một số giao diện, nhưng cảm giác như nó không tạo ra bất kỳ sự khác biệt nào (hoặc ít nhất là không có ý nghĩa) ở đây.

Tôi không thể quyết định nếu đây là một mô hình chấp nhận được hay không. Có phải hay không, và tại sao?

Câu trả lời:


6

Vâng, có thể chấp nhận được, miễn là cả lớp và giao diện đều chắc chắn nằm trong các gói chính xác.

Tổ chức các lớp thành một tập các gói phân cấp giống như tổ chức các lớp thành một hệ thống phân cấp kế thừa. Nó hoạt động khá tốt hầu hết thời gian, nhưng có rất nhiều trường hợp hợp pháp mà không phù hợp.

Lưu ý rằng Java thậm chí không thực sự có cấu trúc phân cấp gói - bản chất phân cấp không mở rộng hơn cấu trúc thư mục. Tôi không thể nói cho các nhà thiết kế ngôn ngữ Java, nhưng tôi nghi ngờ họ đã đưa ra quyết định này một cách tình cờ!

Đôi khi tôi thấy việc nghĩ về 'phân cấp gói' là một trợ lý để giúp các lập trình viên tìm thấy gói mà họ đang tìm kiếm.


3

Mặc dù chính thức hợp pháp, điều này có thể chỉ ra mùi mã . Để tìm hiểu xem điều này có đúng như vậy trong trường hợp của bạn không, hãy tự hỏi mình ba câu hỏi:

  1. Bạn sẽ cần nó?
  2. Tại sao bạn gói nó theo cách đó?
  3. Làm thế nào để bạn biết nếu API gói của bạn thuận tiện để sử dụng?

Bạn sẽ cần nó?

Nếu bạn không thấy lý do để đầu tư thêm nỗ lực vào khả năng duy trì mã , hãy xem xét để nguyên như vậy. Cách tiếp cận này dựa trên nguyên tắc YAGNI

lập trình viên không nên thêm chức năng cho đến khi thấy cần thiết ... "Luôn thực hiện mọi thứ khi bạn thực sự cần chúng, không bao giờ khi bạn thấy trước rằng bạn cần chúng."

Các cân nhắc được cung cấp dưới đây cho rằng nỗ lực đầu tư vào phân tích sâu hơn là hợp lý, giả sử bạn kỳ vọng mã sẽ được duy trì lâu dài hoặc quan tâm đến việc thực hành và mài giũa kỹ năng để viết mã có thể duy trì. Nếu điều này không áp dụng, vui lòng bỏ qua phần còn lại - bởi vì bạn sẽ không cần nó .

Tại sao bạn gói nó theo cách đó?

Điều đáng chú ý đầu tiên là cả hai quy ướchướng dẫn mã hóa chính thức đều không đưa ra bất kỳ hướng dẫn nào về điều đó, gửi tín hiệu mạnh mẽ rằng các quyết định loại này dự kiến ​​sẽ được quyết định bởi một lập trình viên.

Nhưng nếu bạn nhìn vào thiết kế được triển khai bởi các nhà phát triển của JDK , bạn sẽ nhận thấy rằng API gói "rò rỉ" vào các gói cấp cao hơn không được thực hiện ở đó. Ví dụ, xem xét gói sử dụng đồng thời và các gói phụ của nó - khóanguyên tử .

  • Đáng lưu ý rằng việc sử dụng API gói phụ bên trong được coi là OK, ví dụ: triển khai đồng thời nhập khẩu khóa và sử dụng ReentrantLock. Nó chỉ không lộ API khóa là công khai.

Được rồi, các nhà phát triển API cốt lõi dường như tránh điều đó và để tìm hiểu lý do tại sao nó lại tự hỏi, tại sao bạn lại đóng gói theo cách đó? Lý do tự nhiên cho điều đó là mong muốn giao tiếp với người dùng gói một số loại phân cấp, loại lớp , để gói con tương ứng với các khái niệm sử dụng mức độ thấp hơn / chuyên biệt hơn / hẹp hơn.

Điều này thường được thực hiện để làm cho API dễ sử dụng và dễ hiểu hơn, để giúp người dùng API hoạt động trong lớp cụ thể, tránh làm phiền họ với những lo ngại thuộc về các lớp khác. Nếu đây là trường hợp, việc phơi bày API cấp thấp hơn từ các gói phụ chắc chắn sẽ đi ngược lại ý định của bạn.

  • Lưu ý bên cạnh, nếu bạn không thể biết tại sao bạn đóng gói theo cách này, hãy xem xét quay lại thiết kế của bạn và phân tích kỹ lưỡng cho đến khi bạn hiểu điều này. Hãy nghĩ về nó, nếu thậm chí khó giải thích với bạn, ngay cả bây giờ, sẽ khó hơn thế nào với những người sẽ duy trì và sử dụng gói của bạn trong tương lai?

Làm thế nào để bạn biết nếu API gói của bạn thuận tiện để sử dụng?

Vì thế, tôi thực sự khuyên bạn nên viết các bài kiểm tra API thực hiện được cung cấp bởi gói của bạn. Yêu cầu ai đó đánh giá cũng không gây hại, nhưng rất có thể người đánh giá API có kinh nghiệm sẽ yêu cầu bạn cung cấp các thử nghiệm như vậy. Điều đó là, thật khó để đánh bại việc xem một ví dụ sử dụng thực tế khi xem xét API.

  • Người ta có thể nhìn vào lớp với một phương thức duy nhất có tham số duy nhất và mơ ước thật đơn giản , nhưng nếu thử nghiệm để yêu cầu nó tạo ra 10 đối tượng khác, gọi 20 phương thức với tổng 50 tham số, điều này phá vỡ một ảo ảnh nguy hiểm và cho thấy sự phức tạp thực sự của thiết kế.

Một lợi thế khác của việc kiểm tra là những điều này có thể đơn giản hóa rất nhiều việc đánh giá các ý tưởng khác nhau mà bạn xem xét để cải thiện thiết kế của bạn.

Đôi khi, điều đó xảy ra với tôi rằng những thay đổi triển khai tương đối nhỏ đã kích hoạt các đơn giản hóa lớn trong các thử nghiệm, giảm lượng nhập khẩu, đối tượng, yêu cầu phương thức và tham số. Ngay cả trước khi chạy thử nghiệm, điều này phục vụ một dấu hiệu tốt về cách đáng để theo đuổi hơn nữa.

Đối diện cũng xảy ra với tôi - đó là, khi những thay đổi lớn, đầy tham vọng trong việc triển khai của tôi nhằm đơn giản hóa API đã được chứng minh là sai bởi tác động nhỏ, không đáng kể trong các thử nghiệm, giúp tôi bỏ ý tưởng không hiệu quả và để lại mã như vậy (có thể chỉ là một nhận xét được thêm vào để giúp hiểu nền tảng của thiết kế và giúp người khác tìm hiểu về cách sai).


Đối với trường hợp cụ thể của bạn, điều đầu tiên bạn nghĩ đến là xem xét thay đổi kế thừa thành thành phần - nghĩa là, thay vì thực hiện SomeClasstrực tiếp Something, bao gồm một đối tượng thực hiện giao diện đó bên trong lớp,

package me.my;
import me.my.pkg.Something;
public class SomeClass {
    private Something something = new Something() {
        /* ... implementation of Something goes here ... */
    }
    /* ... some more method implementations go here too ... */
}

Cách tiếp cận này có thể trả lại trong tương lai, ngăn ngừa rủi ro cho một số lớp con SomeClassvô tình ghi đè lên một phương thức Something, làm cho nó hoạt động theo cách mà bạn không có kế hoạch.

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.