Sök…


Singleton

Ruby Standard Library har en Singleton-modul som implementerar Singleton-mönstret. Det första steget i att skapa en Singleton-klass är att kräva och inkludera Singleton modulen i en klass:

require 'singleton'

class Logger
  include Singleton
end

Om du försöker instansera den här klassen som du vanligtvis gör i en vanlig klass NoMethodError undantag från NoMethodError . Konstruktören görs privat för att förhindra att andra instanser skapas av misstag:

Logger.new

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

För att få tillgång till instansen för den här klassen måste vi använda instance() :

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

#=> true

Loggerexempel

require 'singleton'


class Logger
  include Singleton

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

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

För att använda Logger objektet:

Logger.instance.log('message 2')

Utan Singleton inkludera

Ovanstående singletonimplementeringar kan också göras utan att Singleton-modulen inkluderas. Detta kan uppnås med följande:

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

vilket är en kortfattad notation för följande:

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

Tänk dock på att Singleton-modulen är testad och optimerad, varför det är det bästa alternativet att implementera din singleton med.

Observatör

Observatörsmönstret är ett mönster för mjukvarudesign i vilket ett objekt (kallat subject ) upprätthåller en lista över dess beroende (kallas observers ) och meddelar dem automatiskt om tillståndsförändringar, vanligtvis genom att ringa en av deras metoder.

Ruby tillhandahåller en enkel mekanism för att implementera Observer-designmönstret. Modulen Observable tillhandahåller logiken för att meddela abonnenten om eventuella förändringar i det observerbara objektet.

För att detta ska fungera måste den observerbara hävda att den har förändrats och meddela observatörerna.

Objekt som observeras måste implementera en update() , som kommer att vara återuppringning för Observer.

Låt oss genomföra en liten chatt där användare kan prenumerera på användare och när en av dem skriver något får abonnenterna ett meddelande.

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

Producerar följande utgång:

# Computer says: No
# Computer says: No

Vi har utlöst metoden att write på Moderator-klassen två gånger och meddelat dess prenumeranter, i detta fall bara en.

Ju fler prenumeranter vi lägger till, desto mer kommer förändringarna att spridas.

Dekoratormönster

Dekoratörsmönster lägger till beteenden utan att påverka andra objekt i samma klass. Dekoratormönstret är ett användbart alternativ till att skapa underklasser.

Skapa en modul för varje dekoratör. Denna strategi är mer flexibel än arv eftersom du kan blanda och matcha ansvar i fler kombinationer. Eftersom transparensen gör att dekoratörer kan kapslas rekursivt, tillåter det dessutom ett obegränsat antal ansvarsområden.

Anta att pizzaklassen har en kostnadsmetod som returnerar 300:

class Pizza
  def cost
    300
  end
end

Representera pizza med ett extra skikt ostbristning och kostnaden ökar med 50. Det enklaste tillvägagångssättet är att skapa en PizzaWithCheese underklass som returnerar 350 i kostnadsmetoden.

class PizzaWithCheese < Pizza
  def cost
    350
  end
end

Därefter måste vi representera en stor pizza som lägger till 100 till kostnaden för en normal pizza. Vi kan representera detta med hjälp av en LargePizza-underklass av Pizza.

class LargePizza < Pizza
  def cost
    400
  end
end

Vi kan också ha en ExtraLargePizza som lägger till en ytterligare kostnad på 15 till vår LargePizza. Om vi skulle tänka på att dessa pizzatyper skulle kunna serveras med ost, skulle vi behöva lägga till LargePizzaWithChese och ExtraLargePizzaWithCheese-underklasser. Vi slutar med totalt 6 klasser.

För att förenkla tillvägagångssättet använder du moduler för att dynamiskt lägga till beteende i Pizza-klassen:

Modul + förlängning + superdekoratör: ->

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

Ombud

Proxy-objekt används ofta för att säkerställa skyddad åtkomst till ett annat objekt, vilken intern affärslogik vi inte vill förorena med säkerhetskraven.

Anta att vi vill garantera att endast användare med specifika behörigheter kan komma åt resursen.

Proxy-definition: (det säkerställer att endast användare som faktiskt kan se reservationer kan konsumentreservation_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

Modeller och reservationService:

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

Konsumenttjänst:

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

Testa:

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)

FÖRDELAR
  • vi undviker alla ändringar i ReservationService när åtkomstbegränsningar ändras.
  • Vi blandar inte affärsrelaterade data ( date_from , date_to , reservations_count ) med domänrelaterade koncept (användarrättigheter) i tjänsten.
  • Konsument ( StatsService ) är också fri från behörighetsrelaterad logik

TÄNK
  • Proxy-gränssnittet är alltid exakt samma som objektet som det döljer, så att användare som konsumerar tjänster som är inslagna av proxy inte ens var medvetna om proxy-närvaro.


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow