Có phải Cha mẹ là x = new Child (); Thay vì của Child Child x = new Child (); Một cách thực hành tồi nếu chúng ta có thể sử dụng cái sau?


32

Ví dụ, tôi đã thấy một số mã tạo ra một đoạn như thế này:

Fragment myFragment=new MyFragment();

trong đó khai báo một biến là Fragment thay vì MyFragment, mà MyFragment là một lớp con của Fragment. Tôi không bão hòa dòng mã này vì tôi nghĩ mã này phải là:

MyFragment myFragment=new MyFragment();

cái nào cụ thể hơn, điều đó có đúng không?

Hoặc trong việc khái quát hóa câu hỏi, có phải là sử dụng không tốt:

Parent x=new Child();

thay vì

Child x=new Child();

Nếu chúng ta có thể thay đổi cái trước thành cái sau mà không có lỗi biên dịch?


6
Nếu chúng ta làm sau, không có điểm nào để trừu tượng hóa / khái quát hóa nữa. Tất nhiên cũng có ngoại lệ ...
Walfrat

2
Trong một dự án tôi đang làm việc, các hàm của tôi mong đợi kiểu cha và chạy các phương thức kiểu cha. Tôi có thể chấp nhận bất kỳ đứa trẻ nào và mã đằng sau các phương thức (được kế thừa và ghi đè) đó là khác nhau, nhưng tôi muốn chạy mã đó cho dù tôi đang sử dụng đứa trẻ nào.
Stephen S

4
@Walfrat Thật vậy, có rất ít điểm trừu tượng khi bạn đã tạo kiểu cụ thể, nhưng bạn vẫn có thể trả về kiểu trừu tượng để mã máy khách không biết gì.
Jacob Raihle

4
Đừng sử dụng Cha mẹ / Con cái. Sử dụng Giao diện / Lớp (cho Java).
Thorbjørn Ravn Andersen

4
Google "lập trình đến một giao diện" cho một loạt các giải thích về lý do tại sao sử dụng Parent x = new Child()là một ý tưởng tốt.
Kevin Workman

Câu trả lời:


69

Nó phụ thuộc vào bối cảnh, nhưng tôi cho rằng bạn nên khai báo loại trừu tượng nhất có thể. Bằng cách đó, mã của bạn sẽ càng chung chung càng tốt và không phụ thuộc vào các chi tiết không liên quan.

Một ví dụ sẽ có một LinkedListArrayListcả hai đều đi xuống List. Nếu mã sẽ hoạt động tốt như nhau với bất kỳ loại danh sách nào thì không có lý do gì để tùy tiện giới hạn nó ở một trong các lớp con.


24
Chính xác. Để thêm một ví dụ, hãy suy nghĩ List list = new ArrayList(), Mã sử ​​dụng danh sách không quan tâm loại danh sách đó là gì, chỉ đáp ứng hợp đồng của giao diện Danh sách. Trong tương lai, chúng tôi có thể dễ dàng thay đổi thành một triển khai Danh sách khác và mã cơ bản hoàn toàn không cần thay đổi hoặc cập nhật.
Có lẽ là

19
câu trả lời tốt nhưng nên mở rộng rằng loại trừu tượng nhất có thể có nghĩa là mã trong phạm vi không nên truyền lại cho trẻ để thực hiện một số thao tác cụ thể của trẻ.
Mike

10
@Mike: Tôi hy vọng rằng không cần phải nói, nếu không tất cả các biến, tham số và trường sẽ được khai báo là Object!
JacquesB

3
@JacquesB: vì câu hỏi và câu trả lời này nhận được rất nhiều sự quan tâm, tôi đã nghĩ rằng việc rõ ràng sẽ hữu ích cho những người có tất cả các cấp độ kỹ năng khác nhau.
Mike

1
Nó phụ thuộc vào bối cảnh không phải là một câu trả lời.
Billal Begueradj

11

Câu trả lời của JacquesB là chính xác, mặc dù hơi trừu tượng. Hãy để tôi nhấn mạnh một số khía cạnh rõ ràng hơn:

Trong đoạn mã của bạn, bạn sử dụng newtừ khóa một cách rõ ràng và có lẽ bạn muốn tiếp tục với các bước khởi tạo tiếp theo có khả năng cụ thể cho loại cụ thể: sau đó bạn cần khai báo biến với loại cụ thể đó.

Tình huống sẽ khác khi bạn lấy vật phẩm đó từ nơi khác, vd IFragment fragment = SomeFactory.GiveMeFragments(...);. Ở đây, nhà máy thường trả về loại trừu tượng nhất có thể và mã hướng xuống không nên phụ thuộc vào chi tiết triển khai.


18
"Mặc dù hơi trừu tượng". Badumtish.
rosuav

1
Đây không phải là một chỉnh sửa?
beppe9000

6

Có sự khác biệt hiệu suất tiềm năng.

Nếu lớp dẫn xuất được niêm phong, thì trình biên dịch có thể nội tuyến và tối ưu hóa hoặc loại bỏ những gì sẽ là các cuộc gọi ảo. Vì vậy, có thể có một sự khác biệt hiệu suất đáng kể.

Ví dụ: ToStringlà một cuộc gọi ảo, nhưng Stringđược niêm phong. Bật String, ToStringlà một lệnh không hoạt động, vì vậy nếu bạn khai báo objectđó là một cuộc gọi ảo, nếu bạn khai báo Stringtrình biên dịch thì không có lớp dẫn xuất nào ghi đè phương thức vì lớp này được niêm phong, vì vậy đó là không có op. Cân nhắc tương tự áp dụng cho ArrayListvs LinkedList.

Do đó, nếu bạn biết loại cụ thể của đối tượng, (và không có lý do đóng gói để che giấu nó), bạn nên khai báo là loại đó. Vì bạn vừa tạo đối tượng, bạn biết loại bê tông.


4
Trong hầu hết các trường hợp, sự khác biệt hiệu suất là rất nhỏ. Và trong hầu hết các trường hợp (tôi đã nghe nói rằng đây là khoảng 90% thời gian) chúng ta nên quên đi những khác biệt nhỏ đó. Chỉ khi chúng ta biết (tốt nhất là thông qua đo lường) mà chúng ta già đi trong phần quan trọng về hiệu năng của mã thì chúng ta mới quan tâm đến việc tối ưu hóa như vậy.
Jules

Và trình biên dịch biết rằng "New Child ()" trả về một đứa trẻ, ngay cả khi được gán cho Parent và miễn là nó biết, nó có thể sử dụng kiến ​​thức đó và gọi mã Child ().
gnasher729

+1. Cũng lấy trộm ví dụ của bạn trong câu trả lời của tôi. = P
Nat

1
Lưu ý rằng có những tối ưu hóa hiệu suất làm cho toàn bộ điều này. Ví dụ, HotSpot sẽ nhận thấy rằng một tham số được truyền cho một hàm luôn thuộc một lớp cụ thể và tối ưu hóa tương ứng. Nếu giả định đó trở thành sai, phương pháp sẽ được tối ưu hóa. Nhưng điều này phụ thuộc mạnh mẽ vào môi trường trình biên dịch / thời gian chạy. Lần trước tôi đã kiểm tra CLR thậm chí không thể thực hiện tối ưu hóa khá tầm thường khi nhận thấy p từ Parent p = new Child()luôn luôn là một đứa trẻ ..
Voo

4

Sự khác biệt chính là mức độ truy cập cần thiết. Và mọi câu trả lời hay về sự trừu tượng đều liên quan đến động vật, hoặc tôi được kể như vậy.

Hãy để chúng tôi cho rằng bạn có một vài động vật - Cat BirdDog. Bây giờ, những con vật này có một vài hành vi phổ biến - move(), eat(), và speak(). Tất cả chúng đều ăn khác nhau và nói khác nhau, nhưng nếu tôi cần con vật của mình ăn hoặc nói thì tôi không thực sự quan tâm đến cách chúng làm điều đó.

Nhưng đôi khi tôi làm. Tôi chưa bao giờ chăm chỉ một con mèo bảo vệ hay một con chim bảo vệ, nhưng tôi có một con chó bảo vệ. Vì vậy, khi ai đó đột nhập vào nhà tôi, tôi không thể chỉ dựa vào nói để hù dọa kẻ xâm nhập. Tôi thực sự cần con chó của tôi sủa - điều này khác với tiếng nói của nó, nó chỉ là một sự giả mạo.

Vì vậy, trong mã yêu cầu một kẻ xâm nhập tôi thực sự cần phải làm Dog fido = getDog(); fido.barkMenancingly();

Nhưng hầu hết thời gian, tôi có thể vui vẻ có bất kỳ động vật nào thực hiện các thủ thuật mà chúng giả vờ, kêu meo meo hoặc tweet để đối xử. Animal pet = getAnimal(); pet.speak();

Vì vậy, để cụ thể hơn, ArrayList có một số phương thức Listkhông. Đáng chú ý là trimToSize(). Bây giờ, trong 99% trường hợp, chúng tôi không quan tâm đến điều này. Điều Listnày gần như không bao giờ đủ lớn để chúng ta quan tâm. Nhưng có những lúc nó là. Vì vậy, đôi khi, chúng ta phải đặc biệt yêu cầu ArrayListthực hiện trimToSize()và làm cho mảng sao lưu của nó nhỏ hơn.

Lưu ý rằng điều này không phải được thực hiện khi xây dựng - nó có thể được thực hiện thông qua phương thức trả lại hoặc thậm chí là diễn viên nếu chúng tôi hoàn toàn chắc chắn.


Bối rối tại sao điều này có thể nhận được một downvote. Nó có vẻ kinh điển, cá nhân. Ngoại trừ ý tưởng về một con chó bảo vệ có thể lập trình ...
corsiKa

Tại sao tôi phải đọc các trang, để có câu trả lời tốt nhất? Đánh giá hút.
Basilevs

Cảm ơn những lời tốt đẹp. Có những ưu và nhược điểm đối với các hệ thống xếp hạng, và một trong số đó là những câu trả lời sau này đôi khi có thể bị bỏ lại trong bụi. Nó xảy ra!
corsiKa

2

Nói chung, bạn nên sử dụng

var x = new Child(); // C#

hoặc là

auto x = new Child(); // C++

đối với các biến cục bộ trừ khi có một lý do chính đáng để biến có một kiểu khác với những gì nó được khởi tạo.

(Ở đây tôi bỏ qua thực tế là mã C ++ rất có thể sẽ sử dụng một con trỏ thông minh. Có những trường hợp không và không có nhiều hơn một dòng tôi thực sự không thể biết.)

Ý tưởng chung ở đây là với các ngôn ngữ hỗ trợ phát hiện biến tự động của các biến, sử dụng nó giúp mã dễ đọc hơn và thực hiện đúng (nghĩa là hệ thống loại của trình biên dịch có thể tối ưu hóa càng nhiều càng tốt và thay đổi khởi tạo thành mới loại hoạt động tốt nếu hầu hết trừu tượng đã được sử dụng).


1
Trong C ++, các biến chủ yếu được tạo mà không có từ khóa mới: stackoverflow.com/questions/6500313/iêu
Marian Spanik

1
Câu hỏi không phải là về C # / C ++ mà là về một vấn đề hướng đối tượng chung trong một ngôn ngữ có ít nhất một dạng gõ tĩnh (nghĩa là sẽ vô nghĩa khi hỏi trong một cái gì đó như Ruby). Ngoài ra, ngay cả trong hai ngôn ngữ đó, tôi không thực sự thấy một lý do chính đáng cho lý do tại sao chúng ta nên làm điều đó như bạn nói.
AnoE

1

Tốt vì nó dễ hiểu và dễ sửa đổi mã này:

var x = new Child(); 
x.DoSomething();

Tốt vì nó truyền đạt ý định:

Parent x = new Child(); 
x.DoSomething(); 

Ok, bởi vì nó phổ biến và dễ hiểu:

Child x = new Child(); 
x.DoSomething(); 

Tùy chọn duy nhất thực sự xấu là sử dụng Parentnếu chỉ Childcó một phương thức DoSomething(). Thật tệ vì nó truyền đạt ý định sai:

Parent x = new Child(); 
(x as Child).DoSomething(); // DON'T DO THIS! IF YOU WANT x AS CHILD, STORE x AS CHILD

Bây giờ chúng ta hãy giải thích thêm một chút về trường hợp câu hỏi hỏi cụ thể về:

Parent x = new Child(); 
x.DoSomething(); 

Chúng ta hãy định dạng khác, bằng cách thực hiện một nửa cuộc gọi hàm:

WhichType x = new Child(); 
FunctionCall(x);

void FunctionCall(WhichType x)
    x.DoSomething(); 

Điều này có thể được rút ngắn thành:

FunctionCall(new Child());

void FunctionCall(WhichType x)
    x.DoSomething();

Ở đây, tôi cho rằng nó được chấp nhận rộng rãi WhichTypenên là loại cơ bản / trừu tượng nhất cho phép chức năng hoạt động (trừ khi có những lo ngại về hiệu suất). Trong trường hợp này, loại thích hợp sẽ là một trong hai Parenthoặc một cái gì đó Parentxuất phát từ.

Dòng lý luận này giải thích tại sao sử dụng loại Parentnày là một lựa chọn tốt, nhưng nó không đi vào thời tiết, các lựa chọn khác là xấu (chúng không phải là).


1

tl; dr - Sử dụngChildhơnParentlà thích hợp hơn trong phạm vi địa phương. Nó không chỉ giúp dễ đọc mà còn cần đảm bảo rằng độ phân giải phương thức quá tải hoạt động đúng và giúp cho phép biên dịch hiệu quả.


Trong phạm vi địa phương,

Parent obj = new Child();  // Works
Child  obj = new Child();  // Better
var    obj = new Child();  // Best

Về mặt khái niệm, đó là về việc duy trì thông tin loại nhất có thể. Nếu chúng tôi hạ cấp xuống Parent, về cơ bản, chúng tôi chỉ loại bỏ thông tin loại có thể hữu ích.

Giữ lại thông tin loại hoàn chỉnh có bốn ưu điểm chính:

  1. Cung cấp thêm thông tin cho trình biên dịch.
  2. Cung cấp thêm thông tin cho người đọc.
  3. Mã sạch hơn, chuẩn hơn.
  4. Làm cho logic chương trình trở nên đột biến hơn.

Ưu điểm 1: Thêm thông tin cho trình biên dịch

Loại rõ ràng được sử dụng trong độ phân giải phương thức quá tải và tối ưu hóa.

Ví dụ: Độ phân giải phương thức quá tải

main()
{
    Parent parent = new Child();
    foo(parent);

    Child  child  = new Child();
    foo(child);
}
foo(Parent arg) { /* ... */ }  // More general
foo(Child  arg) { /* ... */ }  // Case-specific optimizations

Trong ví dụ trên, cả hai foo()cuộc gọi đều hoạt động , nhưng trong một trường hợp, chúng ta có được độ phân giải phương thức quá tải tốt hơn.

Ví dụ: Tối ưu hóa trình biên dịch

main()
{
    Parent parent = new Child();
    var x = parent.Foo();

    Child  child  = new Child();
    var y = child .Foo();
}
class Parent
{
    virtual         int Foo() { return 1; }
}
class Child : Parent
{
    sealed override int Foo() { return 2; }
}

Trong ví dụ trên, cả hai .Foo()cuộc gọi cuối cùng đều gọi cùng một overridephương thức trả về 2. Chỉ trong trường hợp đầu tiên, có một phương thức tra cứu ảo để tìm phương thức đúng; tra cứu phương thức ảo này không cần thiết trong trường hợp thứ hai kể từ phương thức đó sealed.

Tín dụng cho @Ben người đã cung cấp một ví dụ tương tự trong câu trả lời của mình .

Ưu điểm 2: Thêm thông tin cho người đọc

Biết loại chính xác, nghĩa là Child, cung cấp thêm thông tin cho bất kỳ ai đang đọc mã, giúp dễ dàng hơn để xem chương trình đang làm gì.

Chắc chắn, có lẽ nó không quan trọng với mã thực tế vì cả hai parent.Foo();child.Foo();có ý nghĩa, nhưng đối với một người nào đó nhìn thấy mã cho lần đầu tiên, biết thêm thông tin là chỉ đơn giản hữu ích.

Ngoài ra, tùy thuộc vào môi trường phát triển của bạn, IDE có thể cung cấp nhiều công cụ và siêu dữ liệu hữu ích Childhơn Parent.

Ưu điểm 3: Mã sạch hơn, chuẩn hơn

Hầu hết các ví dụ mã C # mà tôi đã thấy gần đây sử dụng var, về cơ bản là tốc ký Child.

Parent obj = new Child();  // Sub-optimal
Child  obj = new Child();  // Optimal, but anti-pattern syntax
var    obj = new Child();  // Optimal, clean, patterned syntax "everyone" uses now

Nhìn thấy một vartuyên bố không khai báo chỉ nhìn ra; nếu có một lý do tình huống cho nó, tuyệt vời, nhưng nếu không thì nó có vẻ chống mẫu.

// Clean:
var foo1 = new Person();
var foo2 = new Job();
var foo3 = new Residence();

// Staggered:
Person foo1 = new Person();
Job foo2 = new Job();
Residence foo3 = new Residence();   

Ưu điểm 4: Logic chương trình đột biến hơn cho tạo mẫu

Ba lợi thế đầu tiên là những lợi thế lớn; cái này nhiều tình huống hơn.

Tuy nhiên, đối với những người sử dụng mã như những người khác sử dụng Excel, chúng tôi liên tục thay đổi mã của mình. Có lẽ chúng ta không cần phải gọi một phương thức độc đáo để Childtrong này phiên bản mã, nhưng chúng ta có thể sử dụng lại hoặc làm lại các mã sau.

Ưu điểm của một hệ thống loại mạnh là nó cung cấp cho chúng tôi một số dữ liệu meta nhất định về logic chương trình của chúng tôi, làm cho các khả năng trở nên rõ ràng hơn. Điều này cực kỳ hữu ích trong việc tạo mẫu, vì vậy tốt nhất là giữ nó ở nơi có thể.

Tóm lược

Sử dụng Parentlàm rối quá trình giải quyết phương thức quá tải, ức chế một số tối ưu hóa trình biên dịch, loại bỏ thông tin khỏi đầu đọc và làm cho mã xấu hơn.

Sử dụng varthực sự là con đường để đi. Nó nhanh chóng, gọn gàng, khuôn mẫu và giúp trình biên dịch và IDE thực hiện công việc của họ đúng cách.


Quan trọng : Câu trả lời này là vềParentso vớiChildtrong phạm vi cục bộ của phương thức. Vấn đềParentso vớiChildrất khác nhau đối với kiểu trả về, đối số và trường lớp.


2
Tôi sẽ thêm rằng Parent list = new Child()nó không có nhiều ý nghĩa. Mã trực tiếp tạo ra thể hiện cụ thể của một đối tượng (trái ngược với sự gián tiếp thông qua các nhà máy, phương thức nhà máy, v.v.) có thông tin hoàn hảo về loại được tạo, nó có thể cần phải tương tác với giao diện cụ thể để định cấu hình đối tượng mới được tạo, v.v. Phạm vi địa phương không phải là nơi bạn đạt được sự linh hoạt. Tính linh hoạt đạt được khi bạn hiển thị đối tượng mới được tạo cho khách hàng của lớp bằng giao diện trừu tượng hơn (nhà máy và phương thức nhà máy là một ví dụ hoàn hảo)
crizzis

"tl; dr Sử dụng Child over Parent thích hợp hơn trong phạm vi địa phương. Nó không chỉ giúp dễ đọc" - không nhất thiết ... nếu bạn không sử dụng các chi tiết cụ thể của trẻ, thì nó sẽ chỉ thêm nhiều chi tiết hơn mức cần thiết. "nhưng cũng cần phải đảm bảo rằng độ phân giải phương thức quá tải hoạt động đúng và giúp cho phép biên dịch hiệu quả." - điều đó phụ thuộc rất nhiều vào ngôn ngữ lập trình. Có rất nhiều không có vấn đề gì phát sinh từ việc này.
AnoE

1
Bạn có một nguồn đó Parent p = new Child(); p.doSomething();Child c = new Child; c.doSomething();có hành vi khác nhau? Có thể đó là một sự ngớ ngẩn trong một số ngôn ngữ, nhưng nó được cho là đối tượng kiểm soát hành vi, không phải là tài liệu tham khảo. Đó là vẻ đẹp của sự kế thừa! Miễn là bạn có thể tin tưởng vào việc thực hiện của trẻ em để thực hiện hợp đồng của việc thực hiện của cha mẹ, bạn nên đi.
corsiKa

@corsiKa Vâng, nó có thể theo một số cách. Hai ví dụ nhanh sẽ là nơi chữ ký phương thức được ghi đè, ví dụ với newtừ khóa trong C # và khi có nhiều định nghĩa, ví dụ như có nhiều kế thừa trong C ++ .
Nat

@corsiKa Chỉ là một ví dụ thứ ba nhanh chóng (vì tôi nghĩ đó là một sự tương phản gọn gàng giữa C ++ và C #), C # có một vấn đề như của C ++, nơi bạn cũng có thể có hành vi này từ các phương thức giao diện rõ ràng . Thật buồn cười vì các ngôn ngữ như C # sử dụng các giao diện thay vì một phần thừa kế để tránh các vấn đề này, nhưng bằng cách áp dụng các giao diện, chúng vẫn có một phần của vấn đề - nhưng, nó không hoàn toàn xấu vì trong kịch bản đó, nó chỉ áp dụng đến khi nào Parentlà một interface.
Nat

0

Cho trước parentType foo = new childType1();, mã sẽ được giới hạn trong việc sử dụng các phương thức kiểu cha mẹ trên foo, nhưng sẽ có thể lưu trữ vào foomột tham chiếu đến bất kỳ đối tượng nào có kiểu xuất phát từ - không parentchỉ là các thể hiện của childType1. Nếu childType1ví dụ mới là điều duy nhất foosẽ giữ một tham chiếu, thì có lẽ tốt hơn là khai báo fookiểu của nó là childType1. Mặt khác, nếu foocó thể cần phải giữ các tham chiếu đến các loại khác, việc khai báo nó parentTypesẽ giúp điều đó trở nên khả thi.


0

Tôi đề nghị sử dụng

Child x = new Child();

thay vì

Parent x = new Child();

Cái sau mất thông tin trong khi cái trước thì không.

Bạn có thể làm mọi thứ với cái trước mà bạn có thể làm với cái sau nhưng không phải ngược lại.


Để xây dựng con trỏ hơn nữa, hãy để bạn có nhiều cấp độ kế thừa.

Base-> DerivedL1->DerivedL2

Và bạn muốn khởi tạo kết quả của new DerivedL2()một biến. Sử dụng một loại cơ sở cho bạn tùy chọn sử dụng Basehoặc DerivedL1.

Bạn có thể dùng

Base x = new DerivedL2();

hoặc là

DerivedL1 x = new DerivedL2();

Tôi không thấy bất kỳ cách logic nào để thích cái này hơn cái kia.

Nếu bạn dùng

DerivedL2 x = new DerivedL2();

không có gì để tranh cãi


3
Có thể làm ít hơn nếu bạn không cần phải làm nhiều hơn là một điều tốt, phải không?
Peter

1
@Peter, khả năng làm ít hơn không bị hạn chế. Nó luôn luôn có thể. Nếu khả năng làm nhiều hơn bị hạn chế, nó có thể là một điểm đau.
R Sahu

Nhưng mất thông tin đôi khi là một điều tốt. Liên quan đến hệ thống phân cấp của bạn, hầu hết mọi người có thể sẽ bỏ phiếu cho Base(giả sử nó hoạt động). Bạn thích cụ thể nhất, AFAIK đa số thích ít cụ thể nhất. Tôi muốn nói rằng đối với các biến cục bộ, nó hầu như không quan trọng.
maaartinus

@maaartinus, tôi ngạc nhiên khi đa số người dùng thích mất thông tin. Tôi không thấy lợi ích của việc mất thông tin rõ ràng như những người khác.
R Sahu

Khi bạn khai báo Base x = new DerivedL2();thì bạn bị hạn chế nhiều nhất và điều này cho phép bạn chuyển một đứa trẻ (lớn) khác với nỗ lực tối thiểu. Hơn nữa, bạn thấy ngay lập tức rằng nó có thể. +++ Chúng ta có đồng ý rằng void f(Base)tốt hơn void f(DerivedL2)không?
maaartinus

0

Tôi sẽ đề nghị luôn luôn trả về hoặc lưu trữ các biến là loại cụ thể nhất, nhưng chấp nhận các tham số là loại rộng nhất.

Ví dụ

<K, V> LinkedHashMap<K, V> keyValuePairsToMap(List<K> keys, List<V> values) {
   //...
}

Các thông số là List<T>, một loại rất chung chung. ArrayList<T>, LinkedList<T>Và những người khác có thể tất cả được chấp nhận bởi phương pháp này.

Điều quan trọng, loại trả lại là LinkedHashMap<K, V>, không Map<K, V>. Nếu ai đó muốn gán kết quả của phương pháp này cho a Map<K, V>, họ có thể làm điều đó. Nó thông báo rõ ràng rằng kết quả này không chỉ là bản đồ, mà là bản đồ với thứ tự xác định.

Giả sử phương thức này được trả về Map<K, V>. Nếu người gọi muốn a LinkedHashMap<K, V>, họ sẽ phải thực hiện kiểm tra loại, bỏ và xử lý lỗi, điều này thực sự cồng kềnh.


Logic tương tự buộc bạn phải chấp nhận một loại rộng như một tham số cũng nên áp dụng cho loại trả về. Nếu mục tiêu của hàm là ánh xạ các khóa thành các giá trị, thì nó sẽ trả về Mapkhông LinkedHashMap- bạn chỉ muốn trả về LinkedHashMapmột hàm như keyValuePairsToFifoMaphoặc một cái gì đó. Rộng hơn là tốt hơn cho đến khi bạn có một lý do cụ thể không.
corsiKa

@corsiKa Hmm Tôi đồng ý rằng đó là một cái tên tốt hơn, nhưng đây chỉ là một ví dụ. Tôi không đồng ý rằng (nói chung) mở rộng loại hình trả lại của bạn là tốt hơn. Loại bỏ thông tin nhân tạo của bạn, mà không có bất kỳ biện minh. Nếu bạn hình dung người dùng của bạn sẽ cần phải bỏ lại loại rộng mà bạn đã cung cấp cho họ, thì bạn đã thực hiện cho họ một dịch vụ.
Alexander - Tái lập Monica

"Không có bất kỳ biện minh nào" - nhưng có sự biện minh! Nếu tôi có thể thoát khỏi việc trả lại một loại rộng hơn, nó giúp việc tái cấu trúc trở nên dễ dàng hơn. Nếu ai đó CẦN loại cụ thể vì một số hành vi trên đó, tôi sẽ trả lại loại thích hợp. Nhưng tôi không muốn bị khóa trong một loại cụ thể nếu tôi không phải với tôi. Tôi muốn được tự do để thay đổi các chi tiết cụ của phương pháp của tôi mà không làm tổn hại đến những người tiêu thụ nó, và nếu tôi thay đổi nó từ, nói LinkedHashMapđến TreeMapvì lý do gì, bây giờ tôi có rất nhiều công cụ để thay đổi.
corsiKa

Trên thực tế, tôi đồng ý với điều đó cho đến câu cuối cùng. Bạn đã thay đổi từ LinkedHashMapđến TreeMapkhông phải là một sự thay đổi không đáng kể, chẳng hạn như thay đổi từ ArrayListtới LinkedList. Đó là một sự thay đổi với tầm quan trọng ngữ nghĩa. Trong trường hợp đó, bạn muốn trình biên dịch đưa ra lỗi, buộc người tiêu dùng của giá trị trả về phải thông báo thay đổi và thực hiện hành động thích hợp.
Alexander - Tái lập Monica

Nhưng nó có thể là một thay đổi nhỏ nếu tất cả những gì bạn quan tâm là hành vi bản đồ (mà nếu bạn có thể, bạn nên làm.) Nếu hợp đồng là "bạn sẽ có được một bản đồ giá trị chính" và thứ tự không thành vấn đề (điều này đúng với hầu hết các bản đồ) thì không thành vấn đề nếu đó là bản đồ cây hoặc băm. Ví dụ: Thread#getAllStackTraces()- trả về một Map<Thread,StackTraceElement[]>thay vì HashMapcụ thể để xuống đường họ có thể thay đổi nó thành bất kỳ loại Mapnào họ muốn.
corsiKa
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.