Saturday, December 16, 2017

#1 2005-08-11 05:43:10 am

Chris Moore
Member
From:: Japan, Aiti, Nagoya
Registered: 2003-08-07
Posts: 104

Object Pattern: the Object Switch

This time I want to talk about a great pattern of object usage which can be adapted into _many_ scripting situations. If you're not familiar with script objects, perhaps you should read that article first. I call this pattern the "Object Switch". Here's the situation: you've got a list of things that you're going to loop over and based on some attributes of the object, do something a little different to each one. I can imagine that you're thinking of this repeat statement that covers like five pages, filled with if... else if... else if... ad inifinitum. Impossible to maintain, difficult to read (not much nicer to write), and as exceptions or new cases get added it _grows_ consuming your scripting patience and time. I've seen it in more scripts than I care to recall.

Let's change that! Using objects we can transform that gigantic impenetrable mess into a purring little kitten. The example I'll look at this time is a downloads folder assistant folder action (see also the macscripter faq). As files get downloaded the "adding folder items to... after recieving..." handler gets called. For certain kinds of files I want to define actions to occur, and for other files maybe just tell the finder to reveal it. This fits the above problem exactly; list of files, different actions per file attributes, and the cases we intend to handle is likely to grow or change over time. Let's start by looking at the "adding folder items to" handler of the script:

Applescript:


on adding folder items to downloadsFolder after receiving someFiles
   repeat with eachFile in someFiles
       tell ObjectSwitch to dispatch(eachFile)
   end repeat
end adding folder items to

Instead of a huge "else if" structure I tell a script object (ObjectSwitch) to do all the work. Let's look at the two objects now that make up the base of the "Object Switch" pattern: the switch (in this case ObjectSwitch), and the base case (AnyFile).

Applescript:


script AnyFile
   on match(fileAlias)
       return true --> over-ride me
   end match
   
   on actOn(fileAlias)
       return --> over-ride me
   end actOn
end script

script ObjectSwitch
   property caseList : {AnyFile}
   
   on dispatch(fileAlias)
       repeat with eachCase in caseList
           tell eachCase
               if match(fileAlias) then
                   actOn(fileAlias)
                   return
               end if
           end tell
       end repeat
   end dispatch
end script

Now to add a case, all I'd to is define a script object that may inherit from AnyFile and add it to ObjectSwitch's caseList. In the most basic version of this pattern we only need match and actOn handlers, but for the next revision I'm going to add two more to show you the flexibility of this technique. Let's look at a more complete version of the script with two special cases: AppleScriptFile, and WidgetArchiveFile. (pay attention to the highlighted bits...)

Applescript:


script AnyFile
   on match(fileAlias)
       return true --> over-ride me
   end match
   
   on shouldActOn(fileAlias)
       return true --> over-ride me
   end shouldActOn
   
   on actOn(fileAlias)
       tell application "Finder"
           reveal fileAlias
       end tell
       return --> over-ride me
   end actOn
   
   on didActOn(fileAlias)
       return --> over-ride me
   end didActOn
end script

script AppleScriptFile
   property parent : AnyFile
   property defaultApp : "Script Editor"
   
   on match(fileAlias)
       return (default application of (info for fileAlias)) is (application defaultApp)
   end match
   
   on shouldActOn(fileAlias)
       display dialog "Move " & (fileAlias as text) & " to scripts folder" buttons {"Don't move file", "Move file"} default button "Move file"
       return (button returned of result) is "Move file"
   end shouldActOn
   
   on actOn(fileAlias)
       move fileAlias to scripts folder
   end actOn
end script

script WidgetArchiveFile
   property parent : AnyFile
   
   on goodPosixPath(fileAlias)
       -- escape spaces
       
       set my posixPath to POSIX path of fileAlias
       set oldTids to text item delimiters
       set text item delimiters to " "
       set my posixPath to text items of my posixPath
       set text item delimiters to "\\ "
       set my posixPath to my posixPath as text
       set text item delimiters to oldTids
       
       return my posixPath
   end goodPosixPath
   
   on ensureDirectory()
       try
           do shell script "mkdir ~/Library/Widgets"
       on error
           --it already exists
       end try
   end ensureDirectory
   
   on match(fileAlias)
       set my shellScript to "unzip -l " & (goodPosixPath(fileAlias)) & " | grep wdgt\\/$"
       try
           do shell script my shellScript
       on error
           return false
       end try
       
       return result is not ""
   end match
   
   on actOn(fileAlias)
       ensureDirectory()
       
       set my shellScript to "unzip -qqo " & (goodPosixPath(fileAlias)) & " -d ~/Library/Widgets/"
       
       try
           do shell script my shellScript
       end try
       
   end actOn
   
   on didActOn(fileAlias)
       
       display dialog "Installed widget from " & (fileAlias as text) buttons {"Don't move archive to trash", "Move archive to trash"} default button "Move archive to trash"
       if (button returned of result) is "Move archive to trash" then
           move fileAlias to trash
       end if
       
   end didActOn
end script

script ObjectSwitch
   property caseList : {AppleScriptFile, WidgetArchiveFile, AnyFile}
   
   on dispatch(fileAlias)
       repeat with eachCase in caseList
           tell eachCase
               if match(fileAlias) and shouldActOn(fileAlias) then
                   set actResult to actOn(fileAlias)
                   didActOn(fileAlias)
                   return
               end if
           end tell
       end repeat
   end dispatch
end script

on adding folder items to downloadsFolder after receiving someFiles
   repeat with eachFile in someFiles
       tell ObjectSwitch to dispatch(eachFile)
   end repeat
end adding folder items to

Here's some of the reasons "Object Switch" kicks "else if"'s heinie.

Easier to read: The cases are all named, the parts of a case are even separated, and variables all exist in smaller separate scopes.
Easier to write: No getting lost in the nesting as you write, always know what you're doing, what's been done, and what's left to do.
Easier to navigate: everything get's added to our handy dandy navigation bar!
Easier to optimize: Just adjust the caseList order based on frequency.
Easier to extend: Just add script objects that inherit, even possible to create intermediate base cases to share handlers and properties between similar cases.
Easier to adapt: Adjust the dispatch method to create completely different scenarios, such as applying multiple cases by removing the "return" or adding methods to the base case as we saw.
Easier to maintain: Bugs can be easily pinpointed to specific cases, and within a case to specific parts. Cases can be enabled and disabled simply by adjusting the caseList.
Easier to re-use: Put the code from a case into a library, or even the "switch" object and it's code.


Filed under: Finder, Script_Objects, Moore

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)