as a base for my implementations.if 0 {Chain-of-responsibility:Description: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.Implementation notes: Uses a linked list of snit objects, each having a handle method, a -command option and a -next option. The handle method evaluates the code in the -command option together with any arguments given. If the result is the empty string, it asks the next object in the chain to handle it. }
package require snit
snit::type handler {
option -next list
option -command
method handle args {
if {[eval [$self cget -command] $self $args] eq {}} {
eval [$self cget -next] handle $args
}
}
}
# ''Usage example:''
proc director {self request} {
if {$request >= 0 && $request < 10} {
puts "$self handled request $request"
}
}
proc vicePresident {self request} {
if {$request >= 10 && $request < 20} {
puts "$self handled request $request"
}
}
proc president {self request} {
if {$request >= 20 && $request < 30} {
puts "$self handled request $request"
}
}
proc main args {
handler Mary -command president
handler Gus -command vicePresident -next Mary
handler Tom -command director -next Gus
foreach request [list 2 5 14 22 18 38 3 27 20] {
Tom handle $request
}
}if 0 {Command:Description: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.Implementation notes: This pattern requires interaction between at least three objects:
- invoker
- knows how to schedule command activation, possibly with mechanisms like undo, logging, or priority.
- actor
- knows how to perform the actions specified by the commands.
- command
- contains one action-actor binding.
package require snit
snit::type command {
option -actor
option -action
method execute {} {
[$self cget -actor] action [$self cget -action]
}
}
snit::type actor {
variable state 0
method action action {
set state [eval expr $state $action]
}
}
snit::type invoker {
option -actor
variable commands
variable current 0
method redo levels {
for {set i 0} {$i < $levels} {incr i} {
if {$current < [llength $commands]} {
set cmd [lindex $commands $current 0]
set total [$cmd execute]
puts "Total = $total (following [$cmd cget -action])"
incr current
}
}
}
method undo levels {
for {set i 0} {$i <= $levels} {incr i} {
if {$current > 0} {
set cmd [lindex $commands [incr current -1] 1]
set total [$cmd execute]
puts "Total = $total (following [$cmd cget -action])"
}
}
}
method compute args {
set cmd1 [command %AUTO% -actor [$self cget -actor] -action $args]
set cmd2 [command %AUTO% -actor [$self cget -actor] \
-action [string map {+ - - + * / / *} $args]]
lappend commands [list $cmd1 $cmd2]
$self redo 1
}
}
# ''Usage example:''
proc main args {
invoker user -actor [actor %AUTO%]
user compute + 100
user compute - 50
user compute * 10
user compute / 2
puts "---- Undo 4 levels"
user undo 4
puts "---- Redo 3 levels"
user redo 3
}if 0 {Iterator:Description: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.Implementation notes: The object is wrapped in a class that provides methods for finding the first element, finding the next element, dereferencing the current element, and a predicate for determining if all elements have been traversed. }
package require snit
snit::type stringIterator {
option -variable
variable varname
variable current 0
method first {} {
string index [set [$self cget -variable]] [set current 0]
}
method next {} {
string index [set [$self cget -variable]] [incr current]
}
method isDone {} {
expr {$current >= [string length [set [$self cget -variable]]]}
}
method currentItem {{value {}}} {
set varname [$self cget -variable]
if {$value eq {}} {
string index [set $varname] $current
} else {
set $varname [string replace [set $varname] $current $current $value]
}
}
}
# Usage example:
proc main args {
set ::a {Hello World}
set i [stringIterator %AUTO% -variable ::a]
for {$i first} {![$i isDone]} {$i next} {
puts [$i currentItem]
}
}if 0 {Mediator:Description: Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly, and it lets you vary their interaction independently.}
package require snit
snit::type mediator {
variable colleagues
method register {name class} {
set object [set colleagues($name) [$class %AUTO% -name $name]]
$object configure -mediator $self
return $object
}
method send {from whom what} {
if {[info exists colleagues($whom)]} {
$colleagues($whom) receive $from $what
}
}
}
snit::type beatleParticipant {
option -name
option -mediator
method send {whom what} {
[$self cget -mediator] send [$self cget -name] $whom $what
}
method receive {from what} {
puts "To a Beatle: $from to [$self cget -name]: '$what'"
}
}
snit::type nonBeatleParticipant {
option -name
option -mediator
method send {whom what} {
[$self cget -mediator] send [$self cget -name] $whom $what
}
method receive {from what} {
puts "To a Non-Beatle: $from to [$self cget -name]: '$what'"
}
}
# Usage example:
proc main args {
# Create chatroom
mediator c
# Create 'chatters' and register them
set George [c register George beatleParticipant]
set Paul [c register Paul beatleParticipant]
set Ringo [c register Ringo beatleParticipant]
set John [c register John beatleParticipant]
set Yoko [c register Yoko nonBeatleParticipant]
# Chatting participants
$Yoko send "John" "Hi John!"
$Paul send "Ringo" "All you need is love"
$Ringo send "George" "My sweet Lord"
$Paul send "John" "Can't buy me love"
$John send "Yoko" "My sweet love"
}if 0 {Memento:Description: Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later.Implementation notes: The state of a snit is stored in the options array and in the instance variables. Saving/restoring the options trivial, as shown below. Saving/restoring instance variables requires devising some packing/unpacking scheme. }
package require snit
# originator
snit::type salesProspect {
option -name
option -phone
option -budget
variable currency \$
method show {} {
puts "\nSales prospect ---- "
puts "Name: [$self cget -name]"
puts "Phone: [$self cget -phone]"
puts "Budget: $currency[$self cget -budget]"
}
method euro {} {set currency \u20AC}
method createMemento {} {
array set variables [list currency $currency]
set m [prospectMemory %AUTO% -state [list [array get options] [array get variables]]]
}
method restoreMemento {memento} {
foreach {optionList variableList} [$memento cget -state] break
array set options $optionList
foreach {name val} $variableList {
set $name $val
}
}
}
# memento
snit::type prospectMemory {
option -state
}
# Usage example:
proc main args {
salesProspect s
s configurelist {
-name "Noel van Halen"
-phone "(412) 256-0990"
-budget 25000.0
}
s show
set m [s createMemento]
s configurelist {
-name "Leo Welch"
-phone "(310) 209-7111"
-budget 1000000.0
}
s euro
s show
s restoreMemento $m
s show
}if 0 {Observer:Description: Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.Implementation notes: }
package require snit
snit::type investor {
option -name
method update stock {
puts -nonewline "Investor [$self cget -name] notified of "
puts -nonewline "[$stock cget -symbol]'s change to "
puts [format "%.2f" [$stock cget -price]]
}
}
snit::type stock {
option -symbol
option -price
onconfigure -price value {
set options(-price) $value
$self notify
}
variable investors {}
method attach investor {lappend investors $investor}
method detach investor {
set i [lsearch $investors $investor]
if {$i >= 0} {
set investors [lreplace $investors $i $i]
}
}
method notify {} {
foreach investor $investors {
$investor update $self
}
}
}
snit::type IBM {
delegate method * to base
delegate option * to base
variable base
constructor args {
set base [stock %AUTO%]
$self configurelist $args
}
}
# ''Usage example:''
proc main args {
investor s -name Sorros
investor b -name Berkshire
IBM ibm -symbol IBM -price 120.00
ibm attach s
ibm attach b
foreach p {120.10 121.00 120.50 120.75} {
ibm configure -price $p
}
}if 0 {State:Description: Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.Implementation notes: (as suggested by WHD)
- Delegate the relevant methods to a component called "state".
- Initialize the "state" instance variable to the initial state object.
- When the state changes, assign a different state object to "state".
package require snit
snit::type redState {
option -account
option -balance
onconfigure -balance value {
set options(-balance) $value
if {[set acct [$self cget -account]] ne {}} {
if {[$self cget -balance] > $upperLimit} {
[$self cget -account] alter silverState
}
}
}
variable upperLimit 0.0
variable serviceFee 15.00
method deposit amount {
$self configure -balance [expr [$self cget -balance] + $amount - $serviceFee]
}
method withdraw amount {
puts "No funds available to withdraw!"
}
method payInterest {} {}
}
snit::type silverState {
option -account
option -balance
onconfigure -balance value {
set options(-balance) $value
if {[set acct [$self cget -account]] ne {}} {
set balance $value
if {$balance > $upperLimit} {
$acct alter goldState
} elseif {$balance < $lowerLimit} {
$acct alter redState
}
}
}
variable interest 0.0
variable lowerLimit 0.0
variable upperLimit 1000.0
method deposit amount {
$self configure -balance [expr [$self cget -balance] + $amount]
}
method withdraw amount {
$self configure -balance [expr [$self cget -balance] - $amount]
}
method payInterest {} {
$self configure -balance [expr [$self cget -balance] * (1 + $interest)]
}
}
snit::type goldState {
option -account
option -balance
onconfigure -balance value {
set options(-balance) $value
if {[set acct [$self cget -account]] ne {}} {
set balance $value
if {$balance < 0.0} {
$acct alter redState
} elseif {$balance < $lowerLimit} {
$acct alter silverState
}
}
}
variable interest 0.05
variable lowerLimit 1000.0
method deposit amount {
$self configure -balance [expr [$self cget -balance] + $amount]
}
method withdraw amount {
$self configure -balance [expr [$self cget -balance] - $amount]
}
method payInterest {} {
$self configure -balance [expr [$self cget -balance] * (1 + $interest)]
}
}
snit::type account {
option -owner
delegate method * to state
delegate option * to state
variable state
method balance {} {
$state cget -balance
}
method deposit amount {
$state deposit $amount
puts [format "Deposited %.2f --- " $amount]
puts [format "Balance = %.2f" [$self balance]]
puts [format "Status = %s" $state]
puts {}
}
method withdraw amount {
$state withdraw $amount
puts [format "Withdrew %.2f --- " $amount]
puts [format "Balance = %.2f" [$self balance]]
puts [format "Status = %s" $state]
puts {}
}
method payInterest {} {
$state payInterest
puts "Interest Paid --- "
puts [format "Balance = %.2f" [$self balance]]
puts [format "Status = %s" $state]
puts {}
}
method alter t {
set state [$t ::%AUTO% -balance [$state cget -balance] -account [$state cget -account]]
}
constructor args {
set state [silverState ::%AUTO% -balance 0.0 -account $self]
$self configurelist $args
}
}
# ''Usage example:''
proc main args {
account account -owner "Molly Brown"
account deposit 500.0
account deposit 300.0
account deposit 550.0
account payInterest
account withdraw 2000.00
account withdraw 1100.00
}if 0 {Strategy:Description: Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.Implementation notes:}
package require snit
snit::type warlord {
option -strategy
method hearBirdSing {} {
if {[$self cget -strategy] ne {}} {
[$self cget -strategy] do
} else {
puts ...
}
}
}
snit::type nobunaga {
method do {} {
puts "If the bird does not sing, I shall wring its neck."
}
}
snit::type hideyoshi {
method do {} {
puts "I shall try to teach the bird to sing."
}
}
snit::type ieyasu {
method do {} {
puts "I shall wait for the bird to sing."
}
}
# ''Usage example:''
proc main args {
# the warlord Rikyu wants to hear the bird sing, but it won't.
warlord Rikyu
Rikyu hearBirdSing
# which great leader from history will Rikyu borrow a strategy from?
Rikyu configure -strategy [hideyoshi %AUTO%]
Rikyu hearBirdSing
}Function objects:See: [Snit Lambda]

