From: James French Date: 2012-04-05T23:25:18+09:00 Subject: Re: Design pattern question -----Original Message----- From: Robert Klemme [mailto:lists@ruby-forum.com] Sent: 05 April 2012 14:20 To: ruby-talk ML Subject: Re: Design pattern question James French wrote in post #1055131: > Its pretty simple really. I want to be able to call an API like this: > > SourceControl.checkout > SourceControl.update You better do sc.checkout sc.update i.e. not use a constant but an instance variable. > I want to be able to plugin different source control systems that > conform to the API. Well, in Ruby there are no interfaces so you can, but do not need to use inheritance. > My initial thought was just to go with a simple C++ style > wrapper/delegate approach: > > module SourceControl > > def self.checkout(repository, dest, options={}) > @sourceControl.checkout(repository, dest, options) end > > def self.update(path, options={}) > @sourceControl.update(parth, options) end > > class SourceControlAPI > > def checkout(repository, dest, options={}) > end > > def update(path, options={}) > end > > end > > end > > But I bet there is a nicer way, perhaps without having to duplicate > the API? Especially, what advantages does it have? I'd probably simply do class CVS def checkout(dest, options={}) end def update(path, options={}) end end class SVN def checkout(dest, options={}) end def update(path, options={}) end end class Perforce def checkout(dest, options={}) end def update(path, options={}) end end and then # connection sc = SVN.new("svn-repo.my.network.com", "James", "p@ssword") # repository sc.checkout("repo", "/tmp/work") do |repo| repo.update("foo") File.open(repo.path + "foo", "a") {|io| io.write("a change)} repo.submit("foo") end # auto commit sc.close # needed? You could even make SVN.new accept a block which receives "sc" and cleans up in ensure regardless how the block terminates. http://blog.rubybestpractices.com/posts/rklemme/002_Writing_Block_Methods.html Kind regards robert Using the Jay Fields link and slightly modifying I've got something I'm happy with. Thanks for the help Robert, as always :) module SourceControl def self.init(subject) subject.public_methods(false).each do |meth| (class << self; self; end).class_eval do define_method meth do |*args| subject.send meth, *args end end end end end class SourceControlAPI def checkout(repository, dest, options={}) end def update(path, options={}) end end class SVN < SourceControlAPI def checkout(repository, dest, options={}) puts "Checking out from SVN" end def update(path, options={}) end end SourceControl.init(SVN.new) SourceControl.checkout('foo', 'bar')