Szukaj…


Singel

Ruby Standard Library ma moduł Singleton, który implementuje wzorzec Singleton. Pierwszym krokiem w tworzeniu klasy Singleton jest wymaganie i włączenie modułu Singleton do klasy:

require 'singleton'

class Logger
  include Singleton
end

Jeśli spróbujesz utworzyć tę klasę tak, jak normalną klasę, NoMethodError jest wyjątek NoMethodError . Konstruktor zostaje ustawiony jako prywatny, aby zapobiec przypadkowemu utworzeniu innych instancji:

Logger.new

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

Aby uzyskać dostęp do wystąpienia tej klasy, musimy użyć instance() :

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

#=> true

Przykład rejestratora

require 'singleton'


class Logger
  include Singleton

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

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

Aby użyć obiektu Logger :

Logger.instance.log('message 2')

Bez Singleton to

Powyższe implementacje singletonu można również wykonać bez włączenia modułu Singleton. Można to osiągnąć za pomocą:

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

co jest skrótowym zapisem następujących elementów:

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

Należy jednak pamiętać, że moduł Singleton jest testowany i zoptymalizowany, dlatego jest lepszą opcją do wdrożenia singletona.

Obserwator

Wzorzec obserwatora to wzorzec projektowania oprogramowania, w którym obiekt (zwany subject ) utrzymuje listę swoich osób zależnych (zwanych observers ) i powiadamia ich automatycznie o wszelkich zmianach stanu, zwykle przez wywołanie jednej z ich metod.

Ruby zapewnia prosty mechanizm implementacji wzorca projektowego Observer. Moduł Observable zapewnia logikę powiadamiającą subskrybenta o wszelkich zmianach w obiekcie Observable.

Aby to zadziałało, obserwowalny musi stwierdzić, że się zmienił i powiadomić obserwatorów.

Obserwowane obiekty muszą zaimplementować metodę update() , która będzie wywołaniem zwrotnym dla obserwatora.

Zaimplementujmy mały czat, w którym użytkownicy mogą subskrybować użytkowników, a gdy jeden z nich coś napisze, subskrybenci zostaną powiadomieni.

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

Wytwarzanie następujących danych wyjściowych:

# Computer says: No
# Computer says: No

Uruchomiliśmy metodę write dwa razy w klasie Moderator, powiadamiając subskrybentów, w tym przypadku tylko jednego.

Im więcej subskrybentów dodamy, tym bardziej zmiany będą rozpowszechniane.

Wzór dekoratora

Wzór dekoratora dodaje zachowanie do obiektów bez wpływu na inne obiekty tej samej klasy. Wzór dekoratora jest przydatną alternatywą do tworzenia podklas.

Utwórz moduł dla każdego dekoratora. To podejście jest bardziej elastyczne niż dziedziczenie, ponieważ można mieszać i dopasowywać obowiązki w większej liczbie kombinacji. Ponadto, ponieważ przezroczystość pozwala na rekursywne zagnieżdżanie dekoratorów, pozwala na nieograniczoną liczbę obowiązków.

Załóżmy, że klasa Pizza ma metodę kosztu, która zwraca 300:

class Pizza
  def cost
    300
  end
end

Reprezentuj pizzę z dodaną warstwą pęknięcia sera, a koszt wzrośnie o 50. Najprostszym podejściem jest utworzenie podklasy PizzaWithCheese która zwraca 350 według metody kosztu.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

Następnie musimy przedstawić dużą pizzę, która dodaje 100 do kosztu normalnej pizzy. Możemy to przedstawić za pomocą podklasy pizzy LargePizza.

class LargePizza < Pizza
  def cost
    400
  end
end

Możemy również mieć ExtraLargePizza, która dodaje dodatkowy koszt 15 do naszej LargePizza. Jeśli weźmiemy pod uwagę, że te rodzaje pizzy można podawać z serem, musielibyśmy dodać podklasy LargePizzaWithChese i ExtraLargePizzaWithCheese. W sumie otrzymamy 6 klas.

Aby uprościć to podejście, użyj modułów, aby dynamicznie dodać zachowanie do klasy Pizza:

Moduł + przedłużenie + super dekorator: ->

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

Pełnomocnik

Obiekt proxy jest często używany do zapewnienia strzeżonego dostępu do innego obiektu, którego wewnętrznej logiki biznesowej nie chcemy zanieczyszczać wymogami bezpieczeństwa.

Załóżmy, że chcielibyśmy zagwarantować, że tylko użytkownik określonych uprawnień może uzyskać dostęp do zasobów.

Definicja serwera proxy: (zapewnia, że tylko użytkownicy, którzy faktycznie widzą rezerwacje, będą mogli korzystać z usługi booking_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

Modele i rezerwacja Usługa:

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

Serwis obsługi klienta:

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)

KORZYŚCI
  • unikamy jakichkolwiek zmian w ReservationService przy zmianie ograniczeń dostępu.
  • my nie mieszanie danych biznesowych powiązanych ( date_from , date_to , reservations_count ) z domeny niepowiązanych pojęć (uprawnienia użytkownika) w służbie.
  • Konsument ( StatsService ) jest również wolny od logiki związanej z uprawnieniami

PRZESTROGI
  • Interfejs proxy jest zawsze dokładnie taki sam jak obiekt, który ukrywa, więc użytkownik korzystający z usługi opakowanej przez proxy nie był nawet świadomy obecności proxy.


Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow