2010-03-26 22 views
11

Tôi tìm thấy chúng một cách rất tự nhiên để mở rộng các lớp hiện có, đặc biệt là khi bạn chỉ cần "hàn điểm" một số chức năng vào một lớp hiện có.Động lực đằng sau "Sử dụng phương pháp mở rộng một cách tiết kiệm là gì?"

Microsoft nói, "Nói chung, chúng tôi khuyên bạn nên triển khai các phương pháp mở rộng một cách tiết kiệm và chỉ khi bạn phải làm như vậy". Và các phương pháp mở rộng vẫn tạo thành nền tảng của LINQ; trên thực tế, Linq là lý do các phương pháp mở rộng được tạo ra.

Có tiêu chí thiết kế cụ thể nào khi sử dụng các phương pháp mở rộng được suy ra theo thừa kế hoặc thành phần không? Theo những tiêu chí nào họ không khuyến khích?

Trả lời

17

Chúng tôi hiển thị các tính năng ngôn ngữ mới được đề xuất (ít nhất là các tính năng có cơ hội nửa chừng nhìn thấy ánh sáng ban ngày) cho C# MVP để phản hồi sớm. Một số phản hồi chúng tôi nhận được rất thường xuyên về nhiều tính năng là "tốt I sẽ sử dụng tính năng này một cách thận trọng và tuân thủ các nguyên tắc thiết kế của đối tượng địa lý, nhưng đồng nghiệp goofball của tôi sẽ phát điên với điều này và viết lên khối lượng mã không thể hiểu được, không thể hiểu được rằng tôi sẽ gặp khó khăn khi gỡ lỗi trong mười năm tới, nhiều như tôi muốn, xin đừng bao giờ thực hiện tính năng này! "

Mặc dù tôi đang phóng đại phần nào cho hiệu ứng hài hước, đây là điều chúng tôi rất coi trọng; chúng tôi muốn thiết kế các tính năng mới để khuyến khích việc sử dụng chúng nhưng không khuyến khích lạm dụng chúng.

Chúng tôi lo lắng rằng các phương pháp mở rộng sẽ được sử dụng để phân biệt một cách bừa bãi "đối tượng" một cách tùy ý và do đó tạo ra những quả bóng lớn được đánh bóng lỏng lẻo khó hiểu, gỡ lỗi và bảo trì.

Cá nhân tôi đặc biệt quan tâm đến các phương pháp tiện ích mở rộng "dễ thương" mà bạn thấy trong chương trình "thông thạo". Nội dung như

6.Times(x=>Print("hello")); 

Yuck. "cho" vòng là phổ quát dễ hiểu và thành ngữ trong C#; không được dễ thương.

+0

Trớ trêu thay vì đây là, phương pháp mở rộng đó có thể được đặt ngay bên cạnh phương thức '.ForEach ' của tôi! Tôi thấy rằng dễ hiểu và ít chi tiết hơn so với vòng lặp for, nó cũng sẽ làm cho nó không thể vô tình giới thiệu off bằng 1 lỗi bằng cách sử dụng i

+2

@Chris: Trong trường hợp đó, chỉ cần sử dụng foreach. Bài đăng tuyệt vời của Eric ở đây giải thích các vấn đề với việc thực hiện ForEach - http://blogs.msdn.com/ericlippert/archive/2009/05/18/foreach-vs-foreach.aspx –

+2

Và chưa, F # bao gồm 'Seq.iter' tương đương với phương thức mở rộng 'IEnumerable .ForEach'. Tôi biết, ngôn ngữ khác nhau, triết lý thiết kế khác nhau, nhưng lập luận về việc tạo ra các tác dụng phụ vẫn có thể được áp dụng, và F # gần hơn với chức năng "thuần khiết" hơn C#. –

11

Vấn đề là các phương pháp mở rộng không nhất thiết phải rõ ràng.

Khi bạn nhìn thấy một cái gì đó như thế này trong mã:

myObject.Foo(); 

Bản năng tự nhiên là Foo() là một phương pháp xác định trên lớp myObject, hoặc một lớp cha của lớp myObject của.

Với phương pháp mở rộng, điều đó không đúng. Phương pháp này có thể được định nghĩa gần như bất kỳ đâu, đối với hầu hết mọi loại (bạn có thể định nghĩa một phương thức mở rộng trên System.Object, mặc dù đó là một ý tưởng tồi ...). Điều này thực sự làm tăng chi phí bảo trì mã của bạn, vì nó làm giảm khả năng phát hiện.

Bất cứ khi nào bạn thêm mã không rõ ràng hoặc thậm chí làm giảm tính "hiển nhiên" của việc triển khai, có một chi phí liên quan đến khả năng bảo trì lâu dài.

+2

Thật khó để nhấp chuột phải và chọn "Chuyển đến định nghĩa"? –

+2

@Mongus: Không, không khó. - nhưng bạn vẫn có thể bị nhầm lẫn về vị trí được xác định trong cấu trúc phân cấp. Ngoài ra, nếu bạn có các phương thức mở rộng có cùng tên với các phương thức ví dụ, nó sẽ làm tăng thêm sự nhầm lẫn. Có nhiều lý do để tránh chúng - ngay cả khi chúng thường hữu ích. –

+0

Ít nhất trong khi gõ, intellisense cung cấp các chỉ báo thông qua các biểu tượng trong menu intellisense về loại chức năng. – RBT

2

Khi Borland phát minh ra chúng (Người trợ giúp lớp Delphi) hướng dẫn đã là: "chỉ nên được sử dụng trong các tình huống đặc biệt".

Chúng đã trở nên được chấp nhận nhiều hơn (như một phần của LINQ), nhưng chúng vẫn là một chức năng bổ sung một cách kỳ lạ. Tôi nghĩ Reed có một câu trả lời tốt về lý do tại sao.

6

Sử dụng quá nhiều phương pháp mở rộng không tốt vì cùng một lý do sử dụng quá nhiều toán tử quá tải là xấu. Hãy để tôi giải thích.

Hãy xem ví dụ về quá tải toán tử trước. Khi bạn nhìn thấy mã:

var result = myFoo + myBar; 

bạn muốn hy vọng rằng myFoomyBar nhiều loại số, và operator + là thực hiện bổ sung. Điều này là hợp lý, trực quan, dễ hiểu. Nhưng một khi nhà điều hành quá tải vào hình ảnh, bạn không còn có thể chắc chắn về những gì myFoo + myBar thực sự đang làm - toán tử + có thể bị quá tải thành có nghĩa là bất cứ điều gì. Bạn không thể chỉ đọc mã và tìm ra những gì đang xảy ra mà không cần phải đọc tất cả các mã nằm bên dưới các kiểu liên quan đến biểu thức. Hiện tại, operator + đã bị quá tải đối với các loại như String hoặc DateTime - tuy nhiên, có một cách diễn giải trực quan về những gì bổ sung có nghĩa là trong những trường hợp đó. Quan trọng hơn, trong những trường hợp thông thường, nó bổ sung thêm rất nhiều sức mạnh biểu cảm cho mã. Vì vậy, nó có giá trị tiềm năng nhầm lẫn.

Vì vậy, tất cả điều này phải làm gì với các phương pháp mở rộng? Vâng, các phương pháp mở rộng giới thiệu một tình huống tương tự.Nếu không có phương pháp khuyến nông, khi bạn nhìn thấy:

var result = myFoo.DoSomething(); 

bạn có thể giả định rằng DoSomething() hoặc là một phương pháp myFoo hoặc một trong đó là các lớp cơ sở. Điều này rất đơn giản, dễ hiểu và trực quan. Nhưng với các phương pháp mở rộng, DoSomething()có thể được định nghĩa ở bất kỳ đâu - và tệ hơn, định nghĩa phụ thuộc vào tập hợp các câu lệnh using trong tệp mã và đưa nhiều lớp vào hỗn hợp, bất kỳ nhóm nào có thể lưu trữ triển khai DoSomething().

Bây giờ đừng hiểu lầm tôi. Cả hai phương thức quá tải và mở rộng của toán tử là các tính năng ngôn ngữ hữu ích và mạnh mẽ. Nhưng hãy nhớ - với sức mạnh to lớn có trách nhiệm lớn. Bạn nên sử dụng các tính năng này khi chúng cải thiện độ rõ ràng hoặc khả năng thực hiện. Nếu bạn bắt đầu sử dụng chúng một cách bừa bãi, chúng sẽ thêm sự nhầm lẫn, phức tạp và có thể là khuyết điểm đối với những gì bạn đang cố gắng tạo ra.

+2

+1 để trích dẫn Bác Ben. – RationalGeek

+0

'Nhưng với các phương thức mở rộng, DoSomething() có thể được định nghĩa ở bất kỳ đâu - và tệ hơn, định nghĩa phụ thuộc vào tập hợp các câu lệnh trong tệp mã và đưa vào nhiều lớp có khả năng kết hợp. DoSomething() '- Chỉ đơn giản là cần phải bấm chuột phải và chọn đi đến định nghĩa của nó từ trình đơn ngữ cảnh để con số này ra (trong studio trực quan). – RBT

1

Phương pháp mở rộng có thể được coi là Hướng khía cạnh. Tôi tin rằng Microsoft đang nói, nếu bạn có mã nguồn, bạn nên thay đổi lớp gốc. Nó phải làm với sự rõ ràng và dễ bảo trì. Lý do các phương pháp mở rộng là cần thiết là chúng mở rộng chức năng của các đối tượng hiện có. Trong trường hợp của LINQ, nếu bạn tìm hiểu cách nó hoạt động, các đối tượng có thể được tạo ra với nhiều loại khác nhau mà không thể biết trước. Sử dụng các phương thức mở rộng cho phép một số hành vi được thêm vào.

Ví dụ: việc mở rộng đối tượng Khuôn khổ .NET có ý nghĩa vì bạn không có nguồn, nhưng có thể có một số hành vi để thêm vào đối tượng. Mở rộng chuỗi, các lớp DateTime và FrameworkElement được chấp nhận. Nó cũng có thể chấp nhận được để tạo các phương thức mở rộng cho các lớp vượt qua các ranh giới ứng dụng. Giả sử, nếu bạn muốn chia sẻ một lớp DTO và có một số hành vi giao diện người dùng cụ thể, hãy tạo phương thức tiện ích đó trong giao diện người dùng.

Với ngôn ngữ động, mô hình là khác nhau, nhưng tôi tin rằng bạn đang nói về C# và VB.net.

3

Tôi tuân theo một quy tắc rất đơn giản với các phương thức mở rộng và các lớp trợ giúp.

Bất cứ lúc nào tôi có phương pháp có thể xuống hạng tới lớp trợ giúp tĩnh tôi sẽ cân nhắc tính hữu ích của nó trong phạm vi chung, tôi có thực sự muốn phương thức này được chia sẻ không? Ý nghĩa rõ ràng và hữu ích cho người khác?

Nếu tôi có thể trả lời có cho câu hỏi này, tôi sẽ tạo nó thành một phương pháp mở rộng. Ở đây tôi có một thư viện chia sẻ rằng trong số những thứ khác chứa tất cả các phương thức Extension của tôi trong 1 không gian tên với mỗi tệp lớp được định nghĩa là TypeNameExtension để nó trở nên rất rõ ràng những gì mong đợi bên trong lớp đó để bạn có thể dễ dàng định vị mã.

Nếu tôi đặt câu hỏi về nhu cầu về một phương thức trợ giúp như một cách sử dụng có thể truy cập trên toàn cầu, tôi sẽ khai báo phương thức tĩnh riêng và để nó bên trong lớp sở hữu.

2

Tôi có một thư viện ngày càng tăng về các phương pháp mở rộng hoạt động trên các lớp học BCL để đơn giản hóa các vấn đề phổ biến, trông có vẻ khó hiểu ngay từ cái nhìn đầu tiên. Thành thật mà nói, tôi nghĩ rằng phương pháp mở rộng làm cho mọi thứ dễ đọc hơn nhiều. Ví dụ, có một nếu kiểm tra tuyên bố để xem nếu một số nằm giữa hai số:

gì nếu right là ít hơn left về tai nạn? Và chính xác nó làm gì? Khi các biến chỉ đơn giản là như xleft, thì nó sẽ dễ thực hiện. Nhưng nếu họ là tài sản trên một lớp dài thì sao? Câu lệnh if có thể nhận được khá lớn, điều này làm xáo trộn thứ khá đơn giản mà bạn đang cố gắng làm.

Vì vậy, tôi đã viết này:

public static bool Between<T>(this T test, T minValue, T maxValue) 
    where T : IComparable<T> 
{ 
    // If minValue is greater than maxValue, simply reverse the comparision. 
    if (minValue.CompareTo(maxValue) == 1) 
     return (test.CompareTo(maxValue) >= 0 && test.CompareTo(minValue) <= 0); 
    else 
     return (test.CompareTo(minValue) >= 0 && test.CompareTo(maxValue) <= 0); 
} 

Bây giờ tôi có thể cải thiện của tôi nếu tuyên bố vào này:

if (x.Between(left, right)) 
{ 
    // Do Something 
} 

Đây có phải là "phi thường"? Tôi không nghĩ như vậy ... nhưng tôi nghĩ rằng đó là một cải tiến đáng kể về khả năng đọc, và nó cho phép kiểm tra thêm về đầu vào thử nghiệm cho an toàn.

Mối nguy hiểm mà tôi nghĩ Microsoft muốn tránh ở đây là mọi người viết một tấn Phương pháp mở rộng xác định lại EqualsToString.

+0

Nhưng nó thực sự tốt hơn nếu (Giữa (test, minv, maxv)) {}? – jmucchiello

+0

@jmucchiello - Thực ra, nó sẽ là 'if (MathUtilities.Between (test, minv, maxv)) {}'. Vì vậy, bạn sẽ phải tự hỏi mình, giá trị nào có 'MathUtilities' cho tôi, và nó có lấy đi từ tính dễ đọc không? Tôi nghĩ câu trả lời là có. Đó là toàn bộ điểm đằng sau giao diện Fluent. – Nick

+0

@jmucchiello nó chắc chắn đọc tốt hơn và rõ ràng hơn là những gì sẽ có giữa những gì .. –

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