Có phải nó luôn có ý nghĩa đối với chương trình trên mạng với giao diện của Java trong Java không?


9

Tôi đã thấy cuộc thảo luận về câu hỏi này liên quan đến việc một lớp thực hiện từ một giao diện sẽ được khởi tạo như thế nào. Trong trường hợp của tôi, tôi đang viết một chương trình rất nhỏ bằng Java sử dụng một cá thể TreeMapvà theo ý kiến ​​của mọi người ở đó, nó nên được khởi tạo như sau:

Map<X> map = new TreeMap<X>();

Trong chương trình của tôi, tôi đang gọi hàm map.pollFirstEntry(), không được khai báo trong Mapgiao diện (và một vài người khác cũng có mặt trong Mapgiao diện). Tôi đã quản lý để làm điều này bằng cách truyền tới TreeMap<X>mọi nơi tôi gọi phương thức này như:

someEntry = ((TreeMap<X>) map).pollFirstEntry();

Tôi hiểu các ưu điểm của hướng dẫn khởi tạo như được mô tả ở trên đối với các chương trình lớn, tuy nhiên đối với một chương trình rất nhỏ mà đối tượng này sẽ không được chuyển sang các phương thức khác, tôi nghĩ nó không cần thiết. Tuy nhiên, tôi đang viết mã mẫu này như một phần của đơn xin việc và tôi không muốn mã của mình trông xấu và lộn xộn. Điều gì sẽ là giải pháp thanh lịch nhất?

EDIT: Tôi muốn chỉ ra rằng tôi quan tâm nhiều hơn đến các hoạt động mã hóa tốt thay vì áp dụng chức năng cụ thể TreeMap. Như một số câu trả lời đã chỉ ra (và tôi đã đánh dấu là đã trả lời câu trả lời đầu tiên để làm như vậy), nên sử dụng mức độ trừu tượng cao hơn mà không làm mất chức năng.


1
Sử dụng TreeMap khi bạn cần các tính năng. Đó có lẽ là một lựa chọn thiết kế cho một lý do cụ thể vì vậy nó cũng nên là một phần của việc thực hiện.

@JordanReiter tôi chỉ nên thêm một câu hỏi mới có cùng nội dung, hoặc có một cơ chế đăng chéo nội bộ không?
jimijazz


2
"Tôi hiểu những lợi thế của hướng dẫn khởi tạo như được mô tả ở trên đối với các chương trình lớn" Phải bỏ đi khắp nơi không phải là lợi thế cho dù quy mô của chương trình là gì
Ben Aaronson

Câu trả lời:


23

"Lập trình cho giao diện" không có nghĩa là "sử dụng phiên bản trừu tượng nhất có thể". Trong trường hợp đó mọi người sẽ chỉ sử dụng Object.

Điều đó có nghĩa là bạn nên xác định chương trình của mình theo mức độ trừu tượng thấp nhất có thể mà không làm mất chức năng . Nếu bạn yêu cầu TreeMapthì bạn sẽ cần xác định hợp đồng bằng cách sử dụng a TreeMap.


2
TreeMap không phải là một giao diện, nó là một lớp triển khai. Nó thực hiện các giao diện Map, SortedMap và NavigableMap. Phương thức được mô tả là một phần của giao diện NavigableMap . Việc sử dụng TreeMap sẽ ngăn việc triển khai chuyển sang một concienSkipListMap (ví dụ), đó là toàn bộ điểm mã hóa cho một giao diện thay vì thực hiện.

3
@MichaelT: Tôi đã không nhìn vào sự trừu tượng chính xác mà anh ấy cần trong kịch bản cụ thể này nên tôi đã sử dụng TreeMaplàm ví dụ. "Chương trình cho một giao diện" không nên được coi là một giao diện hoặc một lớp trừu tượng - một triển khai cũng có thể được coi là một giao diện.
Jeroen Vannevel

1
Mặc dù giao diện / phương thức công khai của lớp triển khai về mặt kỹ thuật là 'giao diện', nhưng nó phá vỡ khái niệm đằng sau LSP và ngăn thay thế một lớp con khác, đó là lý do tại sao bạn muốn lập trình public interfacethay vì 'phương thức công khai của việc triển khai' .

@JeroenVannevel Tôi đồng ý rằng lập trình cho một giao diện có thể được thực hiện khi giao diện thực sự được đại diện bởi một lớp. Tuy nhiên, tôi không thấy lợi ích gì khi sử dụng TreeMapsẽ có hơn SortedMaphoặcNavigableMap
toniedzwiedz

16

Nếu bạn vẫn muốn sử dụng một giao diện, bạn có thể sử dụng

NavigableMap <X, Y> map = new TreeMap<X, Y>();

không nhất thiết phải luôn luôn sử dụng một giao diện nhưng thường có một điểm mà bạn muốn có một cái nhìn tổng quát hơn cho phép bạn thay thế việc thực hiện (có lẽ để thử nghiệm) và điều này thật dễ dàng nếu tất cả các tham chiếu đến đối tượng được trừu tượng hóa như loại giao diện.


3

Điểm đằng sau việc mã hóa cho một giao diện thay vì triển khai là để tránh rò rỉ các chi tiết triển khai, điều này sẽ hạn chế chương trình của bạn.

Xem xét tình huống trong đó phiên bản gốc của mã của bạn đã sử dụng HashMapvà phơi bày điều đó.

private HashMap foo = new HashMap();
public HashMap getFoo() { return foo; }  // This is bad, don't do this.

Điều này có nghĩa là bất kỳ thay đổi nào getFoo()là thay đổi API vi phạm và sẽ khiến mọi người sử dụng nó không hài lòng. Nếu tất cả những gì bạn đảm bảo là fooBản đồ, thay vào đó bạn nên trả lại.

