Question

If a class can only perform one task, could it be considered to be "too specialized"?

My intuition is that classes should be abstractions that are capable of representing themselves in multiple ways. I'm reviewing some class definitions that are so specific that these objects cannot be used except for a single use case.

Example: If I wanted to have a class called Dog, I may also want a subclass like Dog::Collie. If we want an instance of Lassie the dog, Dog::Collie should be abstract enough to describe her. If we need to specialize a new class Dog::Collie::Lassie, I'd be inclined to refactor Collie to support this - not super specialize a new derivation.

Edit: for clarification, Lassie is not an attribute of Collie or Dog, it's a concrete class definition that has functions like getHelp() and smileForCamera() that are not in Dog or Collie.

Was it helpful?

Solution

If we need to specialize a new class Dog::Collie::Lassie, I'd be inclined to refactor Collie to support this - not super specialize a new derivation.

Why? Apparently, there have been nine different Lassies over the years, and they presumably each had their own birth date, weight, height, medical history, list of film and television appearances, etc. Depending on what your program is meant to do, it might make sense to have Lassie as a subclass because not all Collie instances need to track their media appearances, fan club connections, etc.

I think the point you were trying to make is that if a class represents one specific thing rather than a category of things, it's too specific. My counterargument is that when you create a class you often can't know how many instances of a thing you'll eventually have, and you shouldn't worry about that. If generalizing Collie to include the behaviors needed for Lassie makes you write a lot of code that looks like:

if (dangerPresent) {
    if (name is Lassie) {
        runForHelp
        barkBarkBark
    else {
        runAway
    }
}

then it's probably a lot better to create a Lassie subclass even if you know for certain that it'll only ever represent a single dog.

If a class can only perform one task, could it be considered to be "too specialized"?

Some would argue the opposite, i.e. that according to the Single Responsibility Principle a class should only do one thing. Exactly what counts as a "task" or "responsibility" depends a lot on where the class fits into the design of the system. Your example above helps to clarify that... if the job of the Collie class is to represent any collie, then making it also model characteristics that apply only to some subset of collies (even if that subset has only one member) violates SRP.

I think a class can be too specialized, but mainly because it purports to be more abstract than it really is. Again, see the SRP. If a class's implementation is doing things to support only one or more subsets of the category that it represents, then the class is too specialized and should be refactored.

OTHER TIPS

Collie can be regarded a special kind of dog, Lassie cannot be regarded a special kind of Collie.

You seem to have trouble making the distinction between classes on the one hand and individual variations on the other hand. Lassie is a Collie incarnation, an individual or in OO-speak: an instance. You can have an infinite number of instances of Collie and they may all be different in some way, yet they would not be different in "collieness".

It is only for fundamental differences that apply to all instances of a kind of dog that you would consider introducing a new class. Collies have long hair, they all have a pointy snout, they all share the patched coloring and the fluffy tail, so... maybe we should have a sub-class of dog to model this kind of dog. And then you would only do this if you care about these traits, if they make a difference in your problem domain. I am more of a cat man so I would be happy with just Dog (dogs are dogs, I don't care about their furs or sizes, I just want them to keep their distance).

The bottom line is: a class should make your software easier to understand, to write and to maintain. If a new class defeats that purpose, if it only adds complexity period, then you are over-specializing.

Classes are not exactly abstractions, they are concretions (potentially of an abstraction). Generally, whatever you can create an instance of typically:

  • Is concrete.
  • May have specific state.
  • May have specific behavior.

It might be that you are confusing the class definition and the class instances. Of course class definitions are an abstraction that represents the range of all possible instances (defined as the set of all instances occurring by cover the entire domain of the internal state parameters).

Your Dog::Collie:Lassie example showcases a minor problem. Dog and Collie are class definitions, but Lassie should be a specific class instance. It is a just matter of taxonomy, really. Collie is a Type, but Lassie is a Name. So, unless you really want to represent Lassie as a Type (in which case you may have multiple Lassie instances).

To make this clearer, let me introduce you to my two friends. I have 3 instances of Michael and another 3 instances of Jordan:

Michael morningMichael = new Michael();
Michael afternoonMichael = new Michael();
Michael nightMichael = new Michael();

Jordan morningJordan = new Jordan();
Jordan afternoonJordan = new Jordan();
Jordan nightJordan = new Jordan();

Now, this is my mental model. When I see Michael in the afternoon, it is a different Michael, who happens to bear terrific resemblance to the morning version of Michael. He is a little more tired than morning Michael, his weight is slightly different, and stuff, but he still has two hands, two legs, one head, same hair color, same complexion, etc. There are two obvious problems with this mental model, and one that is not so obvious.

  • There is gross definition duplication. Michael and Jordan share very similar traits so I will have to code definitions multiple times. I can overcome this by using base definitions and inheriting, but I will reach a point where, after inheriting, there will be virtually nothing more to define.
  • There is no resemblance of this mental model to reality, which is what I have been trained in for my entire life, so reality is a model I can perfectly, easily and fluently relate to, and whatever I can translate to or from reality is, practically, easier for me to understand, grasp and develop around. When I see a friend after a day goes by, I don't ever feel like it is a copy of my friend...
  • The not so obvious problem is that this is perfectly allowed using available coding tools and definition/prototyping capabilities.

In solving the third problem, at some point, Types must end, and State must come to be. Therefore, I choose to stop my definitions (types, really) at Human and let state manifest, in the form of instances. Then, whenever I read my code, it really takes me less than a few milliseconds to understand that whenever I come across instance micheal, it is really the same Human I came across earlier, when talking about michael. He may have changed internally, but it is the same guy (viz. the same byte-group in memory).

Human michael = new Human();

Human jordan = new Human();

So, like I said earlier, it is a matter of taxonomy, really. It is more like philosophy, but also a lot about what makes you more productive. Object-oriented programming borrows from the real world, because understanding the real world is something we all have a lot of training in, and we can express our ideas much easier in that model.

All that aside, the extent of detail one reaches is always dependent on the case. For example, while birds are, technically, dinosaurs, you will not come across many real-world code-bases with a hierarchy such as Dinosaur::Bird.... So, as usual, more often than not, your very own definitions in your code generally reflect what you are trying to achieve, and this is what you should use to balance your choices.

Licensed under: CC-BY-SA with attribution
scroll top