« Octopus Delay| Main | The Idea and the Thing »
OOP-Man versus the Forces Of the Octopus
Recently a reader told me that partial classes were good for cases where one class had to implement multiple interfaces. What are you going to do, for instance, if you have a class that has to implement seven interfaces, each with twenty or so methods? This guy had classes that were over 3000 lines of code. I said that it seems to me there has to be a way to keep from having these huge honking classes. The commenters made some good points.
What if you are on a tight schedule? Come on, in the real world we don't get to make our classes all pretty. All this OOP stuff might be nice in a textbook or in a classroom, but hey, in the real world life ain't always perfect, dude. You've got over a hundred methods to implement and multiple interfaces and you already have this huge class that you are working with. You're on the clock. What are you going to say about that?
Quite a bit, actually.
Let's assume you were given that monstrosity and given just one day to make a couple of small changes and check the code back in. (For argument's sake, let's have Unit Testing happen on Day 2) What should you do?
First, let's look at what's going on here. You've got one class that has to implement something like 140 methods. It has contracts to support seven interfaces. I know you can't change the world, but you should at least understand the nature of your pain. What is going wrong here?
The main problem is that you've violated Separation of Concerns, which for our purposes means that classes should do one kind of thing and do it well. Why? Well there are a million reasons. How about the general purpose idea that when you open a file to work on it, you cannot remember every little thing in the entire program. You shouldn't have to. The purpose of classes is to group functionality and data into little packages that can easily be maintained.
Supporting seven interfaces is almost a guarantee that you can't do that. Sometimes, of course, you can be required to support multiple interfaces. A good example might be a list or collection class, which might have iList, Ienumerable, or any of a few interfaces that it supports. But take note here: most all of these interfaces are talking about the same general idea -- puttin' stuff in a sack. Adding the interfaces doesn't mean that the collection class has to do something completely new, like socket programming. It just means that the collection code needs to be more involved. The concern -- the idea of a sack o' stuff -- is still one thing.
So if you got interfaces all asking you to do different things you got a grade-A Charlie Foxtrot, as we used to say in the Marines. I'll let you figure that one out for yourself.
Looks like we ain't got no separation of concerns. We got a class that is required to do things that are diverse and unrelated. We can't fix the world. What can we do?
Well. Let's start with "where are we going?" What's the end state? Where would you like this code to be after a week of programming and cleaning up? If you can answer that question, you can move forward. If you can't, go read some more books on OOAD.
What do we call a thing that provides a bunch of loosely-coupled services to external actors? Last I checked, we called that a program, not a class. So the nature of your problem is that you have one class trying to do too much work. Your class is like a mini-program.
Where would I like to go? Heck, I'd like to take each of these separate concerns and put them in a freaking box. Let them do their thing without cluttering up the rest of my class. Something like this:

Break up and bust out into smaller pieces
Take your interfaces and make classes for them. Call the classes Something_Manager. After all, that what they are going to do, right? Manage this responsibility. Inside your original class, shell out all of your interface calls to your subclasses. Your "master" class should just be a huge switchboard, handling requests to your mini-program and delegating all the tough work to the little helper guys.
The beauty of this is that it forces you to start thinking about how to generically implement those interfaces, which is bound to be a big help when you have to do this again with another one of these classes from hell. The more you can genericize how to handle those interfaces, the LESS code you have to write. That's good. Let's do that.
But there is one other approach. I should mention it here. You can create nested classes. Basically this is the same strategy except instead of having separate helper classes you have helper classes that are part of your "master" class. The major benefit here is that, well, it looks more like a program anyway. After all, if your class is really fulfilling the role of mini-program, nested class structures model the way it would happen if it were in its own project. This solution looks kind of like this:

Use nested classes. Makes my head hurt just looking at the diagram. Coding should be even more fun
I do not like this alternative and I would not use it. But IF you do, then surprise, surprise! Partial classes might just be for you. After all, I wouldn't want to put all of those nested classes into one big file. I'd make several partial classes. Each one of those would be, of course, A NESTED CLASS. So you have partial classes, but no, it's not really partial classes. It's just using the partial class feature to separate sub, nested classes of your parent object.
Whatever you'd like to do (and I'd like to caution against option 2), now you are ready to start. Depending on how good a keysmith you are, this can be easy or this can be tough. Remember -- YOU ARE NOT CHANGING ANY FUNCTIONALITY AT FIRST. Simply bust out the helper classes, hook up the wiring, and use global variables if you have to in order to keep the code unchanged. Your goal is wiring, not coding.
Break it out and test it. If you have been diligent about not adding functionality, it should work okay first pass through. If not, correct it. Give yourself about a 1/3 to 1/2 of your time for this. Once the wiring is changed and working, then make your minor changes and test again. You should be stylin' and profilin' at this point. Make a note in your mind that getting rid of those globals will be step two, the next time you are in the code.
So that's how it is done, boys and girls. If you have a better way, I'm here for you. Tell me your plan. Remember that with IDE tricks you can do a lot of wordsmithing in a very short time. Learn those shortcuts! Coding in the IDE is just an advanced form of Word Processing (the real work should be happening between your ears, right?) so getting sharp on the Word Processor is just being smart at your job.
Leave a comment