代码Github连接 :https://github.com/tangbi123/Tab-Design-Pattern
Command模式
命令也是类
每一项想做的工作不再是“方法的调用”这种动态处理,而是一个表示命令的类的实例,既可以用“物”来表示。要想管理工作的历史记录,只需管理这些事例的集合即可,而且还可以随时再次执行过去的命令,或是将多个过去的命令整合为一个新命令并执行。
Command也被称为事件。
1、示例
代码清单
1)Command
public interface Command {
public abstract void execute();
}
2)MacroCommand
public class MacroCommand implements Command{
private Stack commands = new Stack();
@Override
public void execute() {
Iterator it =commands.iterator();
while(it.hasNext()){
((Command)it.next()).execute();
}
}
public void append(Command cmd){
if(cmd != this){
commands.push(cmd);
}
}
public void undo(){
if(!commands.empty()){
commands.pop();
}
}
public void clear(){
commands.clear();
}
}
3)DrawCommand
public class DrawCommand implements Command{
protected Drawable drawable;
private Point position;
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
@Override
public void execute() {
drawable.draw(position.x, position.y);
}
}
4)Drawable
public interface Drawable {
public abstract void draw(int x, int y);
}
5)DrawCanvas
public class DrawCanvas extends Canvas implements Drawable{
private Color color = Color.red;
private int radius = 6;
private MacroCommand history;
public DrawCanvas(int width, int height, MacroCommand history){
setSize(width, height);
setBackground(Color.white);
this.history = history;
}
public void paint(Graphics g){
history.execute();
}
@Override
public void draw(int x, int y) {
Graphics g= getGraphics();
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
6)CommandMain
public class CommandMain extends JFrame implements ActionListener,
MouseMotionListener, WindowListener {
private MacroCommand history = new MacroCommand();
private DrawCanvas canvas = new DrawCanvas(400,400,history);
private JButton clearButton = new JButton("clear");
public CommandMain(String title){
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
pack();
show();
}
public static void main(String[] args) {
new CommandMain("Command Pattern Sample");
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == clearButton){
history.clear();
canvas.repaint();
}
}
@Override
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
@Override
public void mouseMoved(MouseEvent e) {
}
@Override
public void windowOpened(WindowEvent e) {
}
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
@Override
public void windowClosed(WindowEvent e) {
}
@Override
public void windowIconified(WindowEvent e) {
}
@Override
public void windowDeiconified(WindowEvent e) {
}
@Override
public void windowActivated(WindowEvent e) {
}
@Override
public void windowDeactivated(WindowEvent e) {
}
}
时序图
2、角色
1)Command
负责定义接口(API)
2)ConcreteCommand
负责实现在Command角色中定义的接口(API)
3)Receiver
Command角色执行命令时的对象,命令接收者
4)Client
负责生成ConcreteCommand角色并分配Receiver角色。
相应鼠标拖动事件时,他声称了DrawCommand类的实例,并将攀岩Receiver角色的DrawCanvas类的实例传递给了DrawCommand类的构造函数。
5)Invoker
开始执行命令的角色,它会调用在Command角色中定义的接口(API)
3、思路要点
命令中应该包含那些信息
没有绝对答案,命令的目的不同,包含的信息也不同。
加入保存了事件发生的时间戳,那么重新绘制时,也可以重现用户鼠标操作的缓急。
保存历史记录
MacroCommand保存绘制的历史记录
适配器
Interpreter模式
语法规则也是类
解释器:翻译程序会理解迷你语言,并解释和运行迷你程序。
迷你语言语法:
BFN时Backus-Naur Form 或 Backus Normal Form的略称,他经常被用于描述语法。
1)program + command
<program> ::= program <command list>
ex: program go
2)多个命令 + end
<command list> ::= <command>* end
program go right go right go right end
3)command 的定义
<command> ::= <repeat command> | <primitive command>
4)循环命令 < repeat command>
<repeat command> ::= repeat <number> <command list>
program repeat 4 repeat 3 go right go left end right end end
5)基本命令 < rimitive command>
<primitive command> ::= go | right | left
6)终结符表达式与非终结符表达式
终结符表达式:像< primitive command>不会被进一步展开的表达式。(Nonterminal Expression)
非终结符表达式:像< program> 和< command> 需要被进一步展开的表达式。
1、示例
实现了一个迷你程序的语法解析器,将其推到成语法树进行处理,就是语法解析。
代码清单
1)Context
public class Context {
private StringTokenizer tokenizer;
private String currentToken;
public Context(String text){
tokenizer = new StringTokenizer(text);
nextToken();
}
public String nextToken(){
if(tokenizer.hasMoreTokens()){
currentToken = tokenizer.nextToken();
}else{
currentToken = null;
}
return currentToken;
}
public String currentToken(){
return currentToken;
}
public void skipToken(String token)throws ParseException{
if(!token.equals(currentToken)){
throw new ParseException("Warning:" + token + "is expected,but" +
currentToken + "is not found.,");
}
nextToken();
}
public int currentNumber()throws ParseException{
int number = 0;
try{
number = Integer.parseInt(currentToken);
}catch (NumberFormatException e){
throw new ParseException("Warning:" + e);
}
return number;
}
}
2)ParseException
public class ParseException extends Exception{
public ParseException(String msg){
super(msg);
}
}
3)Node
public abstract class Node {
public abstract void parse(Context context)throws ParseException;
}
4)ProgramNode
public class ProgrameNode extends Node{
private Node commandListNode;
@Override
public void parse(Context context) throws ParseException {
context.skipToken("program");
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString(){
return "[program" + commandListNode + "]";
}
}
5)CommandListNode
public class CommandListNode extends Node{
private ArrayList list = new ArrayList();
public void parse(Context context)throws ParseException{
while(true){
if(context.currentToken() == null){
throw new ParseException("Missing ‘end'");
}
else if(context.currentToken().equals("end")){
context.skipToken("end");
break;
}
else{
Node commandNode = new CommandNode();
commandNode.parse(context);
list.add(commandNode);
}
}
}
public String toString(){
return list.toString();
}
}
6)CommandNode
public class CommandNode extends Node{
private Node node;
@Override
public void parse(Context context) throws ParseException {
if(context.currentToken().equals("repeat")){
node = new RepeatCommandNode();
node.parse(context);
}else{
node = new PrimitiveCommandNode();
node.parse(context);
}
}
public String toString(){
return node.toString();
}
}
7)RepeatCommandNode
public class RepeatCommandNode extends Node{
private int number;
private Node commandListNode;
@Override
public void parse(Context context) throws ParseException {
context.skipToken("repeat");
number = context.currentNumber();
context.nextToken();
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString(){
return "[repeat " + number + " " + commandListNode + "]";
}
}
8)PrimitiveCommandNode
public class PrimitiveCommandNode extends Node{
private String name;
@Override
public void parse(Context context) throws ParseException {
name = context.currentToken();
context.skipToken(name);
if(!name.equals("go") && !name.equals("right") && !name.equals("left")){
throw new ParseException(name + "is undefined");
}
}
public String toString(){
return name;
}
}
9)program.txt
program end
program go end
program go right go right go right go right end
program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end
10)Main
public class InterpreterMain {
public static void main(String[] args) {
try{
BufferedReader reader = new BufferedReader(
//注意文件的位置
new FileReader("src/Interpreter/program.txt")
);
String text;
while((text = reader.readLine()) != null){
System.out.println("text = \"" + text + "\"");
Node node = new ProgrameNode();
node.parse(new Context(text));
System.out.println("node = " + node);
}
reader.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
2、角色
1)AbstractException(抽象表达式)
定义了语法树节点的共同接口(API). Node => parse
2)TerminalExpression(终结符表达式)
对应BNF中的终结符表达式。 =》 PrimitiveCommandNode
3)NonterminalExperssion(非终结符表达式)
对应BNF中的非终结符表达式。 ProgramNode、CommandNode…
4)Context
为解析器进行语法解析提供了必要信息。
5)Client
为了推到语法树,Client角色会调用TerminalExpression角色和NonterminalExperssion角色。
3、思路要点
还有其他那些迷你语言
正则表达式
检索表达式
批处理语言
跳过标记还是读取标记
在制作解析器时,经常会出现多读了一个标记或是漏读了一个标记的Bug。