Recherche…


Singleton

Ruby Standard Library possède un module Singleton qui implémente le modèle Singleton. La première étape de la création d'une classe Singleton consiste à exiger et à inclure le module Singleton dans une classe:

require 'singleton'

class Logger
  include Singleton
end

Si vous essayez d'instancier cette classe comme vous le feriez normalement avec une classe normale, une exception NoMethodError est NoMethodError . Le constructeur est rendu privé pour empêcher que d'autres instances soient créées accidentellement:

Logger.new

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

Pour accéder à l'instance de cette classe, nous devons utiliser l' instance() :

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

#=> true

Exemple de Logger

require 'singleton'


class Logger
  include Singleton

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

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

Pour utiliser l'objet Logger :

Logger.instance.log('message 2')

Sans Singleton inclure

Les implémentations singleton ci-dessus peuvent également être effectuées sans l'inclusion du module Singleton. Cela peut être réalisé avec les éléments suivants:

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

qui est une notation abrégée pour les éléments suivants:

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

Cependant, gardez à l'esprit que le module Singleton est testé et optimisé, ce qui en fait la meilleure option pour implémenter votre singleton.

Observateur

Le modèle d'observateur est un modèle de conception logicielle dans lequel un objet (appelé subject ) conserve une liste de ses dépendants (appelés observers ) et les avertit automatiquement de tout changement d'état, généralement en appelant l'une de ses méthodes.

Ruby fournit un mécanisme simple pour implémenter le modèle de conception Observer. Le module Observable fournit la logique pour informer l'abonné de tout changement dans l'objet Observable.

Pour que cela fonctionne, l'observable doit affirmer qu'il a changé et en informer les observateurs.

Les objets observant doivent implémenter une méthode update() , qui sera le rappel de l'observateur.

Implémentons une petite discussion, où les utilisateurs peuvent s'abonner aux utilisateurs et quand l'un d'eux écrit quelque chose, les abonnés sont avertis.

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

Produire la sortie suivante:

# Computer says: No
# Computer says: No

Nous avons déclenché la méthode write à la classe Moderator deux fois, en avertissant ses abonnés, dans ce cas-ci un seul.

Plus nous ajoutons d'abonnés, plus les changements se propageront.

Motif Décorateur

Le motif de décorateur ajoute un comportement aux objets sans affecter les autres objets de la même classe. Le motif de décorateur est une alternative utile à la création de sous-classes.

Créez un module pour chaque décorateur. Cette approche est plus flexible que l'héritage, car vous pouvez combiner et associer des responsabilités dans davantage de combinaisons. De plus, la transparence permet aux décorateurs d’être imbriqués récursivement, ce qui permet un nombre illimité de responsabilités.

Supposons que la classe Pizza a une méthode de coût qui renvoie 300:

class Pizza
  def cost
    300
  end
end

Représenter la pizza avec une couche supplémentaire de fromage et le coût augmente de 50. L'approche la plus simple consiste à créer une sous-classe PizzaWithCheese qui renvoie 350 dans la méthode du coût.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

Ensuite, nous devons représenter une grande pizza qui ajoute 100 au coût d'une pizza normale. Nous pouvons représenter cela en utilisant une sous-classe LargePizza de Pizza.

class LargePizza < Pizza
  def cost
    400
  end
end

Nous pourrions également avoir un ExtraLargePizza qui ajoute un coût supplémentaire de 15 à notre LargePizza. Si nous considérions que ces types de pizza pouvaient être servis avec du fromage, nous devions ajouter les sous-classes LargePizzaWithChese et ExtraLargePizzaWithCheese.

Pour simplifier l'approche, utilisez des modules pour ajouter dynamiquement un comportement à la classe Pizza:

Module + extend + super décorateur: ->

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

Procuration

L'objet proxy est souvent utilisé pour garantir un accès sécurisé à un autre objet, quelle logique métier interne nous ne voulons pas polluer avec les exigences de sécurité.

Supposons que nous souhaitons garantir que seul l'utilisateur des autorisations spécifiques puisse accéder aux ressources.

Définition du proxy: (il garantit que seuls les utilisateurs capables de voir les réservations pourront utiliser le service reservation_service)

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

Modèles et service de réservation:

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

Service client:

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

Tester:

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)

AVANTAGES
  • nous évitons tout changement dans ReservationService lorsque les restrictions d'accès sont modifiées.
  • Nous ne date_from pas les données liées à l'entreprise ( date_from , date_to , reservations_count ) avec les concepts non liés au domaine (autorisations utilisateur) en service.
  • Consumer ( StatsService ) est également exempt de logique liée aux autorisations

CAVEATS
  • L'interface proxy est toujours exactement identique à l'objet qu'elle cache, de sorte que l'utilisateur qui consomme le service encapsulé par un proxy n'était même pas au courant de la présence de proxy.


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow