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
end
is 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
end
There 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
end
and
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