Sự khác biệt trong phong cách: IDictionary vs Dictionary


76

Tôi có một người bạn mới bắt đầu phát triển .NET sau khi phát triển bằng Java trong nhiều năm và, sau khi xem một số mã của anh ấy, tôi nhận thấy rằng anh ấy làm những việc sau khá thường xuyên:

IDictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

Anh ấy đang khai báo từ điển là Giao diện chứ không phải Lớp. Thông thường, tôi sẽ làm như sau:

Dictionary<string, MyClass> dictionary = new Dictionary<string, MyClass>();

Tôi chỉ sử dụng giao diện IDictionary khi cần thiết (chẳng hạn như để chuyển từ điển sang một phương thức chấp nhận giao diện IDictionary).

Câu hỏi của tôi là: cách làm việc của anh ấy có xứng đáng không? Đây có phải là một thực tế phổ biến trong Java?


11
Đối với các biến cục bộ thực sự không có ý nghĩa.
Joren


5
Một thực tiễn tốt NHƯNG hãy cẩn thận với chi phí hiệu suất: nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue
MaYaN

1
Hai cách triển khai chính của IDictionary( DictionaryConcurrentDictionary) có các đặc điểm hiệu suất rất khác nhau (ví dụ: Countrất nhanh trên cái trước và rất chậm trên cái sau). Do đó, đề xuất của tôi là chỉ định lớp cụ thể (thay vì IDictionary) trong C #, nếu có thể, để người tiêu dùng biết cách sử dụng từ điển một cách hiệu quả.
mjwills

1
Ngoài ra, IDictionarycó một số ' kỳ quặc ' về hiệu suất - nimaara.com/2016/03/06/beware-of-the-idictionary-tkey-tvalue .
mjwills

Câu trả lời:


67

Nếu IDictionary là kiểu "chung chung hơn" so với Từ điển thì việc sử dụng kiểu chung hơn trong việc khai báo biến sẽ rất hợp lý. Bằng cách đó, bạn không phải quan tâm nhiều đến lớp thực thi được gán cho biến và bạn có thể thay đổi kiểu dễ dàng trong tương lai mà không cần phải thay đổi nhiều đoạn mã sau. Ví dụ, trong Java, nó thường được coi là tốt hơn để làm

List<Integer> intList=new LinkedList<Integer>();

hơn nó phải làm

LinkedList<Integer> intList=new LinkedList<Integer>();

Bằng cách đó, tôi chắc chắn rằng tất cả các mã sau đều coi danh sách là Danh sách chứ không phải Danh sách liên kết, giúp bạn dễ dàng chuyển đổi LinkedList cho Vector hoặc bất kỳ lớp nào khác triển khai Danh sách trong tương lai. Tôi muốn nói rằng điều này là phổ biến đối với Java và lập trình tốt nói chung.


25
+1, nhưng bạn có thể muốn sử dụng từ 'chung' chứ không phải 'chung', mà đã có một bucketload của nghĩa gắn liền :)
AakashM

28

Thực hành này không chỉ giới hạn ở Java.

Nó cũng thường được sử dụng trong .NET khi bạn muốn tách cặp đối tượng khỏi lớp bạn đang sử dụng. Nếu bạn sử dụng Giao diện thay vì Lớp, bạn có thể thay đổi kiểu hỗ trợ bất cứ khi nào cần mà không làm hỏng phần còn lại của mã.

Bạn cũng sẽ thấy phương pháp này được sử dụng nhiều khi xử lý vùng chứa IoC và cài đặt bằng cách sử dụng mẫu Factory.


1
Nhưng bạn mất tất cả các chức năng mà Dictionary có thể cung cấp mà không có trong IDictionary.
kiềm chế

10
Nhưng nếu bạn có thể sử dụng IDictionary thay vào đó ... thì bạn đang không sử dụng chức năng bắt đầu.
Justin Niessner

1
Nhưng nếu bạn chỉ cần các chức năng trong IDictionary ... Nó là dễ dàng hơn để thay đổi IDictionary vào Từ điển hơn so với xung quanh cách khác :)
Svish

1
Việc truyền Từ điển sang IDictionary dễ dàng hơn so với cách khác. :)
kiềm chế

10
Đánh mất chức năng mà Dictionary cung cấp nhưng IDictionary không phải là toàn bộ điểm của câu thành ngữ này. Việc khai báo biến cục bộ của bạn là IDictionary buộc bạn phải hạn chế việc sử dụng biến đó đối với những gì có sẵn thông qua giao diện. Sau đó, nếu sau này bạn phát hiện ra bạn phải sử dụng một số lớp khác triển khai IDictionay thay vì Từ điển, bạn có thể làm như vậy bằng cách thay đổi một dòng gọi hàm tạo các lớp cụ thể (vì bạn biết mã của mình chỉ sử dụng nó như một IDictionary chứ không phải Từ điển ).
Stephen C. Steel

15

Bạn của bạn đang tuân theo nguyên tắc rất hữu ích:

"Tóm tắt bản thân từ chi tiết triển khai"


6
Nhưng chúng tôi đang giải quyết các chi tiết triển khai. Chúng tôi không tạo giao diện cho một lớp - chúng tôi chỉ khai báo một biến cục bộ.
kiềm chế

8
Không hẳn, IMHO. Nếu thành viên giao diện là đủ cho bạn, tôi thực sự khuyên bạn nên gắn bó với giao diện. Làm cho mã của bạn càng chung chung càng tốt.
Vitaliy Liptchinsky

8

Bạn nên luôn cố gắng lập trình theo giao diện chứ không phải là lớp cụ thể.

Trong Java hoặc bất kỳ ngôn ngữ lập trình hướng đối tượng nào khác.

Trong thế giới .NET thường sử dụng một Iđể chỉ ra rằng đó là một giao diện mà bạn đang sử dụng. Tôi nghĩ điều này phổ biến hơn vì trong C # họ không có implementsextendsđể tham chiếu đến sự kế thừa của lớp và giao diện.

Tôi nghĩ whey sẽ gõ

 class MyClass:ISome,Other,IMore 
 { 
 }

Và bạn có thể biết ISomean IMorelà các giao diện trong khi Otherlà một lớp

Trong Java không cần những thứ như vậy

 class MyClass extends Other implements Some, More {
 }

Khái niệm này vẫn được áp dụng, bạn nên thử viết mã cho giao diện.


3
Tôi không nghĩ câu hỏi của anh ấy là về cách thức hoạt động của các giao diện hoặc tại sao chúng bắt đầu bằng I. Hơn nữa, các lớp cơ sở phải được khai báo trước các giao diện. Vì vậy, public class MyClass : BaseClass, IFirstInterface, ISecondInterfacesẽ là chính xác. Không có gì đặc biệt về tôi khi bắt đầu giao diện. Trình biên dịch không xử lý nó theo cách khác. Đó là một điểm đánh dấu cho các lập trình viên khác. Bạn có thể đặt tên cho các giao diện của mình mà không có chúng. . . nhưng các lập trình viên khác có thể sẽ ghét bạn.
tandrewnichols 26/12/12

7

Đối với các biến cục bộ và các trường riêng tư, đã là chi tiết triển khai, tốt hơn nên sử dụng các kiểu cụ thể hơn là giao diện cho các khai báo vì các lớp cụ thể cung cấp hiệu suất tăng (gửi trực tiếp nhanh hơn gửi giao diện / ảo). JIT cũng sẽ có thể dễ dàng hơn trong các phương thức nội tuyến nếu bạn không truyền đến các kiểu giao diện trong chi tiết triển khai cục bộ một cách không cần thiết. Nếu một thể hiện của một kiểu cụ thể được trả về từ một phương thức trả về một giao diện, thì việc ép kiểu là tự động.


4
Đó là một tối ưu hóa vi mô chỉ nên được thực hiện nếu bạn có một điểm nghẽn hiệu suất cụ thể cần giải quyết, nó không nên thúc đẩy thiết kế.
Yishai

