The Strategy pattern also solves varying algorithms but takes advantage of delegation.
In a nutshell, the varying algorithms are extracted into several classes, which define the so called Strategy objects. Each object has the same named API message receiver, but follows a different strategy how to achieve its goal. Apart from the Strategy objects, there is the particular Context object. It is the context in which the strategy works.
The example Context class:
class Person attr_reader :id, :resource_name attr_accessor :strategy def initialize strategy @id = 1 @resource_name = 'people' @strategy = strategy end def request strategy.query self end endis characterized by some (hard coded for example reasons) values: @id and @resource_name. Furthermore the Person class is a proxy and its objects are persisted somewhere else. They can be requested via different kind of webservices, the different strategies. Adding 2 types of webservices as strategies:
require 'uri' require 'net/http' class WebserviceRequest attr_reader :domain def initialize domain uri = URI.parse domain @http = Net::HTTP.new uri.host, uri.port @http.use_ssl = false end def query context raise "#{self.class}#query not yet implemented." end end class SoapRequest < WebserviceRequest def query context @http.post "/#{context.resource_name}", soap_body(context) { |c| "<m:GetPerson> <m:Id>#{c.id}</m:Id> </m:GetPerson>" }, { 'Host' => @http.address, 'Content-Type' => 'text/xml' } end private def soap_body context, &block "<?xml version='1.0'?> <soap:Envelope xmlns:soap='http://www.w3.org/2001/12/soap-envelope' soap:encodingStyle='http://www.w3.org/2001/12/soap-encoding'> <soap:Body xmlns:m='http://#{@http.address}/#{context.resource_name}'> #{yield(context)} </soap:Body> </soap:Envelope>" end end class RestRequest < WebserviceRequest def query context @http.get "/#{context.resource_name}/#{context.id}" end endThere is a SOAP (ugh!) and a REST strategy. Both expect a domain address to be requested:
Person.new(RestRequest.new 'http://localhost').request => #<Net::HTTPOK 200 OK readbody=true> Person.new(SoapRequest.new 'http://localhost').request => #<Net::HTTPNotFound 404 Not Found readbody=true>Both requests send a HTTP request to localhost on port 80 with some parameters. Apparently the SOAP query was not the right strategy in the Person context.
But the pattern is obvious: the person Context object has a Strategy object and sends the query message to it with itself as the parameter. The method itself is the implementation detail, the varying part.
Strategies can be added as many as required. For example a Strategy to query a database:
class SqlRequest attr_reader :connection def initialize connection @connection = connection end def query context @connection.execute "SELECT * FROM #{context.resource_name} WHERE ID = #{context.id};" end endand
Person.new(SqlRequest.new database_connection).request => SELECT * FROM people WHERE ID = 1;The advantage is the complete class decoupling. It only relies on duck typing. Any class can be a Strategy as long as it has the right interface.
Further articles of interest:
Supported by Ruby 2.1.1
Keine Kommentare:
Kommentar veröffentlichen