2012-03-09 31 views
6

Gần đây tôi đã xem xét rất nhiều mã (vì lợi ích của chính mình, vì tôi vẫn đang học lập trình), và tôi đã nhận thấy một số dự án Java (từ những gì dường như là những lập trình viên được tôn trọng) trong đó họ sử dụng một số loại ngay lập tức xuống đúc.Lợi ích của việc truyền xuống ngay lập tức là gì?

Tôi thực sự có nhiều ví dụ, nhưng đây là một mà tôi kéo thẳng từ mã:

public Set<Coordinates> neighboringCoordinates() { 
    HashSet<Coordinates> neighbors = new HashSet<Coordinates>(); 
    neighbors.add(getNorthWest()); 
    neighbors.add(getNorth()); 
    neighbors.add(getNorthEast()); 
    neighbors.add(getWest()); 
    neighbors.add(getEast()); 
    neighbors.add(getSouthEast()); 
    neighbors.add(getSouth()); 
    neighbors.add(getSouthWest()); 
    return neighbors; 
} 

Và từ cùng một dự án, đây là khác (có lẽ ngắn gọn hơn) ví dụ:

private Set<Coordinates> liveCellCoordinates = new HashSet<Coordinates>(); 

Trong ví dụ đầu tiên, bạn có thể thấy rằng phương thức có kiểu trả về là Set<Coordinates> - tuy nhiên, phương thức cụ thể đó sẽ luôn chỉ trả lại HashSet - và không có loại nào khác là Set.

Trong ví dụ thứ hai, liveCellCoordinates ban đầu được định nghĩa là Set<Coordinates>, nhưng ngay lập tức được chuyển thành HashSet.

Và nó không chỉ là dự án cụ thể duy nhất này - tôi đã tìm thấy điều này là trường hợp trong nhiều dự án.

Tôi tò mò về logic đằng sau điều này là gì? Có một số quy ước mã sẽ xem xét thực hành tốt này? Liệu nó làm cho chương trình nhanh hơn hoặc hiệu quả hơn bằng cách nào đó? Nó sẽ có lợi ích gì?

+2

