Ruby Language
Design Patterns und Idiome in Ruby
Suche…
Singleton
Ruby Standard Library verfügt über ein Singleton-Modul, das das Singleton-Muster implementiert. Der erste Schritt beim Erstellen einer Singleton-Klasse besteht darin, das Singleton
Modul in eine Klasse aufzunehmen und aufzunehmen:
require 'singleton'
class Logger
include Singleton
end
Wenn Sie versuchen, diese Klasse wie eine normale Klasse zu instanziieren, wird eine NoMethodError
Ausnahme NoMethodError
. Der Konstruktor wird als privat festgelegt, um zu verhindern, dass andere Instanzen versehentlich erstellt werden:
Logger.new
#=> NoMethodError: private method `new' called for AppConfig:Class
Um auf die Instanz dieser Klasse zuzugreifen, müssen wir die instance()
:
first, second = Logger.instance, Logger.instance
first == second
#=> true
Logger-Beispiel
require 'singleton'
class Logger
include Singleton
def initialize
@log = File.open("log.txt", "a")
end
def log(msg)
@log.puts(msg)
end
end
Um das Logger
Objekt zu verwenden:
Logger.instance.log('message 2')
Ohne Singleton gehören dazu
Die obigen Singleton-Implementierungen können auch ohne das Singleton-Modul durchgeführt werden. Dies kann mit folgendem erreicht werden:
class Logger
def self.instance
@instance ||= new
end
end
Dies ist eine Kurzschreibweise für Folgendes:
class Logger
def self.instance
@instance = @instance || Logger.new
end
end
Denken Sie jedoch daran, dass das Singleton-Modul getestet und optimiert wurde. Daher ist es die bessere Option, Ihr Singleton mit zu implementieren.
Beobachter
Das Beobachtermuster ist ein Software-Entwurfsmuster, in dem ein Objekt ( subject
) eine Liste seiner abhängigen Personen (sogenannte observers
) verwaltet und diese automatisch über alle Zustandsänderungen benachrichtigt, normalerweise durch Aufrufen einer ihrer Methoden.
Ruby bietet einen einfachen Mechanismus zum Implementieren des Observer-Entwurfsmusters. Das Modul Observable
bietet die Logik, um den Teilnehmer über Änderungen am Observable-Objekt zu informieren.
Damit dies funktioniert, muss das Beobachtbare behaupten, dass es sich geändert hat, und die Beobachter benachrichtigen.
Beobachtende Objekte müssen eine update()
-Methode implementieren, die der Rückruf für den Observer ist.
Lassen Sie uns einen kleinen Chat implementieren, bei dem Benutzer Benutzer abonnieren können und wenn einer von ihnen etwas schreibt, werden die Abonnenten benachrichtigt.
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
Ausgabe der folgenden Ausgabe:
# Computer says: No
# Computer says: No
Wir haben die Methode write
in der Moderator-Klasse zweimal ausgelöst und ihre Abonnenten benachrichtigt, in diesem Fall nur einen.
Je mehr Abonnenten wir hinzufügen, desto mehr verbreiten sich die Änderungen.
Dekorateur-Muster
Das Dekorationsmuster fügt Objekten ein Verhalten hinzu, ohne dass andere Objekte derselben Klasse betroffen sind. Das Dekorationsmuster ist eine nützliche Alternative zum Erstellen von Unterklassen.
Erstellen Sie für jeden Dekorateur ein Modul. Dieser Ansatz ist flexibler als die Vererbung, da Sie Verantwortlichkeiten in mehr Kombinationen kombinieren können. Da die Transparenz das rekursive Verschachteln von Dekorateuren zulässt, ist außerdem eine unbegrenzte Anzahl von Verantwortlichkeiten möglich.
Angenommen, die Pizzaklasse hat eine Kostenmethode, die 300 zurückgibt:
class Pizza
def cost
300
end
end
Stellen Sie Pizza mit einer zusätzlichen Schicht Käse dar und die Kosten PizzaWithCheese
um 50. Die einfachste Methode besteht darin, eine PizzaWithCheese
Unterklasse zu erstellen, die in der PizzaWithCheese
350 PizzaWithCheese
.
class PizzaWithCheese < Pizza
def cost
350
end
end
Als nächstes müssen wir eine große Pizza darstellen, die die Kosten einer normalen Pizza um 100 erhöht. Wir können dies mit einer LargePizza-Unterklasse Pizza darstellen.
class LargePizza < Pizza
def cost
400
end
end
Wir könnten auch eine ExtraLargePizza haben, was unsere LargePizza um weitere 15 erhöht. Wenn wir bedenken würden, dass diese Pizzasorten mit Käse serviert werden könnten, müssten wir LargePizzaWithChese und ExtraLargePizzaWithCheese-Unterklassen hinzufügen.
Um den Ansatz zu vereinfachen, verwenden Sie Module, um der Pizza-Klasse dynamisch Verhalten hinzuzufügen:
Modul + verlängern + Superdekorateur: ->
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
Proxy
Proxy-Objekte werden häufig verwendet, um den geschützten Zugriff auf ein anderes Objekt sicherzustellen. Diese interne Geschäftslogik möchten wir nicht mit Sicherheitsanforderungen belasten.
Angenommen, wir möchten garantieren, dass nur Benutzer bestimmter Berechtigungen auf die Ressource zugreifen können.
Proxy-Definition: (Es wird sichergestellt, dass nur Benutzer, die tatsächlich Reservierungen sehen können, denesen reservation_service verwenden können.)
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
Modelle und Reservierungsservice:
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
Kundenservice:
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
Prüfung:
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)
LEISTUNGEN
- wir vermeiden Änderungen in
ReservationService
wenn Zugriffsbeschränkungen geändert werden - Wir mischen keine geschäftsbezogenen Daten (
date_from
,date_to
,reservations_count
) mitdate_to
Konzepten (Benutzerberechtigungen) im Service. - Consumer (
StatsService
) ist ebenfalls frei von Logik für Berechtigungen
CAVEATS
- Die Proxy-Schnittstelle entspricht immer genau dem Objekt, das sie verbirgt, so dass der Benutzer, der den von Proxy umschlossenen Dienst in Anspruch nimmt, nicht einmal die Anwesenheit eines Proxy erkannt hat.