8
Nó không phải là một tối ưu hóa vi mô. Bạn đã ở trong quá trình triển khai mà bạn đã chọn một cách triển khai cụ thể. Tại sao lại giả vờ như bạn không có hiệu suất?
Sam Harwell

5

Theo như tôi đã thấy các nhà phát triển Java có xu hướng sử dụng trừu tượng (và các mẫu thiết kế) thường xuyên hơn các nhà phát triển .NET. Đây dường như là một ví dụ khác về nó: tại sao lại khai báo lớp cụ thể khi về cơ bản anh ta sẽ chỉ làm việc với các thành viên giao diện?


Tôi đã ở bên bạn cho đến câu thứ hai. Làm cách nào khác mà bạn phải có được một tham chiếu đến một trình triển khai giao diện? Bạn phải khởi tạo một thứ gì đó thực hiện nó. new IDictionary<string, double>()sẽ không hoạt động, và không có ý nghĩa gì.
Tom W,

Tôi không thực sự hiểu ... quan điểm của tôi là những người đã từng sử dụng thành viên từ điển sẽ chỉ muốn sử dụng các khả năng IDictionary. Rõ ràng là bạn phải khởi tạo một cái gì đó nhưng nếu bạn sử dụng mẫu này, các mã sau đây sẽ có nghĩa giống nhau đối với những người đã từng sử dụng lớp: IDictionary <T1, T2> dictionary = new Dictionary <T1, T2> (); và IDictionary <T1, T2> dictionary = new ConcurrentDictionary <T1, T2> (); Nếu bạn không sử dụng các thành viên cụ thể của Dictionary hoặc ConcreteDictionary, bạn không nên khai báo chúng là kiểu của biến lớp, thêm một lớp trừu tượng.
Gergely Orosz

4

Thông thường, bạn sẽ thấy kiểu giao diện (IDictionary) được sử dụng khi thành viên tiếp xúc với mã bên ngoài, cho dù đó là bên ngoài assembly hay chỉ bên ngoài lớp. Thông thường, hầu hết các nhà phát triển sử dụng kiểu cụ thể trong nội bộ định nghĩa lớp trong khi họ hiển thị một thuộc tính đóng gói bằng cách sử dụng kiểu giao diện. Bằng cách này, chúng có thể tận dụng khả năng của loại cụ thể, nhưng nếu chúng thay đổi loại cụ thể, giao diện của lớp khai báo không cần thay đổi.

Tiện ích lớp công cộng
{
    private Dictionary <string, string> map = new Dictionary <string, string> ();
    public IDictionary <string, string> Map
    {
        lấy {bản đồ trở lại; }
    }
}

sau này có thể trở thành:

class SpecialMap <TKey, TValue>: IDictionary <TKey, TValue> {...}

Tiện ích lớp công cộng
{
    private SpecialMap <string, string> map = new SpecialMap <string, string> ();
    public IDictionary <string, string> Map
    {
        lấy {bản đồ trở lại; }
    }
}

mà không cần thay đổi giao diện của Widget và phải thay đổi mã khác đã sử dụng nó.


3

Trong tình huống được mô tả, hầu hết mọi nhà phát triển Java sẽ sử dụng giao diện để khai báo biến. Cách các bộ sưu tập Java được sử dụng có lẽ là một trong những ví dụ tốt nhất:

Map map = new HashMap();
List list = new ArrayList();

Đoán nó chỉ hoàn thành khớp nối lỏng lẻo trong nhiều tình huống.


3

Bộ sưu tập Java bao gồm vô số triển khai. Do đó, tôi dễ dàng sử dụng

List<String> myList = new ArrayList<String>();

Sau đó, trong tương lai khi tôi nhận ra mình cần "myList" để an toàn cho chuỗi, chỉ cần thay đổi dòng đơn này thành

List<String> myList = new Vector<String>();