private Map foo = new HashMap();
public Map getFoo() { return foo; }

Điều này cho phép bạn linh hoạt thay đổi cách mọi thứ hoạt động bên trong mã của bạn. Bạn nhận ra rằng foothực sự cần phải là một Bản đồ trả lại mọi thứ theo một thứ tự cụ thể.

private NavigableMap foo = new TreeMap();
public Map getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

Và điều đó không phá vỡ bất cứ điều gì cho phần còn lại của mã.

Sau này bạn có thể củng cố hợp đồng mà lớp đưa ra mà không phá vỡ bất cứ điều gì.

private NavigableMap foo = new TreeMap();
public NavigableMap getFoo() { return foo; }
private void doBar() { ... foo.lastEntry(); ... }

Điều này đi sâu vào nguyên tắc thay thế Liskov

Thay thế là một nguyên tắc trong lập trình hướng đối tượng. Nó nói rằng, trong một chương trình máy tính, nếu S là một kiểu con của T, thì các đối tượng thuộc loại T có thể được thay thế bằng các đối tượng loại S (nghĩa là các đối tượng thuộc loại S có thể thay thế các đối tượng thuộc loại T) mà không thay đổi bất kỳ đối tượng nào mong muốn các thuộc tính của chương trình đó (tính chính xác, nhiệm vụ được thực hiện, v.v.).

Vì NavigableMap là một kiểu con của Map, sự thay thế này có thể được thực hiện mà không làm thay đổi chương trình.

Việc phơi bày các kiểu triển khai làm cho khó thay đổi cách chương trình hoạt động bên trong khi cần thay đổi. Đây là một quá trình đau đớn và nhiều lần tạo ra các cách giải quyết xấu xí chỉ gây ra nhiều đau đớn hơn cho người viết mã sau này thấy tên của bạn trong svn đổ lỗi tôi lo lắng).

Bạn vẫn sẽ muốn tránh rò rỉ các loại thực hiện. Ví dụ: bạn có thể muốn triển khai ConcảnSkipListMap thay vì một số đặc điểm hiệu suất hoặc bạn chỉ thích java.util.concurrent.ConcurrentSkipListMapthay vì java.util.TreeMaptrong các báo cáo nhập khẩu hoặc bất cứ điều gì.


1

Đồng ý với các câu trả lời khác rằng bạn nên sử dụng lớp (hoặc giao diện) chung nhất mà bạn thực sự cần thứ gì đó, trong trường hợp này là TreeMap (hoặc giống như ai đó đã đề xuất NavigableMap). Nhưng tôi muốn nói thêm rằng điều này chắc chắn tốt hơn so với việc đúc nó ở khắp mọi nơi, nó sẽ là một mùi lớn hơn nhiều trong mọi trường hợp. Xem /programming/4167304/why-should-casting-be-avoided vì một số lý do.


1

Đó là về việc truyền đạt ý định của bạn về cách sử dụng đối tượng. Ví dụ: nếu phương thức của bạn mong đợi một Mapđối tượng có thứ tự lặp có thể dự đoán được :

private Map<String, String> processOrderedMap(LinkedHashMap<String, String> input) {
    // ...
}

Sau đó, nếu bạn thực sự cần phải nói với người gọi phương thức trên rằng nó cũng trả về một Mapđối tượng với thứ tự lặp có thể dự đoán được , bởi vì có một kỳ vọng như vậy vì một số lý do:

private LinkedHashMap<String,String> processOrderedMap(LinkedHashMap<String,String> input) {
    // ...
}

Tất nhiên, người gọi vẫn có thể coi đối tượng trả về là Mapnhư vậy, nhưng điều đó vượt quá phạm vi phương thức của bạn:

private Map<String, String> output = processOrderedMap(input);

Lùi lại một bước

Lời khuyên chung về mã hóa cho một giao diện là (thường) có thể áp dụng được, bởi vì thông thường đó là giao diện cung cấp sự đảm bảo cho những gì đối tượng có thể thực hiện, còn gọi là hợp đồng . Nhiều người mới bắt đầu HashMap<K, V> map = new HashMap<>()và họ được khuyên nên tuyên bố như vậy Map, bởi vì một người HashMapkhông cung cấp bất cứ điều gì nhiều hơn những gì Mapđáng lẽ phải làm. Từ điều này, sau đó họ sẽ có thể hiểu (hy vọng) tại sao các phương thức của họ nên sử dụng Mapthay vì a HashMapvà điều này cho phép họ nhận ra tính năng kế thừa trong OOP.

Để trích dẫn chỉ một dòng từ mục nhập Wikipedia về nguyên tắc yêu thích của mọi người liên quan đến chủ đề này:

Nó là một mối quan hệ ngữ nghĩa chứ không chỉ đơn thuần là cú pháp bởi vì nó dự định đảm bảo khả năng tương tác ngữ nghĩa của các loại trong một hệ thống phân cấp ...

Nói cách khác, sử dụng một Maptuyên bố không phải vì nó có ý nghĩa 'về mặt cú pháp', mà là các dẫn chứng về đối tượng chỉ nên quan tâm rằng đó là một loại Map.

Mã sạch hơn

Tôi thấy rằng điều này cũng cho phép tôi viết mã sạch hơn, đặc biệt là khi nói đến kiểm thử đơn vị. Tạo một HashMapmục nhập kiểm tra chỉ đọc duy nhất mất nhiều hơn một dòng (không bao gồm việc sử dụng khởi tạo cú đúp), khi tôi có thể dễ dàng thay thế bằng Collections.singletonMap().

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.