Monday, 22 December 2014

DESIGN PATTERN BY EXAMPLE (IN RUBY) - ABSTRACT FACTORY AND SINGLETON PATTERN

Hi everybody and welcome back.

In my first and second article I have shown you how to refactor existing code to use Template, Strategy and Observer design patterns. 


Today, I am going to show you how to refactor an existing application to use the Abstract Factory and the Singleton pattern.

The application we are going to explore today, is a very simple application. It contains a Client class that uses several Product classes, ProdA, ProdB and ProdC. 

The client class will instantiate some Products and call some methods on these products.


 
class Client   
  def initialize @prod_A = ProductA.new  
  end def foo @prod_A.do_your_stuff  
  my_prod_B = ProductB.new  
  my_prod_B.do_it  
  @prod_C = ProductC.new  
  @prod_C.perform  
  end
end  

class ProductA  
  def do_your_stuff puts "I'm a ProductA, doing my stuff" 
  end
end  

class ProductB  
  def do_it puts "I'm a ProductB, doing it" 
  end
end  
 class ProductC  
  def perform puts "I'm a ProductC, performing" 
  end
end  
 
my_client = Client.new  
my_client.foo  

As you can see, the binding between the Client and Product classes is very tight; The Client class specifically names and creates the Product classes it uses. 

This is one of the scenarios where refactor to Factory Pattern removes the coupling between the Client and the various Product classes. 
Also, we are going to extend this application to create several families of products, therefore the Abstract Design Pattern will better suit this use case.

The abstract factory pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes. In normal usage, the client software creates a concrete implementation of the abstract factory and then uses the generic interface of the factory to create the concrete objects that are part of the theme. The client doesn't know (or care) which concrete objects it gets from each of these internal factories, since it uses only the generic interfaces of their products. This pattern separates the details of implementation of a set of objects from their general usage and relies on object composition, as object creation is implemented in methods exposed in the factory interface.
Thus, we create an Abstract factory class containing three “abstract” methods, one for each product creation that the main application.

 module AbstractProductFactory  
  def create_product_A  
   puts "You should implement this method in the concrete factory"  
  end  
  def create_product_B  
   puts "You should implement this method in the concrete factory
  end  
  def create_product_C  
   puts "You should implement this method in the concrete factory"  
  end  
 end  

The implementation for these methods will raise an exception because it will be responsibility of the specific Factory extending this Abstract factory to create an instance of a specific family of product.

In this same class we defined a concrete ProductFactory extending the AbstractFactory class and overriding the three creation methods for creating products. 
Each of these method will create a different product (productA, productB and ProductC) belonging to the same product family. We will call this family "CoolProduct" to distinguish from the other families.

 class CoolProductFactory  
  include AbstractProductFactory    
  def create_product_A  
     CoolProductA.new  
  end  
  def create_product_B  
     CoolProcductB.new  
  end  
  def create_product_C  
     CoolProductC.new  
  end  
 end  

The Client will change to accept a factory in his constructor. It will never instantiate a product by itself anymore. Whenever it will need a product of a certain family and type, it will ask the factory to provide one. 
In the following piece of code, the client now ask the factory to create three different products. Applying this simple pattern we have decoupled the Client from the different Product. The responsibility of creating different family of product has been moved in the correspondent factory.


 require_relative 'abstract_product_factory.rb'  
 class Client  
   attr_accessor :productFactory  
   def initialize(productFactory)  
     @productFactory = productFactory  
     @prod_A = @productFactory.create_product_A  
     @prod_B = @productFactory.create_product_B  
     @prod_C = @productFactory.create_product_C  
   end  
   def foo  
     @prod_A.do_your_stuff  
     @prod_B.do_it  
     @prod_C.perform  
   end  
 end  
 coolClient = Client.new(CoolProductFactory.new)  
 coolClient.foo  


We have seen that the only responsibility of the factory is to create Product objects on demand. The factory doesn't keep any state. That said, a simple question should come in your mind; Do we need to create a different factory for each client using this application ? The answer is no, we don't need to. 
We need only one instance for each factory and we can reuse it in each clients that needs it.

This is the classic scenario where the Singleton pattern comes in help.

In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. 
There are different possible implementation of the singleton pattern. We decided to use the one best suits this scenario. We defined a class variable called @@instance in the factory itself and we assigned an instance of the factory class to it. We created a method self.instance to return this specific instance (and this instance only) when need by the client. We used the code private_class_method :new to prevent any other classes from instantiate this class again. 
This assured us that we will always have one and only one instance of this Factory. Exactly what we wanted.

  class CoolProductFactory   
  include AbstractProductFactory   
  @@instance = CoolProductFactory.new   
  private_class_method :new   
  def self.instance   
   return @@instance   
  end   
  def create_product_A   
   ProductCoolA.new   
  end   
  def create_product_B   
   ProductCoolB.new   
  end   
  def create_product_C   
   ProductCoolC.new   
  end   
  end   

Now we can construct the client object just retrieving a concrete factory instance calling the method "instance" on the FactoryClass we need, and pass it to the client:


 coolClient = Client.new(CoolProductFactory.instance)  
 coolClient.foo  

What if a new family of products needs to be added to the application ? 

Easy, if you used the Abstract Factory Pattern correctly!

Let's say a new family of products called UncoolProduct comes into play.
We need to add three classes, one for each product, of this family. These products had their own behavior. They all look very similar to this one:


 require_relative 'product_a.rb'  
 class UncoolProductA < ProductA  
  def do_your_stuff  
   puts "I'm a UncoolProductA, doing my stuff"  
  end  
 end  

UncoolProductA extends a class called productA which is extended also by the class CoolProductA. 
The same is valid for UncoolProductB and UncoolProductC.

We added a new concrete factory implementing the Abstract Factory. 
This new concrete factory will have the responsibility of creating products for this new product family. 
Also in this case, this factory will be a singleton.

 class UnCoolProductFactory  
  include AbstractProductFactory  
  @@instance = UnCoolProductFactory.new  
  private_class_method :new  
  def self.instance  
   return @@instance  
  end  
  def create_product_A  
   UncoolProductA.new  
  end  
  def create_product_B  
   ProductUncoolB.new  
  end  
  def create_product_C  
   UncoolProductC.new  
  end  
 end  

A new Client instance, the UncoolClient, will be created requiring an instance of the UncoolProductFactory. The client will now ask its factory to produce three uncool products.


 unCoolClient = Client.new(UnCoolProductFactory.instance)  
 unCoolClient.foo  

Also, we observe that:

  • There will be two concrete Factory classes, cool and uncool. They may or may not have a common superclass.
  • Each Factory class will contain a certain number of methods for creating a certain number of products.
  • For each product (A, B and C) there'll be two concrete subclasses (CoolProduct, UncoolProduct).
  • The main application should create the factory instance that is used to instantiate the Client.

The latest point is probably the most important. It shows that the client can operate on different product families simply swapping in and out the concrete factory used. Basically our client can be a CoolClient or an UnCoolClient simply receiving the correspondent factory from the class that creates it.

Below there is a class diagram for this application:




HAPPY CODING !!
LUCA

Sunday, 14 December 2014

DESIGN PATTERN BY EXAMPLE (IN RUBY) - OBSERVER PATTERN

Hi everybody and welcome back.

In this article, I've been talking about how useful can be the Template and the Strategy patterns. We refactored existing code to use these two patterns and the result was a less coupled and more flexible application.

We designed a solution for a simple problem: model a game where an oracle thinks of a number in a certain range and each participant makes a number of guesses to find out what the number is. After each guess, the oracle tells the participant if the are correct or not, and if not, whether their guess was too big to too small. 

We produced the following class diagram:





Now lets add some complexity to the original problem;

Let's say that there is one more entity interested in knowing when a game finishes with a participant either guessing the number correctly or using up all their attempts.


This entity is the Auditor class. The role of the auditor is to observe all participants and ensure that their win/loss profile is in keeping with what would be expected and highlight any anomalies.

This kind of problem is a good example of a problem that can be solved using the Observer design pattern
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state change, usually by calling one of their methods. It is mainly used to implement distributed event handling systems.

The Observer pattern is also a key part in the familiar model–view–controller (MVC) architectural pattern. The observer pattern is implemented in numerous programming libraries and systems, including almost all GUI toolkits.

We created one module called subject that acts as Observable object.
The participant will include this module so we can avoid the usage of inheritance (this is a specific Ruby construct). 
The Subject module contains all the common methods  to an observable object
  • adding and observer
  • deleting and observer  
  • notify an observer;
 module Subject  
  def initialize  @observers=[]  
  end  
  def add_observer(observer)  
   @observers << observer   
  end  
  def delete_observer(observer)  
   @observers.delete(observer)  
  end  
  def notify_observers    
   @observers.each do |observer|     
   observer.update(self)  
  end   
 end  

Now, every time a participant plays, will also notify its observers as you can see highlighted in red:


 require_relative 'subject.rb'  
 require_relative 'oracle.rb'  
 # Tries to guess the 'secret' number using several different strategies  
 class Participant  
  include Subject  
  attr_accessor :num_attempts  
  attr_reader :playStrategy  
  attr_reader :oracle  
  attr_reader :max_num_attempts  
  attr_reader :name  
  attr_reader :age  
  attr_reader :strategyName  
  def initialize(name,age,oracle,playStrategy, max_num_attempts:10)  
   super()  
   @name=name;  
   @age=age;  
   @strategyName=playStrategy.name;  
   @oracle, @max_num_attempts = oracle, max_num_attempts  
   @num_attempts = 0  
   @playStrategy = playStrategy  
  end  
  def play(lower,upper)  
   result = @playStrategy.play(lower,upper,self)  
   notify_observers()  
   result  
  end  
  def reset  
   @num_attempts = 0  
  end  
 end  


Successively, we create an other class called Auditor. The auditor will be an observer of the class Participant and it will try to understand if the Participant is cheating or not. 
Every time a participant plays, the auditor is notified and it will run some specific business logic for understanding if the participant has cheated. In this case it will print out a string. If the participant is a looser it will not doing anything. We do not want to catch loosing cheaters :)

 class Auditor  
  attr_reader :treshold;  
  def initialize  
   @treshold=3;  
  end  
  def update( changed_participant )  
   if (isAWinner?(changed_participant))  
     analizeWinner(changed_participant)  
   elsif  
     analizeLoser(changed_participant)  
   end  
  end  
  private  
  def isAWinner?( participant )  
   participant.num_attempts <= participant.max_num_attempts  
  end  
  private  
  def analizeLoser (particpant)  
  end  
  private  
  def analizeWinner( participant )  
   if isASuspect?(participant)  
    puts "#{participant.name} is a suspect because he won with only #{participant.num_attempts} attempts playing with #{participant.strategyName}"  
   end  
  end  
  private  
  def isASuspect?(participant)  
   participant.num_attempts < @treshold  
  end  
 end  


Finally, we created a DishonestParticipant class. This Participant is a cheater. He knows the oracle number. We simulated this condition overriding the method play to return always success on the first attempt. This shown that the method play should be secured to be a final method. Nobody should be able to override it because changing its logic, all the rules could be broken.

 require_relative 'participant'  
 class DishonestParticipant < Participant  
  def play(lower,upper)  
   num_attempts=1;  
   notify_observers()  
   :success  
  end  


The main application will have to change to register the auditor observer object with the participant observable object:


 # Evaluate the performance of participants using different guessing strategies  
 require_relative 'auditor.rb'  
 require_relative 'oracle.rb'  
 require_relative 'participant.rb'  
 require_relative 'binary_search_play'  
 require_relative 'linear_play'  
 require_relative 'play_strategy'  
 require_relative 'smart_random_play'  
 require_relative 'random_play'  
 require_relative 'dishonest_participant'   
 NUM_OF_RUNS = 8  
 oracle = Oracle.new  
 # evaluate random strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 auditor = Auditor.new  
 ageAuditor = AgeAuditor.new  
 homer = Participant.new("Homer",40,oracle, RandomPlay.new, max_num_attempts: NUM_OF_RUNS*2)  
 homer.add_observer(auditor)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  homer.reset  
  if homer.play(1,NUM_OF_RUNS)==:success  
   total_num_attempts += homer.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play randomly took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate linear strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 bart = Participant.new("Bart",18,oracle, LinearPlay.new, max_num_attempts:NUM_OF_RUNS*2)  
 bart.add_observer(auditor)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  bart.reset  
  if bart.play(1,0)==:success  
   total_num_attempts += bart.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_linear took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate 'smart random' strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 maggie = Participant.new("Maggie",3,oracle, SmartRandomPlay.new,max_num_attempts:NUM_OF_RUNS*5)  
 maggie.add_observer(auditor)    
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  maggie.reset  
  if maggie.play(1,NUM_OF_RUNS)==:success  
   total_num_attempts += maggie.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_smart_random took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate binary search strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 lisa = Participant.new("Lisa",15,oracle,LinearPlay.new, max_num_attempts:NUM_OF_RUNS*5)  
 lisa.add_observer(auditor)  
 lisa.add_observer(ageAuditor)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  lisa.reset  
  if lisa.play(1,NUM_OF_RUNS)==:success  
   total_num_attempts += lisa.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_binary_search took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # dishonest participant  
 total_num_attempts = 0  
 total_num_failures = 0  
 luca = DishonestParticipant.new("Luca",27,oracle,LinearPlay.new, max_num_attempts:NUM_OF_RUNS*5)  
 luca.add_observer(auditor)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  luca.reset  
  if luca.play(1,NUM_OF_RUNS)==:success  
   total_num_attempts += lisa.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  

Here is a full class diagram for the new application:





Just a quick disclaimer, do not abuse this pattern because it can cause memory leaks, known as the lapsed listener problem, because in basic implementation it requires both explicit registration and explicit deregistration, as in the dispose pattern, because the subject holds strong references to the observers, keeping them alive. This can be prevented by the subject holding weak references to the observers.

Happy coding

Luca

Monday, 8 December 2014

DESING PATTERN BY EXAMPLE (IN RUBY) : TEMPLATE AND STRATEGY

Hi and welcome back everybody!

In this post I will show you an example of refactoring existing code to use the template and strategy design pattern in Ruby!

The application we are going to design today models a game where an oracle thinks of a number in a certain range (0...Max) and each participant to this game makes a number of guess to find out what this number is
After each guess, the oracle tells the participant if he is correct or not!

The starting code for this application is composed only by two classes and a main:


  • The participant class is the first class I am going to show you.  A participant tries to guess the 'secret' number using several different strategies:
 require_relative 'oracle.rb'  
 # Tries to guess the 'secret' number using several different strategies  
 class Participant  
  attr_reader :num_attempts   
  def initialize(oracle, max_num_attempts:10)  
   @oracle, @max_num_attempts = oracle, max_num_attempts  
   @num_attempts = 0  
  end  
  def reset  
   @num_attempts = 0  
  end  
  def play_randomly(lower,upper)  
   num = Kernel.rand(lower..upper)  
   @num_attempts+=1  
   while @oracle.is_this_the_number?(num)!=:correct  
    && (@num_attempts <= @max_num_attempts) do  
    num = Kernel.rand(lower..upper)  
    @num_attempts+=1  
   end  
   if (@num_attempts <= @max_num_attempts)  
    :success  
   else  
    fail  
   end  
  end  
  def play_linear(lower)  
   num=lower  
   @num_attempts+=1  
   while @oracle.is_this_the_number?(num)!=:correct  
    && (@num_attempts <= @max_num_attempts) do  
   end  
    num+=1  
    @num_attempts+=1  
   end  
   if (@num_attempts <= @max_num_attempts)  
    :success  
   else  
    fail  
   end  
  end  
  def play_smart_random(lower, upper)  
   num = Kernel.rand(lower..upper)  
   @num_attempts+=1  
   while ((result = @oracle.is_this_the_number?(num)) != :correct)  
    && (@num_attempts <= @max_num_attempts) do  
   end  
    if result == :less_than  
     upper = num-1  
    elsif result == :greater_than  
     lower = num+1  
    end  
    num = Kernel.rand(lower..upper)  
    @num_attempts+=1  
   end  
   if (@num_attempts <= @max_num_attempts)  
    :success  
   else  
    fail  
   end  
  end  
  def play_binary_search(lower, upper)  
   num = (lower+upper)/2  
   @num_attempts+=1  
   while ((result = @oracle.is_this_the_number?(num)) != :correct)  
    && (@num_attempts <= @max_num_attempts) do  
   end  
    if result == :less_than  
     upper = num-1  
    elsif result == :greater_than  
     lower = num+1  
    end  
    num=(lower+upper)/2  
    @num_attempts+=1  
   end  
   if (@num_attempts <= @max_num_attempts)  
    :success  
   else  
    fail  
   end  
  end  
  private  
  def fail  
   :fail  
  end  
 end  

  • The second class represent the Oracle, which generates the number the Participant is trying to guess:

 class Oracle  
  attr_writer :secret_number  
  def initialize(secret_num:0)  
    @secret_number = secret_num end  
    def is_this_the_number? num  if num == @secret_number  
    :correct  elsif num > @secret_number  
    :less_than  elsif num < @secret_number  
    :greater_than  end end  
 end  

As you can see the class Participant defines several methods to play this game highlighted in red. Each of them uses a different algorithm. The main method, wants to make a comparison between this algorithm and measure their performances:

 # Evaluate the performance of participants using different guessing strategies  
 require_relative 'oracle.rb'  
 require_relative 'participant.rb'  
 NUM_OF_RUNS = 8  
 oracle = Oracle.new  
 # evaluate random strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 homer = Participant.new(oracle, max_num_attempts: NUM_OF_RUNS*2)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  homer.reset  
  if homer.play_randomly(1,NUM_OF_RUNS)==:success  
   total_num_attempts += homer.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play randomly took on average   
 #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate linear strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 bart = Participant.new(oracle, max_num_attempts:NUM_OF_RUNS*2)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  bart.reset  
  if bart.play_linear(1)==:success  
   total_num_attempts += bart.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_linear took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 total_num_attempts = 0  
 total_num_failures = 0  
 maggie = Participant.new(oracle, max_num_attempts:NUM_OF_RUNS*5)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  maggie.reset  
  if maggie.play_smart_random(1,NUM_OF_RUNS)==:success  
   total_num_attempts += maggie.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_smart_random took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 total_num_attempts = 0  
 total_num_failures = 0  
 lisa = Participant.new(oracle, max_num_attempts:NUM_OF_RUNS*5)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  lisa.reset  
  if lisa.play_binary_search(1,NUM_OF_RUNS)==:success  
   total_num_attempts += lisa.num_attempts  
  else  
   total_num_failures += 1  
  end  
 end  
 puts "play_binary_search took on average #{total_num_attempts/(NUM_OF_RUNS-total_num_failures)} attempts to succeed"  


If we run this application we will get results similar to these ones:

play randomly took on average 4 attempts to succeed
play_linear took on average 4 attempts to succeed
play_smart_random took on average 1 attempts to succeed
play_binary_search took on average 4 attempts to succeed

As we said before, the Participant class defines different strategy for playing this game. This is totally fine and it will work producing some results that may change execution by execution; 
However, if we take a minute to reflect, we understand that the only aim of the class Participant, is to play this game! The concept that a Participant can play using different strategies should be extrapolated from the class Participant itself.

The strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime. The strategy pattern:
  • defines a family of algorithms,
  • encapsulates each algorithm, and
  • makes the algorithms interchangeable within that family.

Strategy lets the algorithm vary independently from clients that use it. Strategy is one of the patterns included in the influential book Design Patterns by Gamma et al. that popularised the concept of using patterns in software design. In our specific case, we know that the class Participant know four different strategies to play this game, so we will extract each of this strategy in four different classes.

But wait a second, we can do even better here. If you take even a closer look, you will see that every "play" method as a part in common; This piece of code:


   if (@num_attempts <= @max_num_attempts)  
     :success  else   fail  
   end end  

This means that the method play is divided in two main basic steps: guess the number and discover the outcome !
The template method pattern is a behavioural design pattern that defines the program skeleton of an algorithm in a method, called template method, which defers some steps to subclasses. 
It lets one redefine certain steps of an algorithm without changing the algorithm's structure. 
In our specific case, we are going to create a PlayStrategy class that will define

these two steps:

  1. The playGame step will be delegate to the subclasses; The PlayStrategy doesn't know any strategy to play with, so it will defer the implementation of this step to concrete subclasses that know how to play.
  2. The outcome step will be implemented in this class. All the subclasses can call this method because it contains common code.

The class playStrategy will look like this:


 class PlayStrategy  
  def play(lower,upper,context)  
   playGame(lower,upper,context)  
   outcome(context)  
  end  
  def playGame(lower,upper,context)  
   raise("This method should be implemented in the interface")  
  end  
  def outcome(context)  
   if (context.num_attempts <= context.max_num_attempts)  
     :success  else   :fail  end end  
 end  


As we can see the method play doesn't have a real implementation. It just raise 

an exception if it is invoked on an object instance of this class. This is one of 

the way to simulate abstract method in Ruby.

New concrete subclasses will all look like this one:


 require_relative 'play_strategy'  
 class BinarySearchPlay < PlayStrategy  
  def playGame(lower, upper,context)  
   num = (lower+upper)/2  
   context.num_attempts+=1  
   while ((result = context.oracle.is_this_the_number?(num)) != :correct)  
    && (context.num_attempts <= context.max_num_attempts) do  
    # puts "#{__method__}:I guessed #{num}"  
    if result == :less_than  
     upper = num-1  
    elsif result == :greater_than  
     lower = num+1  
    end  
    num=(lower+upper)/2  
    context.num_attempts+=1  
   end  
  end  
 end  

They will define the specific algorithm they implement.

The participant class is much simpler now because we have extract the knowledge of the different play strategies:

 require_relative 'oracle.rb'  
 class Participant  
  attr_accessor :num_attempts  
  attr_reader :playStrategy  
  attr_reader :oracle  
  attr_reader :max_num_attempts  
  def initialize(oracle,playStrategy, max_num_attempts:10)  
   @oracle, @max_num_attempts = oracle, max_num_attempts  
   @num_attempts = 0  
   @playStrategy = playStrategy  
  end  
  def play(lower,upper)  
   @playStrategy.play(lower,upper,self)  
  end  
  def reset  
   @num_attempts = 0  
  end  
  private  
  def fail  
   :fail  
  end  
 end  

Participant doesn't know how to play, but the instance variable @playStrategy will help it on this matter.

Finally the main:


 # Evaluate the performance of participants using different guessing strategies  
 require_relative 'oracle.rb'  
 require_relative 'participant.rb'  
 require_relative 'binary_search_play'  
 require_relative 'linear_play'  
 require_relative 'play_strategy'  
 require_relative 'smart_random_play'  
 require_relative 'random_play'  
 NUM_OF_RUNS = 8  
 oracle = Oracle.new  
 # evaluate random strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 homer = Participant.new(oracle, RandomPlay.new, max_num_attempts: NUM_OF_RUNS*2,)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  homer.reset  
  if homer.play(1,NUM_OF_RUNS)==:success  
   # puts "play randomly found #{i} in #{homer.num_attempts} attempts"  
   total_num_attempts += homer.num_attempts  
  else  
   # puts "play randomly failed to find #{i} after #{homer.num_attempts} attempts"  
   total_num_failures += 1  
  end  
 end  
 puts "play randomly took on average #{total_num_attempts/  
      (NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate linear strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 bart = Participant.new(oracle, LinearPlay.new, max_num_attempts:NUM_OF_RUNS*2)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  bart.reset  
  if bart.play(1,0)==:success  
   # puts "play_linear found #{i} in #{bart.num_attempts} attempts"  
   total_num_attempts += bart.num_attempts  
  else  
   # puts "play_linear failed to find #{i} after #{bart.num_attempts} attempts"  
   total_num_failures += 1  
  end  
 end  
 puts "play_linear took on average #{total_num_attempts/  
      (NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate 'smart random' strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 maggie = Participant.new(oracle, SmartRandomPlay.new,max_num_attempts:NUM_OF_RUNS*5)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  maggie.reset  
  if maggie.play(1,NUM_OF_RUNS)==:success  
   # puts "play_smart_random found #{i} in #{maggie.num_attempts} attempts"  
   total_num_attempts += maggie.num_attempts  
  else  
   # puts "play_smart_random failed to find #{i} after #{maggie.num_attempts} attempts"  
   total_num_failures += 1  
  end  
 end  
 puts "play_smart_random took on average #{total_num_attempts/  
      (NUM_OF_RUNS-total_num_failures)} attempts to succeed"  
 # evaluate binary search strategy  
 total_num_attempts = 0  
 total_num_failures = 0  
 lisa = Participant.new(oracle,LinearPlay.new, max_num_attempts:NUM_OF_RUNS*5)  
 1.upto(NUM_OF_RUNS) do |i|  
  oracle.secret_number = i  
  lisa.reset  
  if lisa.play(1,NUM_OF_RUNS)==:success  
   # puts "play_binary_search found #{i} in #{lisa.num_attempts} attempts"  
   total_num_attempts += lisa.num_attempts  
  else  
   # puts "play_binary_search failed to find #{i} after #{lisa.num_attempts} attempts"  
   total_num_failures += 1  
  end  
 end  
 puts "play_binary_search took on average #{total_num_attempts/  
      (NUM_OF_RUNS-total_num_failures)} attempts to succeed"  

The most important difference to notice, highlighted in red, is that when we create a Participant, we also pass the Strategy as parameter to the constructor. In this way, we decoupled the PlayStrategy from the Participant class, and we will have to call simply the "play" method on the Participant because the strategy as been "injected" by the Client in the Participant constructor.


This is a class diagram of the new application:













HAPPY CODING!!

Luca

Google+ Followers