Và không thay đổi dòng mã nào khác. Điều này cũng bao gồm getters / setters. Ví dụ, nếu bạn nhìn vào số lượng triển khai Bản đồ, bạn có thể hình dung tại sao đây có thể là một phương pháp hay. Trong các ngôn ngữ khác, nơi chỉ có một vài triển khai cho một cái gì đó (xin lỗi, không phải là một gã .NET lớn) nhưng trong Objective-C thực sự chỉ có NSDictionary và NSMutableDictionary. Vì vậy, nó không có nhiều ý nghĩa.

Biên tập:

Không đạt được một trong những điểm chính của tôi (chỉ ám chỉ nó với getter / setters).

Những điều trên cho phép bạn có:

public void setMyList(List<String> myList) {
    this.myList = myList;
}

Và khách hàng sử dụng cuộc gọi này không cần phải lo lắng về việc triển khai cơ bản. Sử dụng bất kỳ đối tượng nào phù hợp với giao diện Danh sách mà chúng có thể có.


thay đổi dòng đó thành: Vector <Chuỗi> myList = new Vector <Chuỗi> (); cũng chỉ thay đổi một dòng.
tster

1
@tster - nhưng mã nào khác gọi các phương thức dành riêng cho ArrayList sẽ phải thay đổi vì Vector không có cùng một phương thức?
Gordon Tucker,

public void setMyList (List <String> list) {myList = list; } Hợp lệ khi khai báo nó dưới dạng Danh sách, không hợp lệ khi sử dụng triển khai trực tiếp. Tức là khách hàng có thể sử dụng bất kỳ danh sách nào họ có thể có mà không cần lo lắng về việc chuyển đổi sang triển khai của bạn.
MarkPowell

3

Đến từ thế giới Java, tôi đồng ý rằng câu thần chú "chương trình thành giao diện" đã được truyền vào bạn. Bằng cách lập trình cho một giao diện, không phải một triển khai, bạn làm cho các phương pháp của mình có thể mở rộng hơn cho các nhu cầu trong tương lai.


1
trừ khi nhu cầu trong tương lai là thay đổi chữ ký hợp đồng như tài sản hoặc phương thức mới.
Matthew Whited

3

IDictionarylà một giao diện và Dictionarylà một lớp.

Dictionarydụng cụ IDictionary.

Điều đó có nghĩa là điều này có thể tham chiếu đến Dictionarycá thể với / theo IDictionarycá thể và gọi hầu hết các Dictionaryphương thức và thuộc tính thông qua IDictionarycá thể.

Điều này rất nên sử dụng các giao diện càng nhiều càng tốt, bởi vì các giao diện tóm tắt các mô-đun và tập hợp của ứng dụng, cho phép đa hình, điều này rất phổ biến và hữu ích trong nhiều tình huống và trường hợp và cho phép thay thế một mô-đun này bằng một mô-đun khác mà không cần chạm vào các mô-đun khác .

Giả sử rằng hiện tại, lập trình viên đã viết:

IDictionary<string> dictionary = new Dictionary<string>();

Và bây giờ dictionarygọi các phương thức và thuộc tính của Dictionary<string>.

Trong tương lai, các cơ sở dữ liệu đã được tăng kích thước và chúng tôi phát hiện ra rằng Dictionary<string>nó quá chậm, vì vậy chúng tôi muốn thay thế Dictionary<string>bằng RedBlackTree<string>, nhanh hơn.

Vì vậy, tất cả những gì cần làm là thay thế hướng dẫn trên thành:

IDictionary<string> dictionary = new RedBlackTree<string>();

Tất nhiên nếu RedBlackTreetriển khai IDictionarythì tất cả mã của ứng dụng biên dịch thành công và bạn có phiên bản ứng dụng mới hơn, nơi ứng dụng hiện hoạt động nhanh hơn và hiệu quả hơn so với phiên bản trước.

Nếu không có giao diện, việc thay thế này sẽ khó thực hiện hơn và sẽ yêu cầu các lập trình viên và nhà phát triển thay đổi nhiều mã hơn có thể gây ra lỗi.


1

Tôi nhận thấy rằng đối với các biến cục bộ, nó thường không quan trọng cho dù bạn sử dụng giao diện hay lớp cụ thể.

