Ricerca…


Singleton

Ruby Standard Library ha un modulo Singleton che implementa il pattern Singleton. Il primo passo nella creazione di una classe Singleton è richiedere e includere il modulo Singleton in una classe:

require 'singleton'

class Logger
  include Singleton
end

Se provi a creare un'istanza di questa classe come faresti normalmente con una classe regolare, viene sollevata un'eccezione NoMethodError . Il costruttore viene reso privato per impedire che altre istanze vengano create accidentalmente:

Logger.new

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

Per accedere all'istanza di questa classe, è necessario utilizzare l' instance() :

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

#=> true

Esempio di logger

require 'singleton'


class Logger
  include Singleton

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

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

Per utilizzare l'oggetto Logger :

Logger.instance.log('message 2')

Senza Singleton include

Le suddette implementazioni singleton possono anche essere eseguite senza l'inclusione del modulo Singleton. Questo può essere ottenuto con il seguente:

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

che è una notazione abbreviata per quanto segue:

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

Tuttavia, tieni presente che il modulo Singleton è testato e ottimizzato, pertanto rappresenta l'opzione migliore per implementare il tuo singleton con.

Osservatore

Il modello di osservatore è un modello di progettazione software in cui un oggetto (chiamato subject ) mantiene un elenco dei suoi dipendenti (chiamati observers ) e li notifica automaticamente di eventuali cambiamenti di stato, di solito chiamando uno dei loro metodi.

Ruby fornisce un semplice meccanismo per implementare il modello di progettazione di Observer. Il modulo Observable fornisce la logica per notificare all'abbonato eventuali modifiche nell'oggetto Observable.

Perché funzioni, l'osservabile deve affermare che è cambiato e avvisare gli osservatori.

Gli oggetti che osservano devono implementare un metodo update() , che sarà il callback per l'Observer.

Implementiamo una piccola chat, in cui gli utenti possono iscriversi agli utenti e quando uno di loro scrive qualcosa, gli utenti ricevono una notifica.

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

Producendo il seguente output:

# Computer says: No
# Computer says: No

Abbiamo attivato il metodo di write due volte nella classe Moderatore, notificando i suoi iscritti, in questo caso solo uno.

Più iscritti aggiungiamo, più le modifiche si propagheranno.

Decoratore

Il pattern Decorator aggiunge un comportamento agli oggetti senza influenzare altri oggetti della stessa classe. Il pattern decoratore è un'alternativa utile alla creazione di sottoclassi.

Crea un modulo per ogni decoratore. Questo approccio è più flessibile dell'ereditarietà perché puoi combinare le responsabilità in più combinazioni. Inoltre, poiché la trasparenza consente ai decoratori di essere annidati in modo ricorsivo, consente un numero illimitato di responsabilità.

Supponiamo che la classe Pizza abbia un metodo di costo che restituisce 300:

class Pizza
  def cost
    300
  end
end

Rappresenta la pizza con uno strato aggiuntivo di scoppio di formaggio e il costo aumenta di 50. L'approccio più semplice consiste nel creare una sottoclasse di PizzaWithCheese che restituisca 350 nel metodo di costo.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

Successivamente, dobbiamo rappresentare una pizza grande che aggiunge 100 al costo di una pizza normale. Possiamo rappresentarlo utilizzando una sottoclasse LargePizza di Pizza.

class LargePizza < Pizza
  def cost
    400
  end
end

Potremmo anche avere un ExtraLargePizza che aggiunge un ulteriore costo di 15 al nostro LargePizza. Se dovessimo considerare che questi tipi di pizza potevano essere serviti con formaggio, dovremmo aggiungere le sottoclassi LargePizzaWithChese e ExtraLargePizzaWithCheese. Finiremo con un totale di 6 classi.

Per semplificare l'approccio, utilizzare i moduli per aggiungere dinamicamente il comportamento alla classe Pizza:

Modulo + estensione + super decoratore: ->

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

delega

L'oggetto proxy viene spesso utilizzato per garantire l'accesso protetto a un altro oggetto, che la logica aziendale interna non vogliamo inquinare con i requisiti di sicurezza.

Supponiamo di voler garantire che solo l'utente con autorizzazioni specifiche possa accedere alla risorsa.

Definizione del proxy: (garantisce che solo gli utenti che effettivamente possono vedere le prenotazioni saranno in grado di prenotare il servizio di prenotazione)

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

Modelli e ReservationService:

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

Servizio al consumo:

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

Test:

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)

BENEFICI
  • stiamo evitando qualsiasi modifica in ReservationService quando vengono modificate le restrizioni di accesso.
  • non stiamo mescolando i dati relativi al business ( date_from , date_to , reservations_count ) con concetti non collegati al dominio (permessi dell'utente) in servizio.
  • Anche Consumer ( StatsService ) è libero dalla logica relativa alle autorizzazioni

CAVEATS
  • L'interfaccia proxy è sempre esattamente uguale all'oggetto che nasconde, quindi l'utente che utilizza il servizio fornito dal proxy non era nemmeno a conoscenza della presenza del proxy.


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow