Interfaces vs Abstract Classes as a module boundary.

Started by
16 comments, last by dilyan_rusev 11 years, 9 months ago

[quote name='ChaosEngine' timestamp='1342995676' post='4962051']
Just to add that interfaces are generally easier to mock and hence unit test client code.


Care to elaborate on this? I don't see how they're really any easier to mock.
[/quote]

Sure. Mocking frameworks such as NMock, Rhino Mocks and Moq mostly work with interfaces. Some of them have some support for mocking abstract classes, but it's a lot easier to use interfaces (hence my comment).


IMO, both have their place. From the perspective of a consumer of a component/class/service, an interface makes the most sense. For an implementer of a service, if there is shared state or functionality, then an abstract class will, as you pointed out reduce DRY.


Or people extend that interface with another. More often than not, the source module then needs to do type checking/casting to see if some other interface happens to be on the returned type, violating LSP.


Ughhh. I agree that is ugly, but I'd argue that's down to bad design. A client should be given the bare minimum of what it needs to do it's work and no more.


My main complaint regarding interfaces is the inability to specify constraints on construction (e.g. an implementing class must supply a constructor with these parameters).
For example, if I have a generic Presenter class that manages a View, it would be nice to be able to write code like this

[source lang="csharp"]
TPresenter<TView> BuildPresenter(TView view)
where TView : IView
where TPresenter<TView> : IPresenter<TView>, new(TView) // doesn't exist in C#
{
var presenter = new TPresenter<TView>(view);
// do some work with presenter
return presenter;
}[/source]


You can already specify a parameter-less constraint, I don't see why that couldn't have been extended. Would save creating factory classes.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight
Advertisement
Sure, that makes sense. I don't remember having problems with Moq or Rhino Mock, but then again I tend to avoid those sort of frameworks when I can.

I agree that it is bad design for the typecasting, though it is pervasive in this new code base I am working with. Part of me sees the pervasive use of manual type checks and casting, the duplication of interface implementation, and the non-existent use of abstract classes... And can't help to make a correlation.
About unit testing... that is not quite true. It is absolutely the same, except for methods that are not virtual. This is easily fixable through the profiler API, though it requires some C++/COM. The mocking frameworks you listed are amateur grade, but if you pick a more advanced tool, you will be able to mock static & sealed classes, static methods & stuff. The problem there is execution speed, as dynamically rewriting the method body/class definition isn't the fastest thing to do, as all optimizations go to hell (plus you do use some sort of a profiler).

Like I said, casting is easily fixable with even basic utilization of the locator pattern - you just look up for features. Of course, you would use a generic interface to have no casting. If the client wants the feature, you just do your stuff. Much more civilized than simple casting.

In my opinion, using only interfaces or only abstract classes is like fanaticism. Abstract classes are very useful when you have classes with a lot of methods (e.g. a class for CRUD operations on more than one type). You really don't want to make the client implement 20 or so methods in an interface.

About restrictions on creation - I disagree. I've grown to appreciate the idea that constructors are just for initializing your fields. You throw less exceptions, your objects are always in valid state (except for statics in parallel code), they are easy to unit test, and code is simpler overall. Why add factory for something as simple as allocating memory? If you use IoC, you already have free factories for the interfaces that you provide. For interfaces that the clients must provide/implement, object creation is an implementation detail, and I would not put any restrictions on it.

The mocking frameworks you listed are amateur grade, but if you pick a more advanced tool, you will be able to mock static & sealed classes, static methods & stuff.


Can you give me an example of a "more advanced tool"? I agree that NMock isn't great, but I've found Rhino to be pretty good.




Like I said, casting is easily fixable with even basic utilization of the locator pattern - you just look up for features. Of course, you would use a generic interface to have no casting. If the client wants the feature, you just do your stuff. Much more civilized than simple casting.


Sweet monkey jesus, no. That's dragging us back to the nightmare of COM and QueryInterface. It's needless complexity and an anti-pattern. Give the client what it needs to do it's work.


In my opinion, using only interfaces or only abstract classes is like fanaticism. Abstract classes are very useful when you have classes with a lot of methods (e.g. a class for CRUD operations on more than one type). You really don't want to make the client implement 20 or so methods in an interface.


Yep, as I said, they were designed for different purposes. Interfaces are designed to expose a contract to a client, abstract classes are designed to share implementation.
Although a abstract class with 20 methods is a code smell.


About restrictions on creation - I disagree. I've grown to appreciate the idea that constructors are just for initializing your fields. You throw less exceptions, your objects are always in valid state (except for statics in parallel code), they are easy to unit test, and code is simpler overall. Why add factory for something as simple as allocating memory? If you use IoC, you already have free factories for the interfaces that you provide. For interfaces that the clients must provide/implement, object creation is an implementation detail, and I would not put any restrictions on it.


Fair point.
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

Can you give me an example of a "more advanced tool"? I agree that NMock isn't great, but I've found Rhino to be pretty good.


Typemock and JustMock come on the top of my mind. If you don't need sealed/non-virtual/static mocking, Rhino is a good enough framework. It's not like it is impossible to mock statics and .NET types, but that requires wrapping and additional work on your part (e.g. utility methods that call the unmockable static method and then mock that). Rhino works by subclassing, so you are in trouble if you need to mock private stuff (if memory serves correctly). That way the framework forces you to make the access level to protected or internal, which sucks a great deal. You might argue that you should not mock & unit test private methods. And you will be right. Yet there are some legitimate cases for that (where you want to hide implementation details in utility methods), but overall it is a bad idea. Nevertheless, I don't like the mocking framework to dictate higher visibility than what is reasonably required.


Sweet monkey jesus, no. That's dragging us back to the nightmare of COM and QueryInterface. It's needless complexity and an anti-pattern. Give the client what it needs to do it's work.


Casting is the same stuff as QueryInterface. I don't see the functional difference between
var sth = baseInstance.GetExtension<IExtension>();
if (sth != null)
{
this.DoExtension(sth);
}

and
var sth = baseInstance as IExtension;
if (sth != null)
{
this.DoExtension(sth);
}
... except that the first gives you the flexibility to return an instance that is different than baseInstance. Why not implement additional features in smaller classes, if it makes sense? If not, just return this and be done with it. I guess it is not necessary for the majority of what people need, but that doesn't mean the idea is bad and rotten to the core. If you don't need that flexibility, don't use it - it will make the codebase better - just like with everything else in programming. But it will also simply stuff if you need it.

The glorified IoC frameworks like Unity & Structure Map use the same concept. When applied properly, it is not a bad thing. Maybe I haven't worked that much with COM to dislike it. It is a technology that made sense at the time for what it was intended to do: C++ binary compatibility for modules (like what C is, and what WinRT will do with supported languages).

Why not implement additional features in smaller classes, if it makes sense?


Because you end up doing this sort of casting/QueryInterface, which is fragile at best.

You end up knowing too much about the implementation details of subclasses, violating LSP or the Law of Demeter depending on your viewpoint.

I'm not saying that things shouldn't end up in smaller classes (quite the opposite), but those smaller classes should be composed rather than exposed via a query interface or conditional casting.


The glorified IoC frameworks like Unity & Structure Map use the same concept. When applied properly, it is not a bad thing.
[/quote]

God I hate IoC frameworks.

Integration/deployment is (in my experience) the source of the worst risk during a software project. We've gotten good at mitigating errors writing code, and testing code, but all of the runtime integration errors are still common to make and messy to debug. Worst yet, they happen close to release (either during integration or during deployment testing); causing the most impact.

IoC containers shift a whole pile of issues into this area. People end up voodoo debugging obscure configs rather than actual logic. Frameworks exist to get rid of that sort of code plumbing, not make more of it.

[quote name='ChaosEngine' timestamp='1343079696' post='4962375']
Can you give me an example of a "more advanced tool"? I agree that NMock isn't great, but I've found Rhino to be pretty good.


Typemock and JustMock come on the top of my mind. If you don't need sealed/non-virtual/static mocking, Rhino is a good enough framework. It's not like it is impossible to mock statics and .NET types, but that requires wrapping and additional work on your part (e.g. utility methods that call the unmockable static method and then mock that). Rhino works by subclassing, so you are in trouble if you need to mock private stuff (if memory serves correctly). That way the framework forces you to make the access level to protected or internal, which sucks a great deal. You might argue that you should not mock & unit test private methods. And you will be right. Yet there are some legitimate cases for that (where you want to hide implementation details in utility methods), but overall it is a bad idea. Nevertheless, I don't like the mocking framework to dictate higher visibility than what is reasonably required.
[/quote]

Cheers, will have a look into that.



Casting is the same stuff as QueryInterface. I don't see the functional difference between
var sth = baseInstance.GetExtension<IExtension>();
if (sth != null)
{
this.DoExtension(sth);
}

and
var sth = baseInstance as IExtension;
if (sth != null)
{
this.DoExtension(sth);
}
...

I understand that they're functionally the same, I don't like either approach. It feels like a code smell. See Telastyns answer above.




IoC containers shift a whole pile of issues into this area. People end up voodoo debugging obscure configs rather than actual logic. Frameworks exist to get rid of that sort of code plumbing, not make more of it.


Not really. Castle allows you to put the interface mapping in code.
[source lang="csharp"]container.Register(
Component.For<IService>()
.ImplementedBy<Service>()
);[/source]
It's pretty trivial and in my current project of ~500k lines, we've never had any significant issues with it. The app calls the installer for the service at startup and that's it. Meanwhile the unit test framework does the standard mocked IoC thing and makes it pretty easy to test.

YMMV, of course. :D
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

I understand that they're functionally the same, I don't like either approach. It feels like a code smell. See Telastyns answer above.


I don't know of a good way to extend an OO API over time that does not involve something ugly. The examples I have are Win32, DirectX (COM) and Eclipse. They've all done it responsibly (meaning screwing with clients as little as possible), but the end result isn't particularly beautiful. Yet we need some way to not break our clients' code every time we need to change the interface. Not everybody has IBM's and Microsoft's resources, so you can't duplicate stuff (COM/Win32), and the only reasonable way that I know of is to add interface extensions (Eclipse). If you are lucky enough so that your change won't break anything - then sure, don't use them. If you have any ideas in that department - I would love to hear them.

About the IoC thingy. There is the dark realm of Unity's method rewriting. I can't seem to remember the proper way to call it, nor can I find anything on google to demonstrate it. The idea is that you can configure your class so that when certain methods (e.g. virtual with a certain matching pattern, like all Get* methods), Unity will create something like a chain of methods to be called when the original method is called instead. Your original implementation is just one of the methods that are going to be called. This enables you to do things like automatic security checks, but it is a nightmare to debug. This is because of the way it is implemented - Unity emits a new assembly where they inherit your class. They override the methods for which you want this behavior, and emit some code in order to implement this. The problem stems from the fact that, when you step into your method, you go right into Unity's resolution logic, some of which is undebuggable (I.e. dynamically emitted IL). So in order to test your original logic, you have to set breakpoints into the body of your original method, you can't just "step in". And this is just one of the many joys if you use the more advanced features of IoC frameworks.

Edit:
Oh yes, it is called method interception. Please make the world a favor and avoid it as much as possible tongue.png

Edit2:
They don't override just the configured methods, but everything virtual. Happy debugging :D

This topic is closed to new replies.

Advertisement