Поиск…


одиночка

Стандартная библиотека Ruby имеет модуль Singleton, который реализует шаблон Singleton. Первым шагом в создании класса 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')

Без Singleton включают

Вышеупомянутые одноэлементные реализации также могут быть выполнены без включения модуля Singleton. Этого можно достичь с помощью следующего:

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

который является сокращенным обозначением для следующего:

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

Однако имейте в виду, что модуль Singleton проверен и оптимизирован, поэтому является лучшим вариантом для реализации вашего синглтона.

наблюдатель

Шаблон наблюдателя представляет собой шаблон разработки программного обеспечения, в котором объект (называемый subject ) поддерживает список своих иждивенцев (называемых observers ) и автоматически уведомляет их о любых изменениях состояния, как правило, путем вызова одного из своих методов.

Ruby предоставляет простой механизм для реализации шаблона проектирования Observer. Модуль Observable предоставляет логику для уведомления абонента о любых изменениях объекта Observable.

Чтобы это работало, наблюдаемое должно было утверждать, что оно изменилось и уведомило наблюдателей.

Наблюдение объектов должно реализовать метод update() , который будет обратным вызовом для Observer.

Давайте реализовываем небольшой чат, где пользователи могут подписаться на пользователей, а когда один из них что-то пишет, подписчики получают уведомление.

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

Мы вызвали метод write в классе Moderator дважды, уведомляя его подписчиков, в этом случае только один.

Чем больше подписчиков мы добавим, тем больше будут распространяться изменения.

Шаблон декоратора

Шаблон Decorator добавляет поведение к объектам, не затрагивая другие объекты того же класса. Шаблон декоратора является полезной альтернативой созданию подклассов.

Создайте модуль для каждого декоратора. Этот подход более гибкий, чем наследование, потому что вы можете смешивать и сопоставлять обязанности в большем количестве комбинаций. Кроме того, поскольку прозрачность позволяет декораторам быть вложенными рекурсивно, это допускает неограниченное количество обязанностей.

Предположим, что класс Pizza имеет метод стоимости, который возвращает 300:

class Pizza
  def cost
    300
  end
end

Представляйте пиццу с добавленным слоем сыра, и стоимость увеличивается на 50. Самый простой подход - создать подкласс PizzaWithCheese который возвращает 350 в методе стоимости.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

Затем нам нужно представить большую пиццу, которая добавит 100 к стоимости обычной пиццы. Мы можем представить это с помощью подкласса LargePizza Pizza.

class LargePizza < Pizza
  def cost
    400
  end
end

У нас также может быть ExtraLargePizza, который добавляет дополнительную стоимость 15 к нашей LargePizza. Если бы мы считали, что эти типы пиццы могут быть поданы с сыром, нам нужно будет добавить подклассы LargePizzaWithChese и ExtraLargePizzaWithCheese. В итоге в итоге будет 6 классов.

Чтобы упростить подход, используйте модули для динамического добавления поведения в класс Pizza:

Модуль + удлинитель + супер-декоратор: ->

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

полномочие

Объект прокси часто используется для обеспечения защищенного доступа к другому объекту, внутренняя бизнес-логика которого мы не хотим загрязнять с требованиями безопасности.

Предположим, мы хотели бы гарантировать, что только пользователь определенных разрешений может получить доступ к ресурсу.

Определение прокси: (он гарантирует, что только пользователи, которые действительно могут видеть оговорки, смогут пользоваться услугой customer_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 ) с не связанными с доменом понятиями (разрешениями пользователя) в сервисе.
  • Потребитель ( StatsService ) также свободен от связанной с разрешениями логики

ПРЕДОСТЕРЕЖЕНИЯ
  • Интерфейс прокси всегда точно такой же, как и объект, который он скрывает, поэтому пользователь, который потребляет услугу, завернутый прокси-сервером, даже не знал о наличии прокси-сервера.


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow