views:

489

answers:

6

Me and my friend had a little debate. I had to implement a "browser process watcher" class which invokes an event whenever the browser that is being watched (let's say Internet explorer) is running.

We created a "Process watcher" class, and here starts the debate:

He said that the constructor should only accept strings (like "iexplore.exe"), and i said we should inherit "Process watcher" to create a "browser watcher" which accepts the currently used browser enum, which the constructor will "translate" it to "iexplore". he said we should use a util function which will act as the translator.

I know both ways are valid and good, but i wonder whats the pros and cons of each, and what is suitable in our case.

+1  A: 

Depends on what use case you have or what god you follow.

I don't say "inheritance is evil" but generally I follow the principle "Favor composition over inheritance" to avoid excessive class hierarchies.

dhiller
+4  A: 

Other things being equal I prefer the simpler solution (a single concrete class which takes a string as a constructor parameter) to the more complicated one (using a base class and a subclass).

Inheritance is appropriate when you want to vary behaviour: if the browser watcher will do something that the ordinary process watcher doesn't. But if you only want to vary the value of the data, then just vary the data.

ChrisW
+1  A: 

Inheritance should only ever be used to implement an "isa" relationship.

As you can say that a "browser watcher" is a specific instance of a "process watcher" then inheritance is suitable for this architecture.

Hence, for me, having the identity of what you are watching passed through as a part of the construction of the browser watcher implementation of the "process watcher" is definitely the way to go.

Edit: More specifically, inheritance is for when you want to specialise behaviour. For example, most animals make a sound, but you could hope to provide which sound to make in a class called animal, you must wait for the specialisation.

So then we have Horse class providing a "neigh" for its sound, a Dog class providing a "bark" for its sound, etc.

HTH

cheers,

Rob

Rob Wells
-1 because you say "is a specific instance" in your second paragraph. The "specific instance" part indicates that it should be an instance of ProcessWatcher, not a subtype. If you could say "is kind of ProcessWatcher", then you want inheritance. Hence why I say "is a" is a bad test.
rmeador
Although often used, saying "is a" isn't a very good answer. You could say swiss army knife "is a" lot of things - it is a knife, a can opener, a screw driver, a corkscrew, or you could say that it has a knife blade (which is a cutting edge), a stubby beaky thing (which isa can opener) etc.
Pete Kirkham
+2  A: 

If you have no other use for ProcessWatcher than to serve as the parent of BrowserWatcher than you shouldn't create it. If other Watchers are being implemented that have shared functionality that can be placed in ProcessWatcher, then you should (both are "isa" relationships so Rob's criterion is met).

It really is as simple as that. Arguing that some day you'll have other watchers is not an argument in favor of creating a separate class. It is a mental tic that you should lose ASAP.

Mark Brittingham
Creating an interface for ProcessWatcher, on the other hand, is widely regarded as a good way to make your application more flexible. I cite Code Complete as my reference.
rmeador
While I'm not generally one to argue with McConnell, I think I'd need a signficant argument for *how* this makes the application more flexible. If the time comes that we need a second class with a similar function, *then* I'd see abstracting a class/interface, but not before.
Mark Brittingham
BTW: If you have that argument to make, be sure to enter an answer here as I'd love to see it! Point us to references on the cc2e.com site, too, if you have them. I'm here to learn as much as to teach so I don't mind being proven wrong.
Mark Brittingham
+5  A: 

Lately I've been taking the approach of "Keep it simple now, and refactor later if you need to extend it".

What you're doing right now seems pretty simple. You only really have one case that you're trying to handle. So I'd say take the simpler approach for now. In the end, if you never have to make another kind of watcher then you'll avoid the extra complexity. However, code it in a way that will make it easier to refactor later if you need to.

In the future, if you find you need another type of watcher, spend the effort then to refactor it into an inheritance (or composition, or whatever other pattern you want to follow). If your initial code is done right the refactoring should be fairly easy, so you're not really adding much extra work.

I've found this approach works fairly well for me. In the cases where I really didn't need inheritance the code stays simple. But when I really do need it I can add it in without any real problems.

Herms
although i use resharper (which helps a lot with refactoring in c# case), i find that this approach doesn't work well with large project.
Orentet
I have a tendency to try and overgeneralize things a lot of the time, so this approach has worked really well for me as it stops me from doing so when I don't need to.
Herms
A: 

I agree that in most cases, simplicity over complexity is a good strategy, as long as your simplicity is not too short-sighted (ref. Herms, write code in such a way that you can easily re-factor later).

However, I also know how difficult it can be to shut up that bug in your ear that encourages a more thorough design. If you still want to favor inheritance without necessarily thinking in terms of "base class" and "subclass", you can simply define an interface (ex. IProcessWatcher) which is implemented by ProcessWatcher. When you use the ProcessWatcher object, refer to it in terms of the interface so that if you later decide to create a BrowserWatcher (or any other kind of ProcessWatcher), you can do so without forcing it to descend from ProcessWatcher, as long as it implements the IProcessWatcher interface.

Warning: Proceed with caution. It gets tempting to want to define an interface for every single object, and let's face it, that just ridiculous. =)

Ultimately, you need to find something that you're both comfortable with, since you will both have to maintain this code, and I think this might be a nice compromise, rather than simply "Inheritance or No inheritance".

Good luck!

ph0enix