Version 6 of Snit design patterns

Updated 2003-08-18 10:42:14

See Design patterns in Tcl.

Started by Peter Lewerin (2003-08-17).

IANAPG (I am not a patterns guru), so I use the example code from http://www.dofactory.com/Patterns/Patterns.aspx as a base for my implementations.

Chain-of-responsibility: "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."

 package require snit

 snit::type handler {
         option -handleIf
         variable condition false
         onconfigure -handleIf value {set condition $value}

         option -handleBy 
         variable consequent {}
         onconfigure -handleBy value {set consequent $value}

         option -elseReportTo
         variable alternative {}
         onconfigure -elseReportTo value {
                 set alternative [concat $value handle \$request]
         }

         method handle request {
                 if $condition $consequent $alternative
         }
 }

Example:

 proc main args {
         handler Tom -handleIf {
                 $request >= 0 && $request < 10
         } -handleBy {
                 puts "$self handled request $request"
         } -elseReportTo Gus

         handler Gus -handleIf {
                 $request >= 10 && $request < 20
         } -handleBy {
                 puts "$self handled request $request"
         } -elseReportTo Mary

         handler Mary -handleIf {
                 $request >= 20 && $request < 30
         } -handleBy {
                 puts "$self handled request $request"
         }

         foreach request [list 2 5 14 22 18 38 3 27 20] {
                 Tom handle $request
         }
 }

Command: "Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations."

 package require snit

 snit::type command {
         option -calculator

         variable state 0

         method execute {operator operand} {
                 set state [[$self cget -calculator] $operator $state $operand]
                 puts "Total = $state (following $operator $operand)"
         }

         method unexecute {operator operand} {
                 set state [[$self cget -calculator] inverse $operator $state $operand]
                 puts "Total = $state (following inverse $operator $operand)"
         }
 }

 # receiver
 snit::type arithmeticCalculator {
         method + {op1 op2} {$self operation + $op1 $op2}

         method - {op1 op2} {$self operation - $op1 $op2}

         method * {op1 op2} {$self operation * $op1 $op2}

         method / {op1 op2} {$self operation / $op1 $op2}

         method operation {operator op1 op2} {
                 expr [list $op1 $operator $op2]
         }

         method inverse {operator op1 op2} {
                 $self operation [string map {+ - - + * / / *} $operator] $op1 $op2
         }
 }

 snit::type invoker {
         variable commands
         variable current 0

         method redo levels {
                 puts "---- Redo $levels levels"
                 for {set i 0} {$i < $levels} {incr i} {
                         if {$current < [llength $commands]} {
                                 foreach {cmd opt opd} [lindex $commands $current] {
                                         $cmd execute $opt $opd
                                 }
                                 incr current
                         }
                 }
         }

         method undo levels {
                 puts "---- Undo $levels levels"
                 for {set i 0} {$i <= $levels} {incr i} {
                         if {$current >= 0} {
                                 foreach {cmd opt opd} [lindex $commands [incr current -1]] {
                                         $cmd unexecute $opt $opd
                                 }
                         }
                 }
         }

         method invoke {command operator operand} {
                 $command execute $operator $operand
                 lappend commands [list $command $operator $operand]
                 set current [llength $commands]
                 incr current
         }
 }

Example:

 proc main args {
         set cmd [command %AUTO% -calculator [arithmeticCalculator %AUTO%]]
         invoker user

         user invoke $cmd + 100
         user invoke $cmd - 50
         user invoke $cmd * 10
         user invoke $cmd / 2

         user undo 4
         user redo 3
 }

Iterator: "Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation."

 package require snit

 snit::type stringIterator {
         method new {varname} {
                  set i [iterator %AUTO% -variable $varname -firstCommand {
                          string index $%N [set %C 0]
                  } -nextCommand {
                          string index $%N $%C
                  } -donePredicate {
                          expr {$%C >= [string length $%N]}
                  } -currentReader {
                          string index $%N $%I
                  } -currentWriter {
                          set %N [string replace $%N $%I $%I $%V]
                  }]
         }
 }

 snit::type iterator {
         option -variable
         variable varname
         onconfigure -variable value {set varname $value}

         option -firstCommand
         option -nextCommand
         option -donePredicate
         option -currentReader
         option -currentWriter

         variable current

         method first {} {
                 set cmd [$self cget -firstCommand]
                 lappend mappings %C [varname current] %N $varname
                 set cmd [string map $mappings $cmd]
                 eval $cmd
         }

         method next {} {
                 set cmd [$self cget -nextCommand]
                 lappend mappings %C [varname current] %N $varname
                 set cmd [string map $mappings $cmd]
                 incr current
                 eval $cmd
         }

         method isDone {} {
                 set cmd [$self cget -donePredicate]
                 lappend mappings %C [varname current] %N $varname
                 set cmd [string map $mappings $cmd]
                 eval $cmd
         }

         method currentItem {{value {}}} {
                 if {$value eq {}} {
                         set cmd [$self cget -currentReader]
                 } else {
                         set cmd [$self cget -currentWriter]
                 }
                 lappend mappings %N $varname %I current %V value
                 set cmd [string map $mappings $cmd]
                 eval $cmd
         }
 } 

Example:

 proc main args {
         set ::a {Hello World}
         stringIterator si
         set i [si new ::a]
         for {$i first} {![$i isDone]} {$i next} {
                 puts [$i currentItem]
         }
 }