Given some library that implements some widespread protocol or something similar (for instance FTP), how would I keep my standard compliant code separate from code that is only needed to be able to cooperate with not so standard compliant systems?
A nice example where this would make sense too IMHO are libraries like jQuery that have to consider all those browser peculiarities. Projects that have to keep legacy compatibility would probably also be a good target audience for such techniques.
I'm especially interested in ruby solutions but language independent patterns or good examples from other languages are welcome too.
I already found a related question here on stackoverflow, but are there any other approaches?
- Define different implementations for the different modes (this prevents you from having to mix "good" code with code that's just there to keep backwards-compatibility). Ideally, the legacy layer is only a wrapper around the standards-compliant code.
- Detect to what degree the underlying system (browser, remote server, ...) is standards-compliant. How this is done in detail is obviously highly dependent on the specific case.
- Choose right implementation for the particular system and transparently plug it in.
- Give the user a chance to check in which mode we are and to force a specific mode.
Small Ruby example:
class GoodServer
def calculate(expr)
return eval(expr).to_s
end
end
class QuirkyServer
def calculate(expr)
# quirky server prefixes the result with "result: "
return "result: %s" % eval(expr)
end
end
module GoodClient
def calculate(expr)
@server.calculate(expr)
end
end
# compatibility layer
module QuirkyClient
include GoodClient
def calculate(expr)
super(expr).gsub(/^result: /, '')
end
end
class Client
def initialize(server)
@server = server
# figure out if the server is quirky and mix in the matching module
if @server.calculate("1").include?("result")
extend QuirkyClient
else
extend GoodClient
end
end
end
good_server = GoodServer.new
bad_server = QuirkyServer.new
# we can access both servers using the same interface
client1 = Client.new(good_server)
client2 = Client.new(bad_server)
p client1.is_a? QuirkyClient # => false
p client1.calculate("1 + 2") # => "3"
p client2.is_a? QuirkyClient # => true
p client2.calculate("1 + 2") # => "3"