La Clase Player: Un Ejemplo de Strategy Pattern

El concepto de jugador es definido mediante una clase abstracta:

module TicTacToe
  class Player
    def initialize( mark )
      @mark = mark # "X" or "O" or " "
    end
····
    attr_reader :mark
····
    def move( board )
      raise NotImplementedError, "Player subclasses must define move()."
    end
····
    def finish( final_board )··
    end
  end
end
de la que heredan los jugadores concretos.

Cada jugador es una clase. Por ejemplo, el jugador humano:

module TicTacToe
  class HumanPlayer < Player
    def move( board )
      print board
      ....
    end
    
    def finish( final_board )
      ....
    end
  end
end
Un par de jugadores mas, uno tonto y otro listo:
module TicTacToe
  class DumbPlayer < Player
    def move( board )
      moves = board.moves
      moves[rand(moves.size)]
    end
  end

  class SmartPlayer < Player
    def move( board )
      ....
    end
  end
end
La Banda de los Cuatro (The Gang of Four o GoF) llama a esta idea de implanta el algoritmo en clases separadas el strategy pattern o patrón estrategia [5], [6]. Esta estrategia puede aplicarse cuando ocurre que en medio de nuestra aplicación tenemos una parte que varía (el tipo de jugador). A veces en esa parte queremos hacer una cosa (usar el jugador SmartPlayer) y a veces otra (el jugador HumanPlayer). Es mas, es seguro que en el futuro se querrá otra cosa (¿que tal un jugador MiniMaxPlayer?).
  1. La idea clave es definir una familia de clases, las strategy o estrategias que hacen de distintas formas la misma cosa: en nuestro ejemplo son los distintos tipos de Player que lo que hacen es decidir la próxima jugada mediante move.
  2. No sólo realizan la misma tarea sino que comparten la misma interfaz definida por la clase Player.
  3. Dado que todos los objetos strategy tiene la misma pinta vistos desde fuera, pueden ser usados por el usuario de la estrategia - Al que la GoF denomina context o contexto - como partes intercambiables. En nuestro caso el context es la clase Game, que usa los Players como objetos intercambiables
  4. Al separar la clase cliente Game - el contexto - de las clases estrategia Players es necesario habilitar un mecanismo de comunicación entre el contexto y la estrategia. En este ejemplo se ha optado por pasar esa información - el tablero - como argumento:
      @board[@x_player.move(@board)] = @x_player.mark # dentro del método play de la clase Game
    
    La estrategia - el jugador @x_player - recibe el tablero @board y retorna su decisión - la jugada @x_player.move(@board) (algo como "b2")
  5. Un ejemplo de uso práctico del patrón Strategy puede verse en el código de rdoc, la herramienta que se usa en Ruby para extraer documentación de los programas: por un lado hay una variación en los parsers que soporta: C, Ruby y FORTRAN. Por otro lado la salida puede hacerse en diversos formatos: XML, distintas versiones de HTML, CHM, ri, etc. En rdoc cada uno de los formatos de salida es manejado mediante una estrategia.

Casiano Rodriguez León 2015-01-07