Observer Pattern

lua-users home
wiki

This is an implementation of the Observer Design Pattern [1], making use of closures.

-- Observer design pattern.

-- Produces a few functions that have a closure that implements

-- subject-observer notification.



-- Example usage:

-- subject.reg, subject.dereg, subject.notify = observer.create()

-- subject:reg( "signal", observer, method )

-- subject:reg( "signal", nil, function )

-- subject:notify( "signal" )

-- subject:dereg( "signal", observer, method )

-- subject:dereg( "signal", nil, function )



local table = require("table");

local base = require("base");

local setmetatable = base.setmetatable

local ipairs = base.ipairs



module("observer")



function create()

  -- Containter holds the filter, and handlers and observers.

  local container = {}



  -- Provision for garbage collection, weak metatable and sentinel.

  local weak = { __mode = "kv" }



  -- Register

  -- Creates an observation between the Subject data and the

  -- Observer data, using filter Signal and handler Method.

  -- Usage:

  --   s:register( "update", o, o.m )

  local register = function( subject, signal, observer, method )

    t = container[signal] or {}

    local o = observer or weak

    local k = { method, o }

    setmetatable( k, weak )

    table.insert( t, k )

    container[signal] = t

  end



  -- Deregister

  -- Removes any observations in the Signal filter, either matching

  -- the Observer and Method, or the whole filter if both are nil.

  -- Usage:

  --   s:deregister( "update" )

  --   s:deregister( "update", o )

  --   s:deregister( "update", o, o.m )

  local deregister = function( subject, signal, observer, method )

    t = container[signal]

    if not t then return end

    if not method and not observer then

      container[signal] = nil

      return

    end

    local i, v

    i = #t

    while i > 0 do

      v = t[i] or {}

      if  ( not method   or v[1] == method )

      and ( not observer or v[2] == observer ) then

        table.remove( t, i )

      end

      i = i - 1

    end

  end



  -- Notify

  -- Uses the Signal Filter to notify all observations via their

  -- registered handlers.

  -- Usage:

  --   s:notify( "update" )

  local notify = function( subject, signal, ... )

    t = container[signal]

    if not t then return end

    for i, v in ipairs( t ) do

      if v[2] == weak then

        v[1](subject, ...)

      elseif v[2] then

        v[1](v[2], subject, ...)

      end

      -- garbage collected observers (nil) are skipped.

    end

  end



  return register, deregister, notify

end



-- Signal

-- Convienience function that gives a shorthand to sending a

-- notification.

-- Usage:

--   s.update = signal( s, s.notify, "update" )

--   s.update()

function signal( subject, notify, name )

  return function( ... )

    return notify( subject, name, ... )

  end

end

Here's an example usage. Note that the observer.signal function helps create a syntax that somewhat imitates the signals and slots model of the C++ framework Qt.

require("observer")



-- Observers.

function a( subject )

  print( "a", type(subject) )

end



function b( data, subject, extra )

  print( "b", data, type(subject), extra )

end



o = {}

function o:m( subject, extra )

  print( "o:m", type(self), type(subject), extra )

end



-- First Observation

reg, dereg, notify = observer.create()



reg( nil, "signal", nil, a )

reg( nil, "signal", "lol", b )

print( "First notification" )

notify( nil, "signal", "zomg" )

dereg( nil, "signal", nil, a )

print( "Second notification" )

notify( nil, "signal" )



-- Second Observation



s = {}

s.reg, s.dereg, s.notify = observer.create()

s.signal = observer.signal( s, s.notify, "signal" )



s:reg( "signal", nil, a )

s:reg( "signal", o, o.m )

print( "Third notification" )

s.signal("rofl")

s:dereg( "signal" )



s:reg( "signal", o, o.m )

print( "Fourth notification" )

o = nil

collectgarbage()

s:notify( "signal" )

comments appreciated.

--WilliamBubel


RecentChanges · preferences
edit · history
Last edited September 16, 2008 2:07 am GMT (diff)