Đây thực sự là ví dụ về việc tạo hình (từ một loại cụ thể đến loại rộng hơn). Xem, ví dụ: [Wikipedia entry on downcasting] (http://en.wikipedia.org/wiki/Downcasting). –

+0

@TedHopp Lấy ví dụ thứ hai mà tôi đã cung cấp, trong ví dụ đó tôi bắt đầu như là một 'Set', và sau đó chuyển đổi thành một' HashSet' (tôi tin rằng 'HashSet' là cụ thể hơn' Set'). Điều đó sẽ không được đúc? Vì tôi sẽ chuyển từ kiểu 'Set' thành một kiểu' HashSet' cụ thể? – Bob

+1

Trong ví dụ thứ hai, bạn đang tạo một 'HashSet' và chuyển đổi nó thành một' Set' khi gán nó cho 'liveCellCoordinates'. Không tồn tại đối tượng 'Set' nào cho đến khi biểu thức' new' được đánh giá và không có 'Set' đang được chuyển thành một' HashSet'. –

Trả lời

12

Khi bạn thiết kế chữ ký phương thức, tốt hơn hết là chỉ nên ghim xuống những thứ cần được ghim xuống. Trong ví dụ đầu tiên, bằng cách chỉ định rằng phương thức trả về một Set (thay vì một đặc biệt HashSet), người triển khai được tự do thay đổi việc thực hiện nếu nó chỉ ra rằng HashSet không phải là cấu trúc dữ liệu đúng. Nếu phương thức đã được khai báo trả về HashSet thì tất cả mã phụ thuộc vào đối tượng cụ thể là HashSet thay vì loại Set chung chung hơn cũng sẽ cần được sửa đổi.

Ví dụ thực tế sẽ là nếu được quyết định rằng neighboringCoordinates() cần để trả về đối tượng an toàn chủ đề Set.Theo văn bản, điều này sẽ rất đơn giản để làm — thay thế dòng cuối cùng của phương pháp này với:

return Collections.synchronizedSet(neighbors); 

Khi nó quay ra, đối tượng Set trả về bởi synchronizedSet() không phải là nhiệm vụ tương thích với HashSet. Điều tốt phương pháp đã được tuyên bố để trả lại một Set!

Xem xét tương tự áp dụng cho trường hợp thứ hai. Mã trong lớp sử dụng liveCellCoordinates không cần phải biết bất cứ điều gì nhiều hơn là nó là Set. (Trong thực tế, trong ví dụ đầu tiên, tôi đã có dự kiến ​​để xem:.

Set<Coordinates> neighbors = new HashSet<Coordinates>(); 

ở phía trên cùng của phương pháp này)

+2

+1: Về việc cố ý về lựa chọn của bạn để yêu cầu tối thiểu của bạn rõ ràng. Sử dụng các loại có nghĩa là những thứ không thực sự cần thiết là khó hiểu và duy trì. –

4

Vì bây giờ nếu chúng thay đổi loại trong tương lai, bất kỳ mã nào tùy thuộc vào láng giềngCoordinates không cần phải cập nhật.

Hãy bạn có:

HashedSet<Coordinates> c = neighboringCoordinates() 

Bây giờ, chúng ta hãy nói rằng họ thay đổi mã của họ để sử dụng một thực hiện khác nhau của bộ này. Đoán xem, bạn phải thay đổi mã của mình.

Nhưng, nếu bạn có:

Set<Coordinates> c = neighboringCoordinates() 

Chừng nào sưu tập của họ vẫn thực hiện thiết lập, họ có thể thay đổi bất cứ điều gì họ muốn trong nội bộ mà không ảnh hưởng mã của bạn.

Về cơ bản, nó chỉ là ít cụ thể nhất có thể (trong lý do) vì mục đích ẩn chi tiết nội bộ. Mã của bạn chỉ quan tâm rằng mã có thể truy cập bộ sưu tập dưới dạng tập hợp. Nó không quan tâm loại cụ thể của bộ nó là, nếu điều đó có ý nghĩa. Vì vậy, tại sao làm cho mã của bạn được kết hợp với một HashedSet?

1

Đó là khá đơn giản.

Trong ví dụ của bạn, phương thức trả về Set. Từ quan điểm của một nhà thiết kế API, điều này có một lợi thế đáng kể so với việc trả về HashSet.

Nếu tại một thời điểm nào đó, lập trình viên quyết định sử dụng SuperPerformantSetForDirections thì anh ấy có thể làm điều đó mà không thay đổi API công khai, nếu lớp mới mở rộng Set.

2

Trong ví dụ đầu tiên, phương thức sẽ luôn trả về một HashSet là một chi tiết triển khai mà người dùng của lớp không cần phải biết. Điều này giải phóng các nhà phát triển để sử dụng một thực hiện khác nhau nếu nó là mong muốn.

2

Nguyên tắc thiết kế trong trò chơi ở đây là "luôn thích chỉ định loại trừu tượng".

Set là trừu tượng; không có lớp bê tông như vậy Set - đó là một giao diện, theo định nghĩa trừu tượng. Hợp đồng của phương thức là trả lại số Set - nhà phát triển đã chọn số loại của Set để trả lại.

Bạn nên làm điều này với các lĩnh vực là tốt, ví dụ:

private List<String> names = new ArrayList<String>; 

không

private ArrayList<String> names = new ArrayList<String>; 

Sau đó, bạn có thể muốn thay đổi để sử dụng một LinkedList - xác định kiểu trừu tượng cho phép bạn làm điều này không có thay đổi mã (ngoại trừ các khóa học khởi tạo).

1

Bí quyết là "mã đến giao diện".

Lý do cho điều này là trong 99,9% các trường hợp bạn chỉ muốn hành vi từ HashSet/TreeSet/WhateverSet phù hợp với giao diện Set được thực hiện bởi tất cả chúng. Nó giữ mã của bạn đơn giản hơn và lý do duy nhất bạn thực sự cần phải nói là HashSet là chỉ định hành vi Bộ bạn cần có.

Như bạn có thể biết HashSet tương đối nhanh nhưng trả về các mục theo thứ tự dường như ngẫu nhiên. TreeSet chậm hơn một chút, nhưng trả về các mục theo thứ tự bảng chữ cái. Mã của bạn không quan tâm, miễn là mã hoạt động như một Tập hợp.

Điều này cung cấp mã đơn giản hơn, dễ làm việc hơn.

Lưu ý rằng các lựa chọn điển hình cho Tập là một HashSet, Bản đồ là HashMap và Danh sách là ArrayList. Nếu bạn sử dụng triển khai không điển hình (cho bạn), cần có lý do chính đáng cho việc này (như, cần phân loại theo thứ tự chữ cái) và lý do đó phải được đặt trong một nhận xét bên cạnh câu lệnh new. Làm cho cuộc sống dễ dàng hơn cho những người bảo trì trong tương lai.

2

Câu hỏi là cách bạn muốn sử dụng biến. ví dụ. có phải trong ngữ cảnh của bạn quan trọng rằng đó là HashSet? Nếu không, bạn nên nói những gì bạn cần, và đây chỉ là một Set.

Mọi thứ sẽ khác nếu bạn sử dụng, ví dụ: TreeSet tại đây. Sau đó, bạn sẽ mất thông tin rằng Set được sắp xếp và nếu thuật toán của bạn dựa vào thuộc tính này, việc thay đổi triển khai thành HashSet sẽ là một thảm họa. Trong trường hợp này, giải pháp tốt nhất là viết SortedSet<Coordinates> set = new TreeSet<Coordinates>();. Hoặc tưởng tượng bạn sẽ viết List<String> list = new LinkedList<String>();: Nếu bạn muốn sử dụng list giống như danh sách, nhưng bạn sẽ không thể sử dụng LinkedList như deque nữa, vì các phương pháp như offerFirst hoặc peekLast không có trên giao diện List.

Vì vậy, quy tắc chung là: Hãy nói chung nhất có thể, nhưng cụ thể khi cần. Hãy tự hỏi mình những gì bạn thực sự cần. Giao diện nhất định có cung cấp tất cả chức năng và lời hứa bạn cần không? Nếu có thì hãy sử dụng nó. Khác là cụ thể hơn, sử dụng giao diện khác hoặc chính lớp đó làm loại.

2

Đây là một lý do khác. Đó là bởi vì các loại tổng quát hơn (trừu tượng) có ít hành vi hơn, tốt bởi vì có ít không gian để gây rối. Ví dụ:

Ví dụ: giả sử bạn đã triển khai phương thức như sau: List<User> users = getUsers(); khi thực tế bạn có thể đã sử dụng loại trừu tượng hơn như sau: Collection<User> users = getUsers();. Bây giờ Bob có thể giả định sai rằng phương thức của bạn trả về người dùng theo thứ tự chữ cái và tạo ra một lỗi. Nếu bạn đã sử dụng Collection, sẽ không có sự nhầm lẫn như vậy.

Các vấn đề liên quan