问题描述
是接口 +扩展方法(mixin)比抽象类更可取?
如果您的答案是"取决于",则取决于什么?
我看到了接口 +扩展方法的两个可能的优势.
- 接口是多继承的,类也不是.
- 您可以使用扩展方法以非断裂方式扩展接口. (实现您的接口的客户将获得您的新基础实现,但仍然可以覆盖它.)
我还没有想到这种方法的缺点.接口 +扩展方法可能会失败.
可能有一个明显的简单原因.有关此主题的两篇有用的文章是
推荐答案
扩展方法的偏低:客户端PRE-C#3/VB9无法轻松使用它.
就我而言,这就是关于这一点 - 我认为基于接口的方法非常好.然后,您可以很好地嘲笑您的依赖项,并且一切基本上都没有紧密结合.我不是班级继承的忠实粉丝编辑:我只是想到了可能有意义的另一个好处.某些具体的实现可能会提供某些通用方法的更优化的版本.
Enumerable.Count是一个很好的例子 - 它明确检查了该序列是否实现了IList,因为如果它可以在列表中调用Count,而不是通过整个序列迭代.如果IEnumerable<T>是具有虚拟Count()方法的抽象类,则可以在List<T>中覆盖它,而不是有明确了解IList的单个实现.我并不是说这总是相关的,也不是IEnumerable<T>应该是抽象的类(绝对不是!) - 只是指出它是一个可能的劣势.通过专门研究现有行为(诚然,只能影响性能而不是结果).
其他推荐答案
imho,这是错误的问题.
您应该将所有内容用于设计.
- 扩展方法不是成员.它们在句法上看起来像会员,因此您可以发现它们更容易.
- 扩展方法只能使用公共(或内部)接口.许多其他课程也可以做同样的事情.因此,扩展方法不是以OO方式的真实封装.
-
它们是静态方法,不能被覆盖,也不能在单位测试中被模拟. IS是一项非OO语言功能,呼叫者在静态上绑定.
-
抽象基类通常被滥用以"重用代码"(而不是真正的继承).这通常适用于继承.
问题应该是:"我什么时候应该使用接口,扩展方法或基类?"
- 只要您需要合同,就会使用接口(这总是发生).
- 使用(摘要)基类时,当您具有真正的继承情况时(您可以写一本关于如何判断这一点的书,所以我只是像这样离开它).界面也主要是同时实现的.
- 使用扩展方法的逻辑方法,这实际上不应该是类型的成员,因为它不是实现它的类型的责任 - 但是您想让它易于找到,并且很自然地称其为成员.
编辑:
或问题应该是:"我如何编写不属于基类的可重复使用功能?"
- 写一个曝光功能的接口
- 写一个可重复使用的库类,该类实现功能
- 编写一个实现接口并通过汇总可重复使用类的功能的类.
一般而言,我会说,扩展方法是商业逻辑的错误场所,除了特殊情况或特殊设计决策.
基本类仅在极少数情况下是正确的决定.如有疑问,事实并非如此.无疑,您应该再考虑一下.
其他推荐答案
接口倾向于使代码更加清洁,我觉得测试更容易.当您添加扩展时,您可以在保持清洁测试代码的同时添加更大的灵活性.
对我来说,抽象课程似乎总是很笨拙,使用接口我可以拥有一个对象工厂,该对象工厂返回一个特定于我试图完成的对象(关注点的分离).
只是做些东西 a具有称为数学的界面,该界面具有添加,减去,分裂和乘以乘法,然后我有一个名为Intmath的类,该类别实现了用于整数数学优化的数学,并且我还有另一个名为FloatMath的类,用于浮动数学,用于浮动数学,数学,而且我有一个通用物,可以实现数学来处理其他所有内容.
当我需要添加一些浮子时,我可以称呼我的Factory MathFactory.getMath(typeof(float)),并且有一定的逻辑知道,如果要使用的类型是浮点,则它将返回floatmath类.
这样,我的所有课程都较小且更可维护,称呼该类的代码较小,等等.
问题描述
Is an interface + extension methods (mixin) preferable to an abstract class?
If your answer is "it depends", what does it depend upon?
I see two possible advantages to the interface + extension approach.
- Interfaces are multiply inheritable and classes are not.
- You can use extension methods to extend interfaces in a non-breaking way. (Clients that implement your interface will gain your new base implementation but still be able to override it.)
I have not yet thought of a downside to this approach. There may be a glaringly simple reason that the interface + extension approach will fail.
Two helpful articles on this topic are
- Create Mixins with Interfaces and Extension Methods
- Abstract Base Classes Have Versioning Problems Too
推荐答案
Downside of extension methods: clients pre-C#3/VB9 won't be able to use it as easily.
That's about it as far as I'm concerned - I think the interface-based approach is significantly nicer. You can then mock out your dependencies nicely, and everything is basically less tightly coupled. I'm not a huge fan of class inheritance unless it's really about specialization :)
EDIT: I've just thought of one other benefit which might be relevant. It's possible that some of the concrete implementations could provide more optimized versions of some of the general methods.
Enumerable.Count is a good example of this - it explicitly checks whether the sequence implements IList or not, because if it does it can call Count on the list instead of iterating through the whole sequence. If IEnumerable<T> had been an abstract class with a virtual Count() method, it could have been overridden in List<T> rather than there being a single implementation which knows about IList explicitly. I'm not saying this is always relevant, nor that IEnumerable<T> should have been an abstract class (definitely not!) - just pointing it out as a small possible disadvantage. That's where polymorphism really would be appropriate, by specializing existing behaviour (admittedly in a way which only affects performance instead of the result).
其他推荐答案
IMHO, this is the wrong question.
You should use everything for what it is designed for.
- Extension methods are not members. They are syntactically looking like members, so you can find them easier.
- Extension methods can only use the public (or internal) interface. Many other classes could do the same. So extension methods are not a real encapsulation in an oo way.
They are static methods and can not be overridden and not be mocked in a unit test. Is is a non-OO language feature and the caller is statically bound to it.
Abstract base classes are really often misused to "reuse code" (instead of a real inheritance). This applies to inheritance in general.
The question should be: "When should I use Interfaces, extension methods or base classes?"
- use interfaces whenever you need a contract (and this happens all the time).
- Use (abstract) base classes when you have a situation for real inheritance (you could write a book about how to judged that, so I just leave it like this). An interface is mostly also implemented at the same time.
- use extension methods for logic that should not be actually a member of the type, because it is not the responsibility of the type to implement it - but you want to make it easy to find and and it feels natural to call it like a member.
Edit:
Or the question should be: "How do I write reusable functionality that does not belong to a base class?"
- write an interface that exposes the functionality
- write a reusable library class that implements the functionality
- write a class that implements the interface and reuses the functionality by aggregating the reusable class.
In general I would say, extension methods are the wrong place for business logic, except of special cases or special design decisions.
Base classes are only in rare cases the right decision. In doubt, it is not. In no-doubt, you should think again about it.
其他推荐答案
Interfaces tend to make code a little cleaner I feel it's a little easier to test. When you add Extensions your adding even more flexibility while keeping clean testable code.
For me abstract classes have always seemed clunky, using interfaces I can have an object factory that returns an object that is specific to what I'm trying to accomplish (separation of concerns).
Just making something up A have the interface called Math that has add, subtract, divide and multiply and then I have a class called IntMAth that implements Math that is optimized for integer math, and I have another class called FloatMath the implements Math that is optimized for Floating Math, and I have a generalMath that implements Math that handles everything else.
When I need to Add some floats I could call my factory MathFactory.getMath(typeof(float)) and it has some logic to know that if the type I'm passing in is a float then it returns the FloatMath class.
This way all of my classes are smaller and more maintainable, the code that calls the classes is smaller, etc.