Wednesday, January 25, 2006

Refactoring: Extract Mixin

I think I might have a previously uncatalogued refactoring here. This is an alpha version. If at least a few people find it useful, I'll do a proper writeup. Here goes:

You have two classes or mixins with similar features.

Create a mixin module, and move the common features to the mixin.

This refactoring is applicable with languages that support mixins, like Ruby.

Motivation

The primary purpose of Extract Mixin is to eliminate code duplication. In this respect, this refactoring is very similar to Extract Superclass. There are some noteworthy differences:
  • Mixins are not a part of the inheritance hierarchy. Thus, it is possible to mix any number of mixins into a class, or into other mixins. Consequently, mixins are more flexible than class hierarchies.
  • Mixins can create instance variables, but this introduces the risk of naming conflicts. Therefore, this refactoring is safest with methods that get all their variables as parameters to the method call.
Extract Mixin can also be used to extract, modularize and encapsulate cross-cutting concerns, for example logging and authentication, in a manner similar to applying advice in Aspect-Oriented Programming (AOP).

Mechanics

  1. Run the test suite to ensure that everything works as it should.
  2. Create a new file containing an empty mixin module.
  3. Include the mixin in the original class (or original mixin).
  4. Run the tests
  5. Move methods, one by one, to the mixin module. Run the tests after each move.
  6. Examine the methods left in the class(es). If there are common parts, use Extract Method to extract those parts into separate methods, then move the methods to the mixin. Run the test suite to verify that everything works.

Example

The following two classes both have a walk() method, but they are in different inheritance hierarchies:


class Human << Mammal
def walk()
...
end
end

class Penguin << Bird
def walk()
...
end
end



By factoring out the walk() method into a mixin, we can eliminate the code duplication:


module Biped
def walk()
...
end
end

class Human << Mammal
include Biped
end

class Penguin << Bird
include Biped
end


5 comments:

pate said...

Nice post, Several of us are going to try this refactoring on a pair or libraries we work on (rwb and r43) to see where it goes. Keep up the good work.

Chris Hedgate said...

I am not sure it needs to be more "proper" than it is, but if you are counting the number of people that find it useful then here is +1.

Henrik said...

I am glad you liked the post. (And by "glad" I mean jumping up and down and screaming "Yes!" loud enough to frighten the fish in our aquarium.)

I'll do a writeup of the corollary to Extract Mixin, i.e. Replace Mixin With Class, in a few days. I should have done it earlier, but I developed a sudden fanatic interest in process simulation, and haven't been able to think about anything else for a couple of weeks.

Chris Hedgate said...

Sounds great. Btw, is it just me or is there no syndaction feed for your blog? I know that Blogger does not syndicate by default, there is an option somewhere to activate it.

Henrik said...

The feed is at http://kallokain.blogspot.com/atom.xml. I had turned it
on, but forgot to put a link to it in the page template.

Thanks for pointing it out.