βοΈ Java Design-Pattern XXβ ’ - Command
βοΈ Java Design-Pattern XXβ
’ - Command
π
γJAVA μΈμ΄λ‘ λ°°μ°λ λμμΈ ν¨ν΄ : μ½κ² λ°°μ°λ GoFμ 23κ°μ§ λμμΈ ν¨ν΄γ
λ₯Ό μ½κ³ μ 리ν κΈμ λλ€.
Command
ν¨ν΄μ΄λ?
- ν΄λμ€λ μκΈ° μμ μ΄λ λ€λ₯Έ ν΄λμ€μ λ©μλλ₯Ό νΈμΆν΄μ μΌμ μ²λ¦¬νλ€.
- λ©μλλ₯Ό νΈμΆν κ²°κ³Όλ κ°μ²΄μ λ°μλμ§λ§, μΌν μ΄λ ₯μ μ΄λμλ λ¨μ§ μλλ€.
- μ΄λ΄ λ λͺ λ Ήμ νννλ ν΄λμ€κ° μμΌλ©΄ νΈλ¦¬νλ€.
- μ²λ¦¬νκ³ μΆμ μΌμ λ©μλ νΈμΆμ΄λΌλ λμ μΈ μ²λ¦¬λ‘ νννμ§ μκ³ , λͺ λ Ήμ λνλ΄λ ν΄λμ€μ μΈμ€ν΄μ€λΌλ νλμ κ°μ²΄λ‘ ννν μ μλ€.
- μ΄λ ₯μ κ΄λ¦¬νκ³ μΆμ λλ ν΄λΉ μΈμ€ν΄μ€μ μ§ν©μ κ΄λ¦¬νλ©΄ λλ€.
- λͺ λ Ήμ μ§ν©μ μ μ₯ν΄λλ©΄ κ°μ λͺ λ Ήμ μ¬μ€νν μλ μκ³ , μ¬λ¬ λͺ λ Ήμ λͺ¨μμ μλ‘μ΄ λͺ λ ΉμΌλ‘ μ¬μ΄μ©ν μλ μλ€.
- λμμΈ ν¨ν΄μμλ μ΄λ¬ν λͺ
λ Ήμ
Command
λΌλ μ΄λ¦μ λΆμμΌλ©°,Event
λΌκ³ λΆλ¦¬λ κ²½μ°λ μλ€. - κ°λ Ή λ§μ°μ€ ν΄λ¦μ΄λ ν€ μ λ ₯ λ±μ μ΄λ²€νΈκ° μΌμ΄λ¬μ λ κ·Έ μ¬κ±΄μ μΌλ¨ μΈμ€ν΄μ€λΌλ κ°μ²΄λ‘ λ§λ€μ΄ λκ³ , λ°μ μμλλ‘ λκΈ° νλ ¬μ λμ΄νλ€.
- κ·Έλ¦¬κ³ λμ΄λ μ΄λ²€νΈλ₯Ό μμλλ‘ μ²λ¦¬ν΄ λκ°λ€.
μμ νλ‘κ·Έλ¨
- λ€μμ κ°λ¨ν κ·Έλ¦Ό 그리기 μννΈμ¨μ΄λ₯Ό μ£Όμ λ‘ ν μμ νλ‘κ·Έλ¨μ΄λ€.
- λ§μ°μ€λ₯Ό λλκ·Ένλ©΄ λΉ¨κ°μ μ μΌλ‘ λ μ μ΄ κ·Έλ €μ§κ³ ,
[clear]
λ²νΌμ λλ₯΄λ©΄ λͺ¨λ μ μ΄ μ¬λΌμ§λ€. - μ¬μ©μκ° λ§μ°μ€λ₯Ό λλκ·Έν λλ§λ€ βμ΄ μμΉμ μ μ 그리μμ€βλΌλ λͺ
λ Ήμ΄
DrawCommand
ν΄λμ€μ μΈμ€ν΄μ€λ‘ μμ±λλ€.
Command
μΈν°νμ΄μ€
1
2
3
4
5
package command;
public interface Command {
public abstract void execute();
}
- λͺ λ Ήμ λνλΈλ€.
execute()
λ©μλλ₯Ό μ€ννμ λ ꡬ체μ μΌλ‘ μ΄λ€ μΌμ΄ μΌμ΄λ μ§λ ν΄λΉ μΈν°νμ΄μ€λ₯Ό ꡬνν ν΄λμ€κ° κ²°μ νλ€.
MacroCommand
ν΄λμ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package command;
import java.util.ArrayDeque;
import java.util.Deque;
public class MacroCommand implements Command {
/* λͺ
λ Ήμ λ°°μ΄ */
private Deque<Command> commands = new ArrayDeque<>();
/* μ€ν */
@Override
public void execute() {
for (Command cmd: commands) {
cmd.execute();
}
}
/* μΆκ° */
public void append(Command cmd) {
if (cmd == this) {
throw new IllegalArgumentException("infinite loop caused by append");
}
commands.push(cmd);
}
/* λ§μ§λ§ λͺ
λ Ήμ μμ */
public void undo() {
if (!commands.isEmpty()) {
commands.pop();
}
}
/* μ λΆ μμ */
public void clear() {
commands.clear();
}
}
- 볡μμ λͺ
λ Ήμ λͺ¨μ λͺ
λ Ήμ λνλ΄λ©°,
Command
μΈν°νμ΄μ€λ₯Ό ꡬννλ€. commands
νλλjava.util.Deque
μΈν°νμ΄μ€λ‘ μ¬λ¬Command
μ μΈμ€ν΄μ€λ₯Ό λͺ¨μ λκΈ° μν κ²μ΄λ€.- λν
Command
μΈν°νμ΄μ€μexecute()
λ©μλλ₯Ό ꡬννκ³ μλλ°, κ° μΈμ€ν΄μ€μexecute()
λ©μλλ₯Ό νΈμΆνκ³ μλ€. - μ΄μ©λ©΄ μ΄
for
루νμμ μ€ννλ €λCommand
κ° μλ‘μ΄MacroCommand
μ μΈμ€ν΄μ€μΌ μλ μλ€. - κ·Έλ° κ²½μ°μλ λ€μ κ·Έ μΈμ€ν΄μ€μ
execute()
λ©μλκ° νΈμΆλλ―λ‘ κ²°κ΅ λͺ¨λCommand
κ° μ€νλλ€. append()
λ©μλλMacroCommand
μΈμ€ν΄μ€μ μλ‘μ΄Command
μ μΈμ€ν΄μ€λ₯Ό μΆκ°νλ λ©μλμ΄λ€.- μΆκ°λλ
Command
μ μΈμ€ν΄μ€λ λ€λ₯ΈMacroCommand
μΈμ€ν΄μ€μΌ μλ μλλ°, μ€μλ‘ μκΈ° μμ μpush()
ν΄λ²λ¦¬λ©΄execute()
λ©μλκ° μμν λλμ§ μμΌλ―λ‘ μΈμλ₯Ό 체ν¬νλ€. undo()
λ©μλλ λ§μ§λ§μΌλ‘ μΆκ°νCommand
μΈμ€ν΄μ€λ₯Ό μ κ±°νλ€.clear()
λ©μλλ λͺ¨λ λͺ λ Ήμ μμ νλ€.
DrawCommand
ν΄λμ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package drawer;
import command.Command;
import java.awt.Point;
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);
}
}
Command
μΈν°νμ΄μ€λ₯Ό ꡬννμ¬, μ μ 그리λ λͺ λ Ήμ ννν ν΄λμ€μ΄λ€.- μ΄ ν΄λμ€μλ
drawable
,position
μ΄λΌλ λ κ°μ νλκ° μλ€. drawable
νλμλ 그리기λ₯Ό μ€νν λμμ μ μ₯νλ€.position
νλλ 그리기λ₯Ό μ€νν μμΉλ₯Ό λνλΈλ€.Point
ν΄λμ€λjava.awt
ν¨ν€μ§μ μ μλμ΄ μλ ν΄λμ€λ‘X
μ’νμY
μ’νλ₯Ό κ°λ 2μ°¨μ νλ©΄μ μμΉλ₯Ό λνλΈλ€.- μμ±μμμλ λ νλλ₯Ό μΈμλ‘ λ°μ, ν΄λΉ μμΉμ μ μ 그리λΌλ λͺ λ Ήμ μμ±νλ€.
execute()
λ©μλμμλdrawable
νλμdraw()
λ©μλλ₯Ό νΈμΆνλ€.
Drawable
μΈν°νμ΄μ€
1
2
3
4
5
package drawer;
public interface Drawable {
public abstract void draw(int x, int y);
}
- 그리λ λμμ λνλΈλ€.
draw()
λ 그리λ λ©μλμ΄λ€.
DrawCanvas
ν΄λμ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package drawer;
import command.MacroCommand;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics;
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;
}
/* μ΄λ ₯ μ 체 λ€μ 그리기 */
@Override
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);
}
}
Drawable
μΈν°νμ΄μ€λ₯Ό ꡬννλ ν΄λμ€λ‘,java.awt.Canvas
ν΄λμ€μ νμ ν΄λμ€μ΄λ€.- μμ μ΄ κ·Έλ €μΌ ν λͺ
λ Ή μ§ν©μ
history
νλμ μ μ₯λλ€. - μμ±μλ λλΉμ λμ΄, λλ‘μ λ΄μ©(
history
)μ λ°μμDrawCanvas
μ μΈμ€ν΄μ€λ₯Ό μ΄κΈ°ννλ€. - μ΄ μμμ νΈμΆνλ
setSize()
λsetBackground()
λ©μλλ κ°κ° ν¬κΈ°μ λ°°κ²½μμ μ§μ νλ€. paint()
λ©μλλDrawCanvas
λ₯Ό λ€μ 그릴 νμκ° μμ λ,java
μ²λ¦¬ μμ€ν μμ νΈμΆλλ λ©μλμ΄λ€.- ν΄μΌ ν μ²λ¦¬λ
history.execute()
λ₯Ό νΈμΆνλ κ² λΏμ΄λ€. - μ΄κ²λ§μΌλ‘λ
history
μ κΈ°λ‘λ λͺ λ Ή μ§ν©μ΄ λ€μ μ€νλλ€. draw()
λ©μλλDrawable
μΈν°νμ΄μ€λ₯Ό ꡬννκ³ μ μ μλ λ©μλμ΄λ€.- μ΄ μμμ
g.setColor()
λ©μλλ‘ μμμ μ§μ νκ³ ,g.fillOval()
λ©μλλ‘ μμ μ§μ νλ€.
Main
ν΄λμ€
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import command.*;
import drawer.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main extends JFrame implements MouseMotionListener, WindowListener {
/* 그리기 μ΄λ ₯ */
private MacroCommand history = new MacroCommand();
/* 그리λ μμ */
private DrawCanvas canvas = new DrawCanvas(400, 400, history);
/* μμ λ²νΌ */
private JButton clearButtonΒ = new JButton("clear");
/* μμ±μ */
public Main(String title) {
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(e -> {
history.clear();
canvas.repaint();
});
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();
setVisible(true);
}
/* MouseMotionListenerμ© */
@Override
public void mouseMoved(MouseEvent e) { }
@Override
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
/* WindowListenerμ© */
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
@Override public void windowActivated(WindowEvent e) {}
@Override public void windowClosed(WindowEvent e) {}
@Override public void windowDeactivated(WindowEvent e) {}
@Override public void windowDeiconified(WindowEvent e) {}
@Override public void windowIconified(WindowEvent e) {}
@Override public void windowOpened(WindowEvent e) {}
public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}
- μμ νλ‘κ·Έλ¨μ μ€ννκΈ° μν ν΄λμ€λ€.
history
νλλ 그리기 μ΄λ ₯μ μ μ₯νλ€.- μ΄λ λμ€μ
DrawCanvas
μΈμ€ν΄μ€μ μ λ¬νλ κ²κ³Ό λμΌνλ€. - μ¦ κ·Έλ¦¬κΈ° μ΄λ ₯μ
Main
μ μΈμ€ν΄μ€μDrawCanvas
μμ 곡μ λλ€κ³ λ³Ό μ μλ€. canvas
νλλ 그리λ μμμ΄λ€.clearButton
νλλ κ·Έλ¦° μ μ μ§μ°λ μμ λ²νΌμ΄κ³ ,JButton
ν΄λμ€λjavax.swing
ν¨ν€μ§ ν΄λμ€λ‘ λ²νΌμ ννν κ²μ΄λ€.- μμ±μμμλ λ§μ°μ€ ν΄λ¦ λ±μ μ΄λ²€νΈλ₯Ό λ°λ 리μ€λλ₯Ό μ€μ νκ³ μ»΄ν¬λνΈλ₯Ό λ°°μΉνκ³ μλ€.
- μ»΄ν¬λνΈ λ μ΄μμμ λ€μκ³Ό κ°λ€.
clearButton.addActionListener()
λ©μλλ₯Ό νΈμΆνλ κ³³μμ λλ€μμ μ΄μ©ν΄ 그리기 μ΄λ ₯μ μ§μ΄ νμ λ€μ 그리기 μ²λ¦¬λ₯Ό μ€μ νλ€.mouseMoved()
,mouseDragged()
λ©μλλMouseMotionListener
μΈν°νμ΄μ€λ₯Ό ꡬνν κ²μ΄λ€.- μ¬κΈ°μ λ§μ°μ€λ₯Ό λλκ·Έν λ (
mouseDragged
)μ μ΄ μμΉμ μ μ 그리λΌλ λͺ λ Ήμ λ§λ€μλ€. - λ§λ λͺ
λ Ήμ
history.append(cmd)
λ‘ μ€ν μ΄λ ₯μ μΆκ°ν νcmd.execute()
λ‘ μ¦μ μ€ννκ³ μλ€. window...
λ‘ μμνλ λ©μλ κ·Έλ£ΉμWindowListener
μΈν°νμ΄μ€λ₯Ό ꡬννκΈ° μν κ²μ΄λ€.- μ¬κΈ°μμλ μ’ λ£ μ²λ¦¬λ§ ꡬννλ€.
main()
λ©μλμμλMain
ν΄λμ€μ μΈμ€ν΄μ€λ₯Ό λ§λ€μ΄ μ€ννκ³ μλ€.
Command
ν¨ν΄μ λ±μ₯μΈλ¬Ό
Command
μΈν°νμ΄μ€
- λͺ
λ Ήμ μΈν°νμ΄μ€λ₯Ό μ μνλ λͺ
λ Ή(
Command
) μμ λ§‘μλ€.
MacroCommand
, DrawCommand
ν΄λμ€
Command
μμ μΈν°νμ΄μ€λ₯Ό ꡬννλ ꡬ체μ μΈ λͺ λ Ή(ConcreteCommand
) μμ λ§‘μλ€.
DrawCanvas
ν΄λμ€
Command
κ° λͺ λ Ήμ μ€νν λ λμμ΄ λλ€.- λͺ
λ Ήμ μμ μλ‘ λΆλ₯΄λ μμ μ(
Receiver
) μμ λ§‘μλ€.
Main
ν΄λμ€
ConcreateCommand
λ₯Ό μμ±νκ³ κ·Έ λReceiver
λ₯Ό ν λΉνλ μλ’°μ(Client
) μμ λ§‘μλ€.Main
ν΄λμ€λ λ§μ°μ€μ λλκ·Έμ λ§μΆμ΄DrawCommand
μΈμ€ν΄μ€λ₯Ό μμ±νλ€.
Main
, DrawCanvas
ν΄λμ€
Command
μΈν°νμ΄μ€λ₯Ό νΈμΆνμ¬ λͺ λ Ή μ€νμ μμνλ νΈμΆμ(Invoker
) μμ λ§‘μλ€.- μ΄ λ ν΄λμ€λ
Command
μΈν°νμ΄μ€μexecute()
λ©μλλ₯Ό νΈμΆνλ€.
μ± μμ μ μνλ ννΈ
λͺ λ Ήμ΄ κ°μ ΈμΌ νλ μ 보λ?
- λͺ λ Ήμ΄ μ΄λ μ λμ μ 보λ₯Ό κ°μ§ μ§λ λͺ©μ μ λ°λΌ λ¬λΌμ§λ€.
DrawCommand
ν΄λμ€μλ 그리λ μ μ μμΉ μ λ³΄λ§ κ°κ³ μμ λΏ, μ μ ν¬κΈ°λ μμ, λͺ¨μ λ±μ μ 보λ μλ€.- λν μ΄λ²€νΈ λ°μ μκ° μ 보λ₯Ό κ°κ³ μλ€λ©΄ λ¨μν κ·Έλ¦¬κΈ°κ° μλλΌ λ§μ°μ€ λμμ μκΈκΉμ§ μ¬νν μ μλ€.
DrawCommand
ν΄λμ€μλ 그리λ λμμ λνλ΄λdrawable
νλλ μλ€.- μμ νλ‘κ·Έλ¨μμ
DrawCanvas
μΈμ€ν΄μ€λ νλ λΏμ΄κ³ , λͺ¨λ 그리기λ κ·Έκ³³μμ μ΄λ£¨μ΄μ§κΈ° λλ¬Έμ, μ΄drawable
νλκ° ν¬κ² μλ―Έκ° μλ€. - κ·Έλ¬λ 그리λ λμμ΄ μ¬λ¬ κ° μ‘΄μ¬νλ νλ‘κ·Έλ¨μΌ κ²½μ° μ΄λ° νλκ° λμμ΄ λλ€.
ConcreateCommand
μ μμ μ΄Receiver
μμ μκ³ μμ΄μConcreateCommand
μμ λκ° κ΄λ¦¬νκ³ κ°μ§κ³ μλ μΈμ λ μ§execute()
ν μ μκΈ° λλ¬Έμ΄λ€.
μ΄λ ₯μ μ μ₯
- μμ νλ‘κ·Έλ¨μμλ 그리기 μ΄λ ₯μ
MacroCommand
μΈμ€ν΄μ€μhistory
μ μ μ₯νκ³ μλ€. - μ΄ μΈμ€ν΄μ€λ μ§κΈκΉμ§ κ·Έλ¦° μ 보λ₯Ό λͺ¨λ κ°μ§κ³ μμΌλ©°, νμΌλ‘ μ μ μ₯ν΄λλ©΄ 그리기 μ΄λ ₯μ΄ λ³΄μ‘΄λλ€.
μ΄λν°
- μμ νλ‘κ·Έλ¨μ
Main
ν΄λμ€μλ λ κ°μ μΈν°νμ΄μ€λ₯Ό ꡬννλλ°, μΈν°νμ΄μ€μ λ©μλ μ€μ μ€μ λ‘ μ¬μ©νλ κ²μ κ·Έ μΌλΆμ΄λ€. - κ°λ Ή
MouseMotionListener
λ©μλ μ€μμ μ¬μ©νλ κ²μmouseDragged()
λ©μλ λΏμ΄λ€. - λ νλ μλ₯Ό λ€λ©΄,
WindowListener
μμλ 7κ°μ λ©μλ μ€windowClosing()
λ©μλλ§ μ¬μ©νλ€. - νλ‘κ·Έλλ°μ κ°κ²°νκ² νκΈ° μν΄ μ΄λν° ν΄λμ€λ€μ΄
java.awt.event
ν¨ν€μ§μ μ€λΉλμ΄ μλ€. MouseMotionListener
μΈν°νμ΄μ€μλMouseMotionAdapter
ν΄λμ€,WindowListener
μΈν°νμ΄μ€μλWindowAdapter
ν΄λμ€κ° μ€λΉλμ΄ μλ€.- μ΄λ¬ν μ΄λν°λ
Adapter
ν¨ν΄μ ν μμ΄λ€. MouseMotionAdapter
μΈν°νμ΄μ€λ‘ μλ₯Ό λ€μ΄ 보면, μ΄ ν΄λμ€λMouseMotionListener
μΈν°νμ΄μ€κ° μꡬνλ λ©μλλ₯Ό λͺ¨λ ꡬννμ§λ§, κ·Έ λ΄μ©μ λͺ¨λ λΉμ΄μλ€.- λ°λΌμ
MouseMotionAdapter
μ νμ ν΄λμ€λ₯Ό λ§λ€κ³ νμν λ©μλλ§ κ΅¬ννλ©΄ λͺ©μ μ λ¬μ±ν μ μλ€. - νΉν
Java
μ μ΅λͺ ν΄λμ€λ₯Ό μ‘°ν©ν΄μ μ΄λν°λ₯Ό μ¬μ©νλ©΄ νμΈ΅ λ νλ‘κ·Έλ¨μ μ€λ§νΈνκ² μμ±ν μ μλ€.
MouseMotionListener
λ₯Ό μ¬μ©νλ κ²½μ°
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main extends JFrame implements MouseMotionListener, WindowListener {
// ...
public Main(String title) {
// ...
canvas.addMouseMotionListener(this);
// ...
}
public void mouseMoved(MouseEvent e) { }
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
// ...
}
MouseMotionAdapter
λ₯Ό μ¬μ©νλ κ²½μ°
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main extends JFrame implements WindowListener {
// ...
public Main(String title) {
// ...
canvas.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
});
// ...
}
// ...
}
- μ΅λͺ ν΄λμ€ κ΅¬λ¬Έμ μ΅μνμ§ μμΌλ©΄ μ½κΈ° μ΄λ ΅μ§λ§, μ£Όμ κΉκ² μ΄ν΄λ³΄λ©΄ λ€μκ³Ό κ°μ μ¬μ€μ μ μ μλ€.
new MouseMotionAdapter()
λ λ§μΉ μΈμ€ν΄μ€λ₯Ό λ§λλ μκ³Ό λΉμ·νλ€.- λ€μ μ΄μ΄μ§λ
{...}
λ λ©μλ μ μμ λΉμ·νλ€.
MouseMotionAdapter
ν΄λμ€μ νμ ν΄λμ€λ₯Ό λ§λ€μ΄ μΈμ€ν΄μ€λ₯Ό μμ±νλ€.- μ€λ²λΌμ΄λν λ©μλλ§ κ΅¬ννλ©΄, λλ¨Έμ§λ μ무κ²λ μΈ νμκ° μμΌλ―λ‘ μ©μ΄νλ€.
This post is licensed under CC BY 4.0 by the author.