Thêm 1 ví dụ về Interface Segregation Principle. Trong bài này, tác giả đề cập đến việc áp dụng Interface Segregation Principle cho 2 trường hợp cơ bản nhất là kiểu dữ liệu của tham số (parameter) được truyền vào trong 1 hàm và kiểu dữ liệu của kết quả trả về (return) từ 1 hàm. Tùy vào mục đích sử dụng hàm mà mình có thể giới hạn tầm hiểu biết (knowdlege) của hàm đối với đối số truyền vào đến mức vừa đủ dùng. Không nên buộc các thực thể phần mềm phụ thuộc vào những interface mà chúng không sử dụng đến.
Cùng xem bài viết dưới đây của Brendan Enrick để hiểu rõ thêm về ví dụ này.
At some point, I’m sure you’ve had someone suggest that you use an interface instead of a concrete class as a dependency. Assuming you’re following the Interface Segregation Principle, that could be exactly what you want to do. Well written interfaces can make your code much simpler and more clearly define your dependencies. That’s why Interface Segregation Principle is one of those popular SOLID principles you’re always hearing about.
I recently read an interesting tweet from Derik Whittaker talking about using IEnumerable when an ICollection or IList would be the better choice.
Ugg, may want to fix your code rather than do this “// ReSharper disable once PossibleMultipleEnumeration”
This move toward making everything IEnumerable<T> is a trend happening across the C# community. I believe that ReSharper (a tool I use and love) is leading to some of this. When you write a method that takes in a collection as a parameter, and your method does a foreach over that collection, ReSharper will suggest (correctly) to change it to an IEnumerable. This is because you only enumerated it once, and that means that you can be more relaxed in your requirements.
Side Note: IEnumerable is not technically the requirement for a foreach loop.
The problem people run into with IEnumerables is that they’re not all collections. IEnumerable<T> just means that there is a method to get an Enumerator. In simple terms, I mean that it’s possible to walk through the items in the enumerable one at a time. Sometimes that requires work that’s more than constant time to get to the next item in the enumerable class. The comment that Derik mentioned in his tweet is one that tells ReSharper to not warn you about a possible issue. That issue is that you might be doing the work of walking through the enumerable more than once. When it sees an enumerable that is enumerated for a second time, you’ll receive this warning.
Keep Your Method Parameters Permissive
When you’re making a method parameter, you want to make sure that you’re only requiring exactly what you need. In doing this, you’ll likely create interfaces following the Interface Segregation Principle. When dealing with collections, you’re likely to accept an IEnumerable<T> if you only enumerate once. You might accept an ICollection<T> if you need to enumerate a couple of times, allow adding and removing, or need to count the items. If your collection needs random, array-like index access, you should consider using IList<T>.
This allows the caller of your method to give you whatever they have at the time. You really don’t care as long as it has what you need. If their object is an IEnumerable that isn’t also an IList, they can convert it before providing it to you. Working this way makes your API much more flexible, since you’re making your minimum requirements clear.
Return Usable Types From Your Methods
Sadly, I see the reverse being pushed as a positive. You will findinformation telling you to return IEnumerable<T>, so that you can easily change. This, however, means that you’re providing your method’s consumer with no information about the object they’re receiving. If they need access to array-like indexing, they have to callToList() on your return value in order to use it.
Since ICollection<T> implements IEnumerable<T> and IList<T> implements ICollection<T> and IEnumerable<T>, you can return an IList<T> allowing your method’s caller to use IList<T>, ICollection<T>, or IEnumerable<T> depending on their usage. You’re using an interface and still giving the consumer the power of choice. You can avoid tightly coupling to a concrete implementation while still providing a useful return value.
I almost never return IEnumerable<T>. I don’t know that the return value will only be enumerated once. That’s outside my current layer of abstraction, so I shouldn’t be dealing with it. The safe bet is just to return a useful collection if that’s what I have. If my value really is enumerable, but not a collection, I will return an IEnumerable. In all other cases, it should be a more useful interface. lists an collections really are cohesive concepts that should be grouped into one object when needed.