This post is part of a series of reviews on the book Design Patterns in Ruby. Check out the Introduction post for a full table of contents along with some generic principles regarding Design Patterns.

An Adapter does what you might expect: it attaches itself into 2 ends which aren’t compatible between them. The classic analogy to this is a plug adapter, that thing you use to turn 3-way plugs compatible into a 2-way socket. Without the adapter, you’d be screwed and things just wouldn’t work, and it’s no different on software.

That class diagrams translates into this: the client thinks he’s talking to his target, but he’s actually talking to an adapter which redirects calls to the real target (adaptee). Let’s start simple with the plugs and sockets example, first we’ll try to fit in a 3-way plug into a 2-way socket:

class TwoWaySocket
	def initialize(plug)
		@plug = plug
	end
	
	def plug_in
		begin
			@plug.two_way_plug_in
		rescue
			puts "Plug doesn't fit!!"
		end
	end
end

class ThreeWayPlug
	def three_way_plug_in
		puts "All 3 pins plugged"
	end
end

socket = TwoWaySocket.new(ThreeWayPlug.new)
socket.plug_in

So I’m not lying, it really doesn’t fit. Adapters to the rescue!

class TwoWaySocket
	def initialize(plug)
		@plug = plug
	end
	
	def plug_in
		begin
			@plug.two_way_plug_in
		rescue
			puts "Plug doesn't fit!!"
		end
	end
end

class ThreeWayPlug
	def three_way_plug_in
		puts "All 3 pins plugged"
	end
end

class PlugAdapter
	def initialize(three_way_plug)
		@three_way_plug = three_way_plug
	end
	
	def two_way_plug_in
		@three_way_plug.three_way_plug_in
	end
end

#we'll now call a socket with the adapter instead
socket = TwoWaySocket.new(PlugAdapter.new(ThreeWayPlug.new))
socket.plug_in

As you can see, we’re just creating an object that matches one interface with the other so all things can work gracefully.

Now we get to the cool part where we do things the Ruby way. Instead of creating an intermediate class, we could…cheat! Yes, why use a plug adapter if we can get a screwdriver and open up the 3-way plug, remove a pin and adjust things inside so it’ll work with our 2-way socket. In real life that might seem hard, but in this case:

#make sure the original class is loaded
require 'three_way_plug'

class ThreeWayPlug
	def two_way_plug_in
		three_way_plug_in
	end
end

socket = TwoWaySocket.new(ThreeWayPlug.new)
socket.plug_in

There, we simply reopened the freakin’ class at runtime and said “you WILL have what the socket requires, you WILL fit!”. The cool thing about doing this is that you don’t have to change the code to use an adapter and you don’t have to create yet another class just to handle the situation, you just reopen and cirurgically change whatever you want.

Does changing the whole class seems too drastic? What if you wanted just that one object to adapt instead of everyone? No problem, Ruby gives you the power to edit instances much like you’d edit classes:

three_way_plug = ThreeWayPlug.new
socket = TwoWaySocket.new(three_way_plug)
socket.plug_in #this won't work

class << three_way_plug
	def two_way_plug_in
		three_way_plug_in
	end
end

socket.plug_in #but this will

Or if you think class << is scary, you can define a new method on the instance by putting the instance name before the method name:

three_way_plug = ThreeWayPlug.new
socket = TwoWaySocket.new(three_way_plug)
socket.plug_in #this won't work

def three_way_plug.two_way_plug_in
	three_way_plug_in
end

socket.plug_in #but this will

As usual, there are pros and cons about both ways. Class/instance redefition in certainly simpler and easier, but that's untrue when it comes to complex classes that you need to adapt, specially when you don't have a full understanding of that class, so, for those cases, stick with the classic, good ol' GoF way.

Case any of these last 2/3 chunks of code look unfamiliar to you, I suggest reading my post on the Ruby Object Model and Metaprogramming, it will help you understand and learn the powers of Ruby reflection, which is constantly used due to Ruby’s dynamic typing style.