이번 학습 주제는
명령(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
파일을 다운 받으시고
실행시키면 되는데요
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
컴파일러에 대해
좀 아시는 분들은
쉽게 이해하실 것 같네요^^
이것으로
이번 학습을 마치겠습니다
그럼 이만-_-
'Java' 카테고리의 다른 글
[Java] 스프링(Spring) 환경 세팅하기 (0) | 2020.09.23 |
---|---|
[Java] 직렬화(Serialization)와 역직렬화(Deserialization) (0) | 2020.08.20 |
[Design Pattern] 대리인 패턴(Proxy Pattern) (0) | 2020.07.22 |
[Design Pattern] 플라이급 패턴(Flyweight Pattern) (0) | 2020.07.21 |
[Design Pattern] 상태 패턴(State Pattern) (0) | 2020.07.20 |