Không giống như các thành viên lớp hoặc chữ ký phương thức, có rất ít nỗ lực tái cấu trúc nếu bạn quyết định thay đổi kiểu, cũng như biến không hiển thị bên ngoài trang web sử dụng của nó. Trên thực tế, khi bạn sử dụng varđể khai báo local, bạn không nhận được kiểu giao diện mà là kiểu lớp (trừ khi bạn truyền rõ ràng đến giao diện).

Tuy nhiên, khi khai báo các phương thức, thành viên lớp hoặc giao diện, tôi nghĩ rằng bạn sẽ đỡ phải đau đầu khi sử dụng kiểu giao diện lên trước, thay vì ghép API công khai với một kiểu lớp cụ thể.


2
Đây cũng là ý kiến ​​của tôi - đối với các định nghĩa phương thức, điều quan trọng hơn là thử và sử dụng kiểu chung vì phương thức có thể được sử dụng lại. Đối với các biến cục bộ, tôi không thấy giá trị.
kiềm chế

1
Cá nhân tôi nghĩ điều đáng mừng là bạn sẽ dễ dàng phát hiện ra các phụ thuộc có thể có vào các lớp triển khai. Ví dụ như nếu bạn chuyển biến cục bộ của mình cho một phương thức tiện ích hoặc một cái gì đó.
NickDK

2
Đối với các biến cục bộ, các đối số mà tôi đã thấy là 1) nó giữ cho bạn có kỷ luật trong việc sử dụng các giao diện và 2) nó cho phép mã của bạn ít nhạy cảm hơn với các kiểu trả về của các lệnh gọi hàm gán cho local - nghĩa là hàm thay đổi kiểu cụ thể mà không ảnh hưởng đến mã của bạn (tất nhiên, miễn là kiểu trả về vẫn tuân theo giao diện).
LBushkin

1
@LBushkin, tôi cũng thấy rằng khi bạn đang phát triển một phương pháp và bạn thực hiện tái cấu trúc như phương pháp trích xuất và tương tự, kiểu cụ thể có thể có xu hướng phổ biến mặc dù lẽ ra phải sử dụng giao diện.
Yishai

1

Sử dụng giao diện có nghĩa là "từ điển" trong đoạn mã sau có thể là bất kỳ triển khai nào của IDictionary.

Dictionary1 dictionary = new Dictionary1();
dictionary.operation1(); // if operation1 is implemented only in Dictionary1() this will fail for every other implementation

Nó tốt nhất được nhìn thấy khi bạn ẩn cấu trúc của đối tượng:

IDictionary dictionary = DictionaryFactory.getDictionary(...);

Tôi đồng ý rằng khi sử dụng một nhà máy để trả về một từ điển không cụ thể, đây là một ý kiến ​​hay.
kiềm chế

1
Ý tôi là việc sử dụng giao diện buộc bạn phải suy nghĩ theo các thuật ngữ "chung chung", thực thi việc tách các lớp (trong ví dụ của tôi, phương pháp sử dụng Dictionary1 phải quen thuộc với cấu trúc bên trong của lớp, không phải với giao diện)
Manrico Corazzi

1

Tôi đã gặp trường hợp tương tự với một nhà phát triển Java. Anh ta khởi tạo các bộ sưu tập VÀ đối tượng vào giao diện của chúng theo cách tương tự. Ví dụ,

IAccount account = new Account();

Các thuộc tính luôn được lấy / đặt làm giao diện. Điều này gây ra sự cố với tuần tự hóa, điều này được giải thích rất rõ ở đây


1
Đây chỉ là một vấn đề với tuần tự hóa "ma thuật" mà .NET sử dụng trên các giao diện dịch vụ web. Việc sử dụng các giao diện thay vì các lớp cụ thể là hoàn toàn phù hợp.
Daniel Pryden

Chúng tôi đã có những vấn đề cụ thể với ViewState serialization trong ASP.NET 1.1
Jim Schubert
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.