I currently have a class Foo
,
i’ve decided I need a second type of Foo
where I want to test out a significantly different implementation. It will no doubt share some functionality with Foo
so I’ll need to create a Abstract Class parent for both of them.
At the end of the process I want to have:
AbstractFoo
: an abstract classFooBoo
: functionaly identical to the originalFoo
FooBar
: the new subclass of AbstractFoo I’ve created.
There are two ways I could go about this:
Copy Paste, then Refactor
- Create a copy of
Foo
, and name itFooBar
and renameFoo
toFooBoo
- Edit
FooBar
with the new implementation - Inspect,
FooBar
andFooBoo
, move methods that are the same between them into a abstract classAbstractFoo
, which both inherit from
Refactor, then Implement
- Rename
Foo
,FooBoo
- Consider what methods are not related to the part of the implementation that
FooBoo
andFooBaz
are going to be different in. Move these toAbstractFoo
- Create
FooBaz
and reimplement all the methods that remain withinFooBoo
Which is to be preferred?
*Is there another method?*
Last time I had to do this, it was because the I decided that Foo
had 2 different modes, that was determined by a boolean passed into the constructor.
This time (on a different class), it is because I want to try out using a different engine underneath that might be faster.
I want to keep FooBoo
around because it has a subclass that I think will be much harder to implement for FooBaz
, and I also want to be able to check that in the end they behave the same.
2
I would do it this way:
- Rename
Foo
toAbstractFoo
and make it abstract - Create
FooBoo
that derives fromAbstractFoo
- Push down all methods from
AbstractFoo
toFooBoo
leaving behind only abstract methods - Implement
FooBar
I like this because most of it can be achieved using refactor functions of IDE. This makes sure that you don’t have to manually change code where Foo
was used. Also, compiler gives you errors in places where you used Foo
‘s constructor, because it cannot instantiate abstract class.
2
Hard to say, it depends on the case, what approach makes the most sense.
If most of the stuff is going into the base class, I’d either use your “Refactor, then Implement” approach, or follow a slightly changed variant of @Euphorics’ approach: I would make sure everything works as before after his Step 3 (completed AbstractFoo
and FooFoo
) and before moving on to his Step 4 (creating FooBar
).
In some cases, it is the other way round. You decide for a common base class, which actually will have only a few methods from old Foo
. In that case, rename Foo
to FooFoo
, create an empty AbstractFoo
and move just the few methods into the new base (don’t forget to change FooFoo
s inheritance).
I have also used the “copy + paste + refactor” approach. It can be useful, when the methods to move to the base compared to the methods that need to stay in FooBoo
are around 50:50 distribution. Again, it depends, and sometimes you’ll find during refactoring, that you need another class to extract things from the Foo
class family.
0
I would go with the copy+paste approach as a starter, that gives you flexibility to do whatever you want with the new class without bringing any unintended side effects into existing code. This would sort of fall under the “tracer application” pattern since your main intent is to try out a completely new engine.
When you have your successful result(s) from your copy+pasted code you will follow the test driven pattern and refactor out the common bits into your abstract base class etc. This would come close to what is discussed in Kent Becks Test-Driven Development by example as well as the common work patterns discussed in the pragmatic programmer.
IMHO the above is how I would approach this problem most of the times since it leaves room for wild crazy experimentation without affecting the already working and tested code with the least amount of effort (eg. no current tests will be modified or broken etc and logic stays intact). The big effort comes when you have your working tracer to either throw it away and write a clean implementation based on your tracer code or to start integrate it with your actual code base.