From: khaines@... Date: 2006-08-22T09:15:49+09:00 Subject: Re: Strategy pattern / interface design arrangement On Tue, 22 Aug 2006, Phrogz wrote: > The short of it is: two different classes of objects are > interchangeable if they both support the same (named) methods that code > using those objects invoke. You don't need static-typing or > compile-type checking. You don't need interfaces. You don't need > contracts. You just need to have the methods available that you are > going to call. An example in real life. IOWA apps receive an Iowa::Request object that encapsulates all of the details about the request to be handled. Because different source environments for the request, such as mod_ruby, FCGI, Webrick, Mongrel, etc... themselves make the request information available in different ways, the Iowa::Request needs to know how to initialize itself if it is given an Apache::Request object from mod_ruby or a Mongrel request object. It's very ugly to have all of that platform specific code piled up in Iowa::Request, though. So, the solution that I settled on is to duck type. Iowa::Request has a method, new_request(). I waffled for a while about just overriding new(), but in the end, decided against it. Anyway, an instance of Iowa::Request is now never created. It simply serves as a superclass to define a common set of accessors and make some common code available to its subclasses, and to act as the factory to create the real request objects. Each of the subclasses implements the same API, but the Iowa::Requests::Apache class takes an Apache::Request to initialize itself, while the Iowa::Requests::WEBrick takes a webrick request object. Each subclass knows how to take care of it's own specific details. The Iowa::Request.new_request() method just provides a call to get a new request. No code except for the code in new_request() cares about the identity of the actual Request objects, and all it does is identify which request class is needed, require the code if it has not already been required, and then create an object of the class, which it returns. Nothing else cares what it is, so long as it has the expected methods. This seems like it might be a reasonable pattern for your credit card processors, and it is easy to implement. You have some method that you call to access or retrieve a processor. Each of the real credit card processor classes implement the same API, so none of the rest of your code has to care at all what they are. They are just a thingy that you feed card and purchase info to; some lights flash, there's some beeping and maybe a gear clack or two, and it spits back a response indicating what happened during the processing. cc_processor = CreditCardProcessor.new_processor(company) buyers.each {|b| b.purchases.each {|p| cc_processor.process(b,p)}} Kirk Haines