Ruby Language
디자인 패턴과 관용구 루비
수색…
하나씩 일어나는 것
Ruby 표준 라이브러리에는 싱글 톤 패턴을 구현하는 싱글 톤 모듈이 있습니다. Singleton 클래스를 생성하는 첫 번째 단계는 클래스에 Singleton
모듈을 필요로하고 포함시키는 것입니다.
require 'singleton'
class Logger
include Singleton
end
일반 클래스와 NoMethodError
클래스를 인스턴스화하려고하면 NoMethodError
예외가 발생합니다. 생성자는 다른 인스턴스가 실수로 생성되는 것을 방지하기 위해 비공개로 만들어집니다.
Logger.new
#=> NoMethodError: private method `new' called for AppConfig:Class
이 클래스의 인스턴스에 액세스하려면 instance()
를 사용해야합니다.
first, second = Logger.instance, Logger.instance
first == second
#=> true
로거 예제
require 'singleton'
class Logger
include Singleton
def initialize
@log = File.open("log.txt", "a")
end
def log(msg)
@log.puts(msg)
end
end
Logger
객체를 사용하려면 다음을 수행하십시오.
Logger.instance.log('message 2')
싱글 톤이 없으면 다음을 포함합니다.
위의 싱글 톤 구현은 싱글 톤 모듈을 포함하지 않고도 수행 할 수 있습니다. 이것은 다음과 같이 수행 할 수 있습니다.
class Logger
def self.instance
@instance ||= new
end
end
이것은 다음에 대한 속기 표기법입니다.
class Logger
def self.instance
@instance = @instance || Logger.new
end
end
그러나 Singleton 모듈은 테스트되고 최적화되므로 Singleton을 구현하는 것이 더 좋은 옵션이라는 것을 명심하십시오.
관찰자
관찰자 패턴은 객체 ( subject
)가 종속체 ( observers
라고 함)의 목록을 유지하고 일반적으로 해당 메소드 중 하나를 호출하여 상태 변경을 자동으로 통지하는 소프트웨어 설계 패턴입니다.
Ruby는 Observer 디자인 패턴을 구현하는 간단한 메커니즘을 제공합니다. Observable
모듈은 Observable 객체의 변경 사항을 구독자에게 알리는 로직을 제공합니다.
이를 위해서는 관찰자가 관찰자에게 변경 사실을 주장하고 관찰자에게 알리십시오.
관찰하는 객체는 Observer의 콜백이 될 update()
메소드를 구현해야합니다.
사용자가 사용자를 구독 할 수있는 작은 채팅을 구현하고 그 중 하나가 무언가를 쓸 때 구독자에게 알림을 보냅니다.
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
다음 출력을 생성합니다.
# Computer says: No
# Computer says: No
Moderator 클래스에서 메서드 write
을 두 번 실행하여 구독자에게이 경우 하나만 알립니다.
우리가 더 많은 가입자를 추가할수록 변경 사항이 전파됩니다.
장식 패턴
데코레이터 패턴은 동일한 클래스의 다른 객체에 영향을주지 않고 객체에 비헤이비어를 추가합니다. 데코레이터 패턴은 하위 클래스를 만드는 데 유용한 대안입니다.
각 데코레이터에 대한 모듈을 만듭니다. 이 방법은 더 많은 조합으로 책임을 혼합하고 조화시킬 수 있으므로 상속보다 유연합니다. 또한 투명도를 사용하면 데코레이터를 반복적으로 중첩시킬 수 있으므로 무제한의 책임을 수행 할 수 있습니다.
피자 클래스에 300을 반환하는 비용 메서드가 있다고 가정합니다.
class Pizza
def cost
300
end
end
치즈 버스트가 추가 된 피자를 PizzaWithCheese
비용은 50 씩 증가합니다. 가장 간단한 방법은 비용 방법에서 350을 반환하는 PizzaWithCheese
하위 클래스를 만드는 것입니다.
class PizzaWithCheese < Pizza
def cost
350
end
end
다음으로, 일반 피자의 가격에 100을 더한 큰 피자를 나타낼 필요가 있습니다. 피자의 LargePizza 하위 클래스를 사용하여 표현할 수 있습니다.
class LargePizza < Pizza
def cost
400
end
end
우리는 또한 LargePizza에 15의 추가 비용을 추가하는 ExtraLargePizza를 가질 수 있습니다. 이러한 피자 유형이 치즈와 함께 제공 될 수 있다고 생각한다면 LargePizzaWithChese 및 ExtraLargePizzaWithCheese 하위 클래스를 추가해야합니다. 총 6 개의 클래스가 있습니다.
접근 방식을 단순화하기 위해 모듈을 사용하여 피자 클래스에 동작을 동적으로 추가합니다.
모듈 + 확장 + 수퍼 데코레이터 : ->
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
대리
프록시 객체는 종종 다른 객체에 대한 보안 된 액세스를 보장하는 데 사용됩니다. 내부 비즈니스 로직은 안전 요구 사항으로 인해 오염되기를 원하지 않습니다.
특정 권한을 가진 사용자 만 리소스에 액세스 할 수 있음을 보장하고자한다고 가정합니다.
프록시 정의 : (실제로 예약을 볼 수있는 사용자 만이 consumer 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
모델 및 예약 서비스 :
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
소비자 서비스 :
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
테스트:
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)
은혜
- 액세스 제한이 변경되면
ReservationService
변경 사항을 피할 것입니다. - 우리는 비즈니스 관련 데이터 (
date_from
,date_to
,reservations_count
)를 서비스에서 도메인과 무관 한 개념 (사용자 권한)과 혼합하지 않습니다. - Consumer (
StatsService
)는 사용 권한 관련 논리가 없습니다.
주의 사항
- 프록시 인터페이스는 항상 숨겨진 개체와 동일하므로 프록시로 래핑 된 서비스를 사용하는 사용자는 프록시 존재를 인식하지 못합니다.