1.47k likes | 1.6k Views
Unit 27 Interpreter. Summary prepared by Kirk Scott. Design Patterns in Java Chapter 25 Interpreter. Summary prepared by Kirk Scott. The Introduction Before the Introduction. There are several problems with this chapter Among them are the following:
E N D
Unit 27Interpreter Summary prepared by Kirk Scott
Design Patterns in JavaChapter 25Interpreter Summary prepared by Kirk Scott
The Introduction Before the Introduction • There are several problems with this chapter • Among them are the following: • 1. The book doesn’t explain the following two points very well: • A. What is an interpreter? • B. Why would you want to use the Interpreter design pattern? • The book touches on these questions in the last subsection of the chapter • Even then, the book seems to concede that not all of the machinery is present in order to make this be very practical or seem very practical
2. Another shortcoming is that in implementing the pattern, some of the book’s code is not easy to understand • This breaks down into two sub-problems • A. The underlying problem seems to me to be that the code violates encapsulation • Not only is this potentially a poor choice, but this is part of what makes it hard to understand
B. The additional observation on this is that the book’s explanations are not incredibly transparent • The code is hard to understand, and even after reading the explanations, you’re left wondering whether the book’s approach was a good idea or not
1.A What is an Interpreter? • Returning to point 1 in more detail: • In general, in what problem domain do you expect to hear about interpreters? • The answer is, this belongs in the category of language processing • In other words, you think of interpreters and compilers at the same time • These are pieces of code which allow you to translate from a source language to a target language, from high level code to machine code
If you have had a course in programming languages or compilers you might also have heard the terms lexical analyzer and parser • These terms refer to different steps in reading a source file containing statements in some language, and translating them, ultimately, into a sequence of instructions that can be executed on a computer chip
For the sake of background, here is a definition of a lexical analyzer (from Aho, Sethi, and Ullman • “The lexical analyzer is the first phase of a compiler. Its main task is to read the input characters and produce as output a sequence of tokens that the parser uses for syntax analysis.” • In other words, given a stream of characters, break it into a sequence of constants, variables, operators, keywords, etc.
Here is a definition of a parser (from Aho, Sethi, and Ullman) • “In our compiler model, the parser obtains a string of tokens from the lexical analyzer, …, and verifies that the string can be generated by the grammar for a source language. We expect the parser to report any syntax errors in an intelligible fashion.” • In other words, the parser checks to see whether the input, after having been broken into tokens, forms a set of statements in the supposed source language.
The last stage in a compiler is translation from the verified statements of the source file to an equivalent sequence of instructions in the target language • So what’s an interpreter, compared to a compiler? • In very simple terms, an interpreter is a system that translates each statement as it’s parsed, and executes it without waiting to translate the complete program
Interpretation can be a preferred way of dealing with programs for several reasons • It is common with command languages, for example • Users may enter ad hoc sequences of instructions at a command prompt • These instructions may be calls to external applications like system utilities
If on the command side, it’s difficult to foresee and impossible to control exactly what results the system utility produces, then a compiler may not be able to form a complete program • It is better to have an interpreted system that is flexible and responsive to the results that are produced command by command • In other words, it makes sense to translate one step at a time
You may recall that Java is referred to as an interpreted language • On the other hand, you are also familiar with the idea that you have to compile Java source code • What’s going on? • A Java source file is written in Java, a high level, human understandable language
Compilation results in a class file, containing what is known as bytecode • Bytecode is not machine language for any actual physical machine • It is the language of the Java Virtual Machine • What is the Java Virtual Machine? • In one respect, it is a simulated machine which runs bytecode
In another respect, it is an interpreter • It interprets bytecode, line by line, into code that can run on the physical host machine • What advantage is there to this overall design? • Java is “universal” • It can be run on any hardware for which a Java Virtual Machine exists
The code is only compiled once, into bytecode • It is not compiled a second time, into machine language • When a Java class file is to be run, there is no delay while it is translated in its entirety into machine language code • It starts running immediately—running on the machine simulation—running “on” the interpreter which the Java Virtual Machine is
B. Why Would You Want to Use the Interpreter Design Pattern? • The book tries to illustrate the Interpreter design pattern in a practical, applied way • Instead of worrying about the problems of language interpretation, the authors pose the problem of creating programs that interact with and control machines in a factory • They develop a mechanism that makes it possible to build programs that would have this effect
Then at the end of the chapter they observe that you could write programs directly which would control the machines • It’s not clear that you’ve gained any benefit from the machinery that makes it possible to compose sequences of commands which could be used as machine control programs • In response to this, they observe the following:
It would be possible to have another language, a language for controlling machines, in which a programmer/engineer/person responsible for a machine could write a program to control it • Such a language would not have to have the features of Java • It could be restricted to concepts of machine control only
Writing machine control programs in such a language would be simpler • The problem would then become, how to translate them into Java code for use in factory environment where implementations were done in that language
The book finally concludes that you would need a parser that could translate from the simple, machine control language, into the machinery for building Java programs which they present as the Interpreter design pattern • Then the interpreter design pattern as presented here would become useful
On the one hand, we can be thankful that the book doesn’t try to complete the job by developing a parser • On the other hand, it turns out that the motivation for the Interpreter design pattern is incomplete • We are shown how it’s done, but it’s hard to imagine that it’s very useful when the book concedes that you could just write straight Java code to accomplish all that the examples accomplish
In conclusion in this introductory subsection, I can only say this: • It’s an eternal problem that when you try to explain something complicated, you end up simplifying • The simplification often makes the example seem pointless • This doesn’t mean that the design pattern is useless • Regardless of the examples, the goal is to understand the pattern
2.A Encapsulation is Violated • It appears that the authors violate encapsulation in part of their implementation of the Interpreter design pattern • This will be pointed out specifically in the code when it is presented • The statement is always made that violating encapsulation is bad • At the very least, you will see that it makes the code hard to understand
The book’s explanations don’t really make it clear • This may lead you to the following, valid conclusion: • Maybe part of the reason that violating encapsulation is bad is that it leads to code that is hard to understand • Maybe part of the reason for developing the concept of encapsulation was to try to avoid the difficulties in understanding that become evident when encapsulation is violated
Introductory Book Material • The Interpreter design pattern can be used to make objects where the objects, in essence, are like individual commands • Different kinds of commands are implemented as different classes • Each of these classes has a method of the same name, something like execute(), and the difference in the command objects consists of the fact that the code for the execute() method differs among the different command classes
In general, the Interpreter design pattern can be thought of as supporting multiple commands • Structurally, the Interpreter pattern is similar to the State and Strategy patterns because each class has the same method • Recall that in the State example, every door state had a touch method • In the strategy example, every kind of advisor had a recommend() method • With the interpreter, each kind of command will have an execute() method
The Interpreter design pattern is also closely related to the Composite design pattern • The Interpreter pattern will be used to create sequences of commands • Macros or subroutine calls in a program are subsequences of commands • A complex sequence of commands may consist of single commands as well as composite commands • A reasonably complete implementation of the Interpreter pattern will track the way things can be put together as individual leaf items, and composite nodes, where each of these is a type of component
Book definition: • The intent of the Interpreter pattern is to let you compose executable objects according to a set of composition rules that you define.
An Interpreter Example • The book illustrates the Interpreter design pattern using the robots that transport material between machines and that can load and unload the machines • The UML diagram on the following overhead shows a Robot class and a small hierarchy of commands • The command hierarchy is the same in structure as a composite hierarchy
In looking at the UML diagram, the following things should be noticed: • There is an abstract method, execute(), at the top of the command hierarchy • It is concretely implemented in the two subclasses • The CommandSequence class also has an add() method for building up composites • Otherwise, there are no other methods in the Command hierarchy
Also notice that the CarryCommand class has a constructor, which takes two machines as input parameters • The way a command is set up to work on robots/machines is by constructing it with the necessary references • There are corresponding instance variables for these references in the CarryCommand class • You can foresee that the implementation of the execute() method will work by calling the carry() method of the Robot class on these references
The next thing to ask is, “So, what part of this design is the interpreter?” • The answer is, the command hierarchy is in essence the interpreter • A program, named ShowInterpreter, will be given shortly, which illustrates the use of the hierarchy
A bunch of things are assumed in the code, but they’re not difficult • You obtain a machine composite object for the factory in Dublin, Ireland • You then obtain references to specific machines • You then write code to load bins into the machines
You then construct two instances of CarryCommand using these machines as input parameters • You then construct an instance of CommandSequence and add the individual commands • Finally, you call execute on the sequence object • It is this one, final line of code which controls the robot/machines in the factory
In essence, the whole machine control program has been packaged up in the one object and the one call to the execute() method • This is what the mean by “composing sequences of instructions” • The code is given on the next overhead
public class ShowInterpreter • { • public static void main(String[] args) • { • MachineCompositedublin = OozinozFactory.dublin(); • ShellAssembler assembler = (ShellAssembler) dublin.find("ShellAssembler:3302"); • StarPress press = (StarPress) dublin.find("StarPress:3404"); • Fuser fuser = (Fuser) dublin.find("Fuser:3102"); • assembler.load(new Bin(11011)); • press.load(new Bin(11015)); • CarryCommand carry1 = new CarryCommand(assembler, fuser); • CarryCommand carry2 = new CarryCommand(press, fuser); • CommandSequenceseq = new CommandSequence(); • seq.add(carry1); • seq.add(carry2); • seq.execute(); • } • }
Rather than showing the code for the command classes in advance, the book shows it after the example where it is used • The CommandSequence class has an ArrayList of commands • The add() method allows commands to be added
The execute() method looks like the methods, like isTree(), in the composite design pattern • It iterates over all of the elements of the ArrayList calling execute() on them • If the element is a leaf, then the version of execute() in the CarryCommand class is used • If the element is a composite, then the call is recursive • The code for the CommandSequence class is shown on the next overhead
public class CommandSequence extends Command • { • protected List commands = new ArrayList(); • public void add(Command c) • { • commands.add(c); • } • public void execute() • { • for (inti = 0; i < commands.size(); i++) { • Command c = (Command) commands.get(i); • c.execute(); • } • } • }
The CarryCommand class has references to two machines • It has a constructor that takes two machine references as input parameters • And it is has an execute() method • The execute() is implemented by making a call to carry() on the single instance of Robot maintained in the Robot class, passing the machine references as parameters to this method • The code is shown on the following overhead
public class CarryCommand extends Command • { • protected Machine fromMachine; • protected Machine toMachine; • public CarryCommand(Machine fromMachine, Machine toMachine) • { • this.fromMachine = fromMachine; • this.toMachine = toMachine; • } • public void execute() • { • Robot.singleton.carry(fromMachine, toMachine); • } • }
The next step in developing the Interpreter design pattern is to add more subclasses to the Command hierarchy • The idea is that each different command type, in its execute() method, will package up a different kind of action that could be taken in controlling machines in a factory • This expands and generalizes the kind of total program for machine control that the interpreter pattern can generate and package altogether
In addition to a CarryCommand, there might be a StartUpCommand and a ShutDownCommand • In addition to simple commands, there might be commands that reflect the structure of a programming language • This is where the complexity starts to set in
The tree-like structure of the composite makes it possible to simply model macros, namely subsequences of code • However, a complete programming language allows iteration • The book introduces the idea of a ForCommand into the Command hierarchy • The ForCommand class implements the logic of a for each loop • The UML diagram for the expanded interpreter is shown on the next overhead • Explanations follow
Most of the UML diagram is just a generalization of the previous one—with more Command classes added • The new aspect of the diagram is the arrow from the ForCommand class to the Command class • An instance of ForCommand will have a reference to another command • This other command constitutes the body of the for each loop
In general, you might expect a design pattern that starts to mimic the elements of a programming language to need a construct that is like a variable • The book points out that in particular, something that works like a for each loop depends on a variable
The syntax for a simple for each loop was given in Unit 9 of CS 202 with this example: • for(Cup7 someCup: mycollection) • { • myterminal.println(someCup); • } • mycollection was an ArrayList, for example, and someCup was a variable local to the loop which referred to the successive elements of mycollection