Java

[Design Pattern] 명령 패턴(Command Pattern)과 통역 패턴(Interpreter Pattern)

구루싸 2020. 7. 24. 21:04
반응형
SMALL

이번 학습 주제는

명령(Command) 패턴과

통역(Interpreter) 패턴입니다

클래스가 일을 실행할 때에는

메소드를 호출합니다

메소드를 호출한 결과는

오브젝트의 상태에 반영되지만

일의 이력은 남지 않습니다

이 때 명령을 표현하는

클래스가 있어서

인스턴스의 집합을 관리한다면

이력을 관리할 수 있습니다

명령 패턴은

이러한 처리를 할 수 있는

패턴입니다

그럼 명령 패턴의

역할들을 정리하고

시작하겠습니다

역할 설명
Command(명령) 명령의 인터페이스를 정의
Concrete Command(구체적 명령) Command 역할의 인터페이스를 실제로 구현
Receiver(수신자) Command 역할이 명령을 실행할 때 대상이 되는 역할
Client(의뢰자) Concrete Command 역할을 생성하고 그 사이에 Receiver 역할을 할당
Invoker(기동자) 명령의 행동을 개시하는 역할로 Command 역할에서 정의되는 인터페이스를 호출

역할들이 실제로

어떻게 작동하는지

구현해보겠습니다

1. 인터페이스(Interface) Command.java

package commandPattern.command;

public interface Command {
	public abstract void execute();
}

2. 클래스(Class) MacroCommand.java

package commandPattern.command;
import java.util.*;
public class MacroCommand implements Command {
	private Stack<Command> commands = new Stack<Command>();
	public void execute() {
		Iterator<Command> 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. 인터페이스(Interface) Drawable.java

package commandPattern.drawer;
import java.awt.*;
public interface Drawable {
	public abstract void init();
	public abstract void draw(int x, int y);
	public abstract void setColor(Color color);
}

4. 클래스(Class) DrawCanvas.java

package commandPattern.drawer;
import commandPattern.command.*;
import java.awt.*;
public class DrawCanvas extends Canvas implements Drawable {
	private Color color;
	private int radius;
	private MacroCommand history;
	public DrawCanvas(int width, int height, MacroCommand history) {
		setSize(width, height);
		setBackground(Color.white);
		this.history = history;
		init();
	}
	public void paint(Graphics g) {
		history.execute();
	}
	public void init() {
		color = Color.red;
		radius = 6;
	}
	public void draw(int x, int y) {
		Graphics g = getGraphics();
		g.setColor(color);
		g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
	}
	public void setColor(Color color) {
		this.color = color;
	}
}

5. 클래스(Class) DrawCommand.java

package commandPattern.drawer;
import commandPattern.command.*;
import java.awt.*;
public class DrawCommand implements Command {
	protected Drawable drawable;
	private Point position;
	public DrawCommand(Drawable drawable, Point position) {
		this.drawable = drawable;
		this.position = position;
	}
	public void execute() {
		drawable.draw(position.x, position.y);
	}
}

6. 클래스(Class) ColorCommand.java

package commandPattern.drawer;
import commandPattern.command.*;
import java.awt.*;
public class ColorCommand implements Command {
	protected Drawable drawable;
	private Color color;
	public ColorCommand(Drawable drawable, Color color) {
		this.drawable = drawable;
		this.color = color;
	}
	public void execute() {
		drawable.setColor(color);
	}
}

7. 클래스(Class) Test.java

import commandPattern.command.*;
import commandPattern.drawer.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test 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");
	private JButton redButton = new JButton("red");
	private JButton blueButton = new JButton("blue");
	private JButton greenButton = new JButton("green");
	private JButton undoButton = new JButton("undo");
	public Test(String title) {
		super(title);
		this.addWindowListener(this);
		canvas.addMouseMotionListener(this);
		clearButton.addActionListener(this);
		redButton.addActionListener(this);
		blueButton.addActionListener(this);
		greenButton.addActionListener(this);
		undoButton.addActionListener(this);
		Box buttonBox = new Box(BoxLayout.X_AXIS);
		buttonBox.add(clearButton);
		buttonBox.add(redButton);
		buttonBox.add(blueButton);
		buttonBox.add(greenButton);
		buttonBox.add(undoButton);
		Box mainBox = new Box(BoxLayout.Y_AXIS);
		mainBox.add(buttonBox);
		mainBox.add(canvas);
		getContentPane().add(mainBox);
		
		pack();
		show();
	}
	public void actionPerformed(ActionEvent e) {
		if ( e.getSource() == clearButton ) {
			history.clear();
			canvas.init();
			canvas.repaint();
		} else if ( e.getSource() == redButton ) {
			Command cmd = new ColorCommand(canvas, Color.red);
			history.append(cmd);
			cmd.execute();
		} else if ( e.getSource() == blueButton ) {
			Command cmd = new ColorCommand(canvas, Color.blue);
			history.append(cmd);
			cmd.execute();
		} else if ( e.getSource() == greenButton ) {
			Command cmd = new ColorCommand(canvas, Color.green);
			history.append(cmd);
			cmd.execute();
		} else if ( e.getSource() == undoButton ) {
			history.undo();
			canvas.repaint();
		}
	}
	public void mouseMoved(MouseEvent e) {
	}
	public void mouseDragged(MouseEvent e) {
		Command cmd = new DrawCommand(canvas, e.getPoint());
		history.append(cmd);
		cmd.execute();
	}
	public void windowClosing(WindowEvent e) {
		System.exit(0);
	}
	public void windowActivated(WindowEvent e) {
	}
	public void windowClosed(WindowEvent e) {
	}
	public void windowDeactivated(WindowEvent e) {
	}
	public void windowDeiconified(WindowEvent e) {
	}
	public void windowIconified(WindowEvent e) {
	}
	public void windowOpened(WindowEvent e) {
	}
	public static void main(String[] args) {
		new Test("Command Pattern Sample");
	}
}

간단하게 그림을 그리는

프로그램이 완성되었습니다

다음으로

통역(Interpreter)

패턴을 알아보겠습니다

디자인 패턴의 목적 중

하나는 클래스의 재이용성을

높이기 위해서입니다

통역 패턴에서는

프로그램이 해결하려는 문제를

미니 언어로 표현하고

미니 언어로 기술된

미니 프로그램을 표현합니다

아무튼 역할들을

정리하고 시작하겠습니다

역할 설명
Abstract Expression(추상적인 표현) 구문 트리의 노드에 공통의 인터페이스를 결정하는 역할
Terminal Expression(종착점 표현) BNF의 Terminal Expression에 대응하는 역할
Nonterminal Expression(비종착점 표현) BNF의 Nonterminal Expression에 대응하는 역할
Context(문맥) 인터프리터가 구문해석을 실행하기 위한 정보를 제공
Client(의뢰자) 구문 트리를 조립하기 위해서 Terminal Expression과 Nontermanal Expression을 호출하는 역할

역할만 봤을 때

엄청 어려울 것 같은 느낌이-_-

아무튼 구현해보겠습니다

1. 추상 클래스(Abstract Class) Node.java

package interpreterPattern;

public abstract class Node {
	public abstract void parse(Context context) throws ParseException;
}

2. 클래스(Class) Context.java

package interpreterPattern;
import java.util.*;
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 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;
	}
}

3. 클래스(Class) ParseException.java

package interpreterPattern;

public class ParseException extends Exception {
	public ParseException(String msg) {
		super(msg);
	}
}

4. 클래스(Class) ProgramNode.java

package interpreterPattern;
/* <program> ::= program <command list> */
public class ProgramNode extends Node {
	private Node commandListNode;
	public void parse(Context context) throws ParseException {
		context.skipToken("program");
		commandListNode = new CommandListNode();
		commandListNode.parse(context);
	}
	public String toString() {
		return "[program " + commandListNode + "]";
	}
}

5. 클래스(Class) CommandNode.java

package interpreterPattern;
/* <command> ::= <repeat command> | <primitive command> */
public class CommandNode extends Node {
	private Node node;
	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();
	}
}

6. 클래스(Class) CommandListNode.java

package interpreterPattern;
import java.util.*;
/* <command list> ::= <command>* end */
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();
	}
}

7. 클래스(Class) PrimitiveCommandNode.java

package interpreterPattern;
/* <primitive command> ::= go | right | left */
public class PrimitiveCommandNode extends Node {
	private String name;
	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;
	}
}

8. 클래스(Class) RepeatCommandNode.java

package interpreterPattern;
/* <repeat command> ::= repeat <number> <command list> */
public class RepeatCommandNode extends Node {
	private int number;
	private Node commandListNode;
	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 + "]";
	}
}

9. 클래스(Class) Test.java

import interpreterPattern.*;
import java.io.*;
public class Test {
	public static void main(String[] args) {
		try {
			BufferedReader reader = new BufferedReader(new FileReader("program.txt"));
			String text;
			while ( (text = reader.readLine()) != null ) {
				System.out.println("text = \"" + text + "\"");
				Node node = new ProgramNode();
				node.parse(new Context(text));
				System.out.println("node = " + node);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

이 프로그램을 돌려보시려면

아래의 program.txt

파일을 다운 받으시고

실행시키면 되는데요

program.zip
0.00MB

txt파일의 내용은

BNF 표기법의 변형인데

Backus-Naur Form 또는

Backus Normal Form입니다

이 프로그램의 인터프리터가

해석하는 미니 언어의 '문법'은

아래와 같습니다

<program> ::= program <command list>
<command list> ::= <command>* end
<command> ::= <repeat command> | <primitive command>
<repeat command> ::= repeat <number> <command list>
<primitive command> ::= go | right | left

컴파일러에 대해

좀 아시는 분들은

쉽게 이해하실 것 같네요^^

이것으로

이번 학습을 마치겠습니다

그럼 이만-_-

 

반응형
LIST