Ruby Language
Projektuj wzory i idiomy w Rubim
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.