Buscar..


Semifallo

Ruby Standard Library tiene un módulo Singleton que implementa el patrón Singleton. El primer paso para crear una clase Singleton es requerir e incluir el módulo Singleton en una clase:

require 'singleton'

class Logger
  include Singleton
end

Si intenta crear una instancia de esta clase como lo haría normalmente en una clase normal, se NoMethodError una excepción NoMethodError . El constructor se hace privado para evitar que otras instancias se creen accidentalmente:

Logger.new

#=> NoMethodError: private method `new' called for AppConfig:Class    

Para acceder a la instancia de esta clase, necesitamos usar la instance() :

first, second = Logger.instance, Logger.instance
first == second

#=> true

Ejemplo de registrador

require 'singleton'


class Logger
  include Singleton

  def initialize
    @log = File.open("log.txt", "a")
  end

  def log(msg)
    @log.puts(msg)
  end
end

Para usar el objeto Logger :

Logger.instance.log('message 2')

Sin Singleton incluye

Las implementaciones singleton anteriores también se pueden realizar sin la inclusión del módulo Singleton. Esto se puede lograr con lo siguiente:

class Logger
  def self.instance
    @instance ||= new
  end
end

que es una notación abreviada para lo siguiente:

class Logger
  def self.instance
    @instance = @instance || Logger.new
  end
end

Sin embargo, tenga en cuenta que el módulo Singleton está probado y optimizado, por lo que es la mejor opción para implementar su Singleton.

Observador

El patrón de observador es un patrón de diseño de software en el que un objeto ( subject llamado) mantiene una lista de sus dependientes (llamados observers ) y les notifica automáticamente cualquier cambio de estado, generalmente llamando a uno de sus métodos.

Ruby proporciona un mecanismo simple para implementar el patrón de diseño Observer. El módulo Observable proporciona la lógica para notificar al suscriptor de cualquier cambio en el objeto Observable.

Para que esto funcione, el observable debe afirmar que ha cambiado y notificar a los observadores.

Los objetos que observan deben implementar un método update() , que será la devolución de llamada para el observador.

Implementemos un pequeño chat, donde los usuarios pueden suscribirse a los usuarios y cuando uno de ellos escribe algo, los suscriptores reciben una notificación.

require "observer"

class Moderator
  include Observable

  def initialize(name)
    @name = name
  end

  def write
    message = "Computer says: No"
    changed
    notify_observers(message)
  end
end

class Warner
  def initialize(moderator, limit)
    @limit = limit
    moderator.add_observer(self)
  end
end

class Subscriber < Warner
  def update(message)
    puts "#{message}"
  end
end

moderator = Moderator.new("Rupert")
Subscriber.new(moderator, 1)
moderator.write
moderator.write

Produciendo la siguiente salida:

# Computer says: No
# Computer says: No

Hemos activado el método de write en la clase Moderador dos veces, notificando a sus suscriptores, en este caso solo uno.

Cuantos más suscriptores agreguemos, más se propagarán los cambios.

Patrón decorador

El patrón de decorador agrega comportamiento a los objetos sin afectar a otros objetos de la misma clase. El patrón decorador es una alternativa útil para crear subclases.

Crea un módulo para cada decorador. Este enfoque es más flexible que la herencia porque puede combinar responsabilidades en más combinaciones. Además, debido a que la transparencia permite que los decoradores se aniden de forma recursiva, permite un número ilimitado de responsabilidades.

Supongamos que la clase de pizza tiene un método de costo que devuelve 300:

class Pizza
  def cost
    300
  end
end

Represente la pizza con una capa adicional de ráfaga de queso y el costo aumenta en 50. El enfoque más simple es crear una subclase de PizzaWithCheese que devuelva 350 en el método de costo.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

A continuación, debemos representar una pizza grande que agregue 100 al costo de una pizza normal. Podemos representarlo usando una subclase LargePizza de Pizza.

class LargePizza < Pizza
  def cost
    400
  end
end

También podríamos tener un ExtraLargePizza que agrega un costo adicional de 15 a nuestro LargePizza. Si tuviéramos que considerar que estos tipos de pizza podrían servirse con queso, tendríamos que agregar LargePizzaWithChese y ExtraLargePizzaWithCheese subclasses. Terminamos con un total de 6 clases.

Para simplificar el enfoque, use módulos para agregar dinámicamente el comportamiento a la clase Pizza:

Módulo + extender + super decorador: ->

class Pizza
  def cost
    300
  end
end

module CheesePizza
  def cost
    super + 50
  end
end

module LargePizza
  def cost
    super + 100
  end
end

pizza = Pizza.new         #=> cost = 300
pizza.extend(CheesePizza) #=> cost = 350
pizza.extend(LargePizza)  #=> cost = 450
pizza.cost                #=> cost = 450

Apoderado

El objeto proxy se usa a menudo para garantizar el acceso protegido a otro objeto, cuya lógica empresarial interna no queremos contaminar con los requisitos de seguridad.

Supongamos que queremos garantizar que solo los usuarios con permisos específicos puedan acceder a los recursos.

Definición de proxy: (garantiza que solo los usuarios que realmente puedan ver las reservas puedan reservar el servicio al consumidor)

class Proxy
  def initialize(current_user, reservation_service)
    @current_user = current_user
    @reservation_service = reservation_service
  end

  def highest_total_price_reservations(date_from, date_to, reservations_count)
    if @current_user.can_see_reservations?
      @reservation_service.highest_total_price_reservations(
        date_from, 
        date_to, 
        reservations_count
      )
    else
      []
    end
  end 
end

Modelos y servicio de reserva:

class Reservation
  attr_reader :total_price, :date

  def initialize(date, total_price)
    @date = date
    @total_price = total_price
  end
end

class ReservationService
  def highest_total_price_reservations(date_from, date_to, reservations_count)
    # normally it would be read from database/external service
    reservations = [
      Reservation.new(Date.new(2014, 5, 15), 100),
      Reservation.new(Date.new(2017, 5, 15), 10),          
      Reservation.new(Date.new(2017, 1, 15), 50)
    ]

    filtered_reservations = reservations.select do |reservation|
      reservation.date.between?(date_from, date_to) 
    end

    filtered_reservations.take(reservations_count)
  end
end        

class User
  attr_reader :name

  def initialize(can_see_reservations, name)
    @can_see_reservations = can_see_reservations
    @name = name
  end

  def can_see_reservations?
    @can_see_reservations
  end
end

Servicio al consumidor:

class StatsService
  def initialize(reservation_service)
    @reservation_service = reservation_service
  end

  def year_top_100_reservations_average_total_price(year)
    reservations = @reservation_service.highest_total_price_reservations(
      Date.new(year, 1, 1),
      Date.new(year, 12, 31),
      100
    )

    if reservations.length > 0
      sum = reservations.reduce(0) do |memo, reservation| 
        memo + reservation.total_price
      end

      sum / reservations.length
    else
      0
    end
  end
end

Prueba:

def test(user, year)
  reservations_service = Proxy.new(user, ReservationService.new)
  stats_service = StatsService.new(reservations_service)
  average_price = stats_service.year_top_100_reservations_average_total_price(year)
  puts "#{user.name} will see: #{average_price}"
end

test(User.new(true, "John the Admin"), 2017)
test(User.new(false, "Guest"),         2017)

BENEFICIOS
  • estamos evitando cualquier cambio en ReservationService cuando se cambian las restricciones de acceso.
  • no estamos mezclando datos relacionados con la empresa ( date_from , date_to , reservations_count ) con conceptos de dominio no relacionados (permisos de usuario) en servicio.
  • El consumidor ( StatsService ) también está libre de la lógica relacionada con los permisos

CUEVAS
  • La interfaz de proxy es siempre exactamente la misma que el objeto que oculta, por lo que el usuario que consume el servicio envuelto por el proxy ni siquiera estaba al tanto de la presencia del proxy.


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow