Wednesday, February 9, 2011

Behavioral Patterns - Interpreter Pattern

Definition

Provides a definition of a macro language or syntax and parsing into objects in a program. 

Intent

  • Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
  • Map a domain to a language, the language to a grammar, and the grammar to a hierarchical object-oriented design.

Problem

A class of problems occurs repeatedly in a well-defined and well-understood domain. If the domain were characterized with a “language”, then problems could be easily solved with an interpretation “engine”.

Where to use & benefits

  • Need your own parser generator.
  • Translate a specific expression.
  • Handle a tree-related information.
  • Related patterns include
    • Composite, which is an instance in an interpreter.
    • Flyweight, which shows how to share symbols with abstract context.
    • Iterator, which is used to traverse the tree structure.
    • Visitor, which is used to maintain behavior of each note in tree structure. 


    Structure

    Interpreter suggests modeling the domain with a recursive grammar. Each rule in the grammar is either a ‘composite’ (a rule that references other rules) or a terminal (a leaf node in a tree structure). Interpreter relies on the recursive traversal of the Composite pattern to interpret the ‘sentences’ it is asked to process.

    Scheme of Interpreter


    Motivation

    The Interpreter is one of the Design Patterns published in the GoF which is not really used. Ussualy the Interpreter Pattern is described in terms of formal grammars, like it was described in the original form in the GoF but the area where this design pattern can be applied can be extended.

    Intent

    - Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
    - Map a domain to a language, the language to a grammar, and the grammar to a hierarchical object-oriented design

  • Implementation

    The implementation of the Interpreter pattern is just the use of the composite pattern applied to represent a grammar. The Interpreter defines the behaviour while the composite defines only the structure.

     The Interpreter Pattern defines a grammatical representation for a language and an interpreter to interpret the grammar. The best example you can get for this is Java itself which is an interpreted language. It converts the code written in English to a byte code format so as to make possible for all the operating systems to understand it. This quality of it makes it platform independent.

    The development of languages can be done when you find different cases but, somewhat similar, it is advantageous to use a simple language which can be interpreted by the system and can deal with all these cases at the same time.

    To make this interpreter clearer, let’s take an example. The “musical notes” is an “Interpreted Language”. The musicians read the notes, interpret them according to “Sa, Re, Ga, Ma…” or “Do, Re, Me… ” etc and play the instruments, what we get in output is musical sound waves. Think of a program which can take the Sa, Re, Ga, Ma etc and produce the sounds for the frequencies.

    For Sa, the frequency is 256 Hz, similarly, for Re, it is 288Hz and for Ga, it is 320 Hz etc etc…

    In this, case, we need these values set somewhere so, that when the system encounters any one of these messages, we can just send the related frequency to the instrument playing the frequency.

    We can have it at one of the two places, one is a constants file, “token=value” and the other one being in a properties file. The properties file can give us more flexibility to change it later if required.

    This is how a properties file will look like:

    MusicalNotes.properties

    # Musical Notes Properties file
    # This denotes the frequencies of musical notes in Hz
    Sa=256
    Re=288
    Ga=320

    Here are the other classes used for this system:


    NotesInterpreter.java
    package bahavioral.interpreter; public class NotesInterpreter {
      private Note note;

    /**
    * This method gets the note from the keys pressed.
    * Them, this sets it at a global level.
    */
    public void getNoteFromKeys(Note note) {
    Frequency freq = getFrequency(note);
    sendNote(freq);
    }

    /**
    * This method gets the frequency for the note.
    * Say, if the note is “Sa”, it will return 256.
    */
    private Frequency getFrequency(Note note) {
    // Get the frequency from properties
    // file using ResourceBundle
    // and return it.
    return freq;
    }

    /**
    * This method forwards the frequency to the
    * sound producer which is some electronic instrument which
    * plays the sound.
    */
    private void sendNote(Frequency freq) {
    NotesProducer producer = new NotesProducer();
    producer.playSound(freq);
    }
    }// End of class
    NotesProducer.java
    package bahavioral.interpreter; public class NotesProducer {

      private Frequency freq;

    public NotesProducer() {
    this.freq = freq;
    }

    /**
    * This method produces the sound wave of the
    * frequency it gets.
    */
    public void playSound(Frequency freq) {
    }
    }// End of class

    This is how an interpreter pattern works in its most simple implementation. If you are using interpreter pattern, you need checks for grammatical mistakes etc. This can make it very complex. Also, care should be taken to make the interpreter as flexible as possible, so that the implementation can be changed at later stages without having tight coupling.

    Other advantage of Interpreter is that you can have more than one interpreter for the same output and create the object of interpreter based on the input. E.g. “Sa” or “Do” can also be implemented as “Download” activity in some other language. In this case, you can use same input and different outputs by getting the proper interpreter from the InterpreterFactory.

    This is not a very common pattern.




    Example

    Given any string expression and a token, filter out the information you want. The below is a simple parser program. the myParser method can be used to parse any expression. The composite, visit and iterator patterns have been used.
    import java.util.*;
    class Parser{
        private String expression;
        private String token; 
        private List result;
        private String interpreted;
        
        public Parser(String e, String t) {
           expression = e;
           token = t;
        }
        
        public void myParser() {  
            StringTokenizer holder = new StringTokenizer(expression, token); 
            String[] toBeMatched = new String[holder.countTokens()];  
            int idx = 0;
            while(holder.hasMoreTokens()) { 
                String item = holder.nextToken(); 
                int start = item.indexOf(","); 
                if(start==0) { 
                   item = item.substring(2); 
                }   
            toBeMatched[idx] = item; 
            idx ++; 
            } 
            result = Arrays.asList(toBeMatched); 
         }
         public List getParseResult() {
            return result;
         }
         public void interpret() {
             StringBuffer buffer = new StringBuffer();
             ListIterator list = result.listIterator();
             while (list.hasNext()){
                String token = (String)list.next();
                if (token.equals("SFO")){
                   token = "San Francisco";          
                }else if(token.equals("CA")) {
                   token = "Canada";
                }
                //...
                buffer.append(" " + token);
             }
             interpreted = buffer.toString();        
         }
         public String getInterpretedResult() {
            return interpreted;
         }
         public static void main(String[] args) {
             String source = "dest='SFO',origin='CA',day='MON'";
             String delimiter = "=,'";
             Parser parser = new Parser(source, delimiter);
             parser.myParser();
             parser.interpret();
             String result = parser.getInterpretedResult();
             System.out.println(result);
         }
    } 
    java Parser
    dest San Francisco origin Canada day MON


No comments:

Post a Comment