Splitting one class with one instance into two classes, one with one instance, and the other with two.
Consider this to be an app with a desktop-style user interface. It is to control some settings (integer values) of some hardware. When you enter the picture, it controls only one single setting via a "vertical slice" that involves three classes:
The ValueTweaker is some sort of composite view object that lets the user both view and control a single setting. Presumably, it lashes together other values (like buttons and a text field, a slider and gauges, or whatever - we're not concerned with that).
The Controller fields user intentions ("raise the setting!") from the
ValueTweaker. It's used to set the true setting in the hardware. Values can't exceed some predefined range. If the user tries to move outside the range, the value is "pegged" at either the top or bottom of the range.
The Hardware is an (imaginary) facade over whatever kind of complexity is required to tell true hardware to take on a new setting. Hardware can also spontaneously change its own value. Any changes to the hardware are published, which causes the
ValueTweakerto update its display. That is, the "Controller" only controls the "downward" direction (toward the hardware), not the display on the
Following a style of user experience design I learned from Jeff
Patton, I metaphorically consider a user's use of an app to include
navigation from place to place within it. A "place" is, roughly,
visible real estate on the screen. So, for example, there's a place
the user goes to change the setting. Oddly, the
Controller also has
responsibility for controlling this navigation. (For this application,
the places only have empty, standin classes.)
The latest task is to put a second
ValueTweaker on the same
place in the UI as the first, and then use it to control a second
hardware setting. This makes the
responsibility annoying, so that addition will come after
Controller into two classes: one that controls
movement from place to place (call it the Navigator) and one
that handles the vertical interaction (call it the
Exercise 1 (of 1+N+1): Superclass refactoring
The short version
SettingController an empty superclass of
Controller. Pull setting-relevant methods up into that
superclass, keeping the tests passing at all time. Sever the
inheritance connection, and rename
I recommend stopping here and trying the refactoring. If you get bogged down, use these...
Create an empty
SettingControllerclass with an empty
SettingControllertest suite. Have
Controllerbe its subclass. (Don't forget to put a
controller.rb.) All the
Controllertests still pass.
Identify the parts of
initializethat are about controlling settings. Move them up into the superclass. All the tests still pass.
One by one, move the
Controllertests that are about controlling settings up into the
SettingControllertest suite. Move the code to make them pass.
When that's done, we still have a
Controllerobject that does everything it used to do. We also have a
Configurationobject that wires the system together. The wiring is tested by an UpAndDownTheVerticalSlice "end-to-end-ish" test.
Configurationso that it (1) creates both a
SettingControllerobject, (2) connects the
ValueTweakerand to the appropriate object down toward the hardware, and (3) continues to connect the
Controllerto the objects that don't have anything to do with changing the tweakable value. The end-to-end test should still pass.
Since it no longer uses its superclass's behavior, change
Controllerso that it's no longer a subclass of
Controller's name to
Exercises 2 to 1+N: Other Refactorings
Invent and follow at least one other path that takes you from the same beginning class structure to the same end class structure.
Exercise 1+N+1: The Next Refactoring
Are you ready to add the next
ValueTweaker to the UI? Or is there another appealing refactoring lurking in the wings?
(I'd be inclined to "reify" the idea of a vertical slice into its own class.)