옵션 (3) 끝까지. 보기를 표시하는 것을 포함하여보기를 "제어"하는 것은 발표자의 작업입니다. 예,이를 허용하려면 뷰의 인터페이스에 추가해야하지만 큰 문제는 아닙니다. 가능한 한 수동적 인 뷰를 만들 수 있습니다. 논리가 전혀 없습니다!
작업 예 :
나는 MVC 아키텍처를 사용하는 간단한 스윙 게임
의이 예제
를 우연히 발견했습니다 . MVC 대신 MVP를 사용하여 스윙 앱을 작성했기 때문에이 예제가 MVC의 진실하고 순수한 예제인지 권위있게 말할 수 없습니다. 그것은 나에게 괜찮아 보이며, 저자
trashgod
는 Swing을 사용하여 여기에서 자신을 입증 한 것 이상으로 합리적이라고 받아 들일 것입니다.연습으로 MVP 아키텍처를 사용하여 다시 작성하기로 결정했습니다.
드라이버 :
아래 코드에서 볼 수 있듯이 이것은 매우 간단합니다. 당신에게 튀어 나와야 할 것은 (생성자를 검사하여) 관심사 분리입니다.
-
모델
클래스는 독립형이며, 뷰 또는 발표자의 지식이 없습니다. -
보기
인터페이스는 독립형 GUI 클래스에 의해 구현하지, 어느 쪽도 어느 모델이나 발표자의 지식을 가지고있다. -
발표자
클래스는 모델과 뷰 모두에 대해 알고있다.
암호:
import java.awt.*;
/**
* MVP version of https://stackoverflow.com/q/3066590/230513
*/
public class MVPGame implements Runnable
{
public static void main(String[] args)
{
EventQueue.invokeLater(new MVPGame());
}
@Override
public void run()
{
Model model = new Model();
View view = new Gui();
Presenter presenter = new Presenter(model, view);
presenter.start();
}
}
그리고 게임에 사용할 GamePiece :
import java.awt.*;
public enum GamePiece
{
Red(Color.red), Green(Color.green), Blue(Color.blue);
public Color color;
private GamePiece(Color color)
{
this.color = color;
}
}
모델 :
주로 모델 의 역할은 다음과 같습니다.
- UI에 대한 데이터 제공 (요청시)
- 데이터 유효성 검사 (요청시)
- 데이터 장기 저장 (요청시)
암호:
import java.util.*;
public class Model
{
private static final Random rnd = new Random();
private static final GamePiece[] pieces = GamePiece.values();
private GamePiece selection;
public Model()
{
reset();
}
public void reset()
{
selection = pieces[randomInt(0, pieces.length)];
}
public boolean check(GamePiece guess)
{
return selection.equals(guess);
}
public List<GamePiece> getAllPieces()
{
return Arrays.asList(GamePiece.values());
}
private static int randomInt(int min, int max)
{
return rnd.nextInt((max - min) + 1) + min;
}
}
보기 :
여기서 아이디어는 가능한 한 많은 애플리케이션 로직을 제거하여 가능한 한 "멍청한"것으로 만드는 것입니다 (목표는 아무것도 갖지 않는 것입니다). 장점 :
JUnit
스윙 코드와 혼합 된 애플리케이션 로직이 없으므로 이제 앱을 100 % 테스트 할 수 있습니다.- 전체 앱을 실행하지 않고도 GUI를 실행할 수 있으므로 프로토 타이핑이 훨씬 빨라집니다.
암호:
import java.awt.*;
import java.awt.event.*;
import java.util.List;
public interface View
{
public void addPieceActionListener(GamePiece piece, ActionListener listener);
public void addResetActionListener(ActionListener listener);
public void setGamePieces(List<GamePiece> pieces);
public void setResult(Color color, String message);
}
및 GUI :
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import javax.swing.*;
/**
* View is "dumb". It has no reference to Model or Presenter.
* No application code - Swing code only!
*/
public class Gui implements View
{
private JFrame frame;
private ColorIcon icon;
private JLabel resultLabel;
private JButton resetButton;
private JButton[] pieceButtons;
private List<GamePiece> pieceChoices;
public Gui()
{
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
icon = new ColorIcon(80, Color.WHITE);
}
public void setGamePieces(List<GamePiece> pieces)
{
this.pieceChoices = pieces;
frame.add(getMainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public void setResult(Color color, String message)
{
icon.color = color;
resultLabel.setText(message);
resultLabel.repaint();
}
private JPanel getMainPanel()
{
JPanel panel = new JPanel(new BorderLayout());
panel.add(getInstructionPanel(), BorderLayout.NORTH);
panel.add(getGamePanel(), BorderLayout.CENTER);
panel.add(getResetPanel(), BorderLayout.SOUTH);
return panel;
}
private JPanel getInstructionPanel()
{
JPanel panel = new JPanel();
panel.add(new JLabel("Guess what color!", JLabel.CENTER));
return panel;
}
private JPanel getGamePanel()
{
resultLabel = new JLabel("No selection made", icon, JLabel.CENTER);
resultLabel.setVerticalTextPosition(JLabel.BOTTOM);
resultLabel.setHorizontalTextPosition(JLabel.CENTER);
JPanel piecePanel = new JPanel();
int pieceCount = pieceChoices.size();
pieceButtons = new JButton[pieceCount];
for (int i = 0; i < pieceCount; i++)
{
pieceButtons[i] = createPiece(pieceChoices.get(i));
piecePanel.add(pieceButtons[i]);
}
JPanel panel = new JPanel(new BorderLayout());
panel.add(resultLabel, BorderLayout.CENTER);
panel.add(piecePanel, BorderLayout.SOUTH);
return panel;
}
private JPanel getResetPanel()
{
resetButton = new JButton("Reset");
JPanel panel = new JPanel();
panel.add(resetButton);
return panel;
}
private JButton createPiece(GamePiece piece)
{
JButton btn = new JButton();
btn.setIcon(new ColorIcon(16, piece.color));
btn.setActionCommand(piece.name());
return btn;
}
public void addPieceActionListener(GamePiece piece, ActionListener listener)
{
for (JButton button : pieceButtons)
{
if (button.getActionCommand().equals(piece.name()))
{
button.addActionListener(listener);
break;
}
}
}
public void addResetActionListener(ActionListener listener)
{
resetButton.addActionListener(listener);
}
private class ColorIcon implements Icon
{
private int size;
private Color color;
public ColorIcon(int size, Color color)
{
this.size = size;
this.color = color;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(color);
g2d.fillOval(x, y, size, size);
}
@Override
public int getIconWidth()
{
return size;
}
@Override
public int getIconHeight()
{
return size;
}
}
}
당장 명확하지 않은 것은 View 인터페이스가 얼마나 커질 수 있는지입니다. GUI의 각 Swing 구성 요소에 대해 다음을 수행 할 수 있습니다.
- 많은 유형 (ActionListener, FocusListener, MouseListener 등)이있는 구성 요소에 리스너를 추가 / 제거합니다.
- 구성 요소의 데이터 가져 오기 / 설정
- 구성 요소의 "사용성"상태 (활성화, 표시, 편집 가능, 초점 가능 등)를 설정합니다.
이것은 정말 빨리 다루기 어려울 수 있습니다. 해결책으로 (이 예에는 표시되지 않음) 각 필드에 대해 키가 생성되고 GUI가 해당 키로 각 구성 요소를 등록합니다 (HashMap 사용). 그런 다음 View 대신 다음과 같은 메서드를 정의합니다.
public void addResetActionListener(ActionListener listener);
// and then repeat for every field that needs an ActionListener
단일 방법이 있습니다.
public void addActionListener(SomeEnum someField, ActionListener listener);
여기서 "SomeEnum"은
enum
주어진 UI의 모든 필드를 정의 하는 입니다. 그런 다음 GUI가 해당 호출을 수신하면 해당 메서드를 호출 할 적절한 구성 요소를 찾습니다. 이 모든 무거운 작업은 View를 구현하는 추상 수퍼 클래스에서 수행됩니다.
발표자 :
책임은 다음과 같습니다.
- 시작 값으로보기 초기화
- 적절한 리스너를 연결하여 View의 모든 사용자 상호 작용에 응답합니다.
- 필요할 때마다보기 상태 업데이트
- 뷰에서 모든 데이터를 가져와 저장을 위해 모델로 전달 (필요한 경우)
코드 (여기에는 스윙이 없습니다) :
import java.awt.*;
import java.awt.event.*;
public class Presenter
{
private Model model;
private View view;
public Presenter()
{
System.out.println("ctor");
}
public Presenter(Model model, View view)
{
this.model = model;
this.view = view;
}
public void start()
{
view.setGamePieces(model.getAllPieces());
reset();
view.addResetActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
reset();
}
});
for (int i = 0; i < GamePiece.values().length; i++)
{
final GamePiece aPiece = GamePiece.values()[i];
view.addPieceActionListener(aPiece, new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
pieceSelected(aPiece);
}
});
}
}
private void reset()
{
model.reset();
view.setResult(Color.GRAY, "Click a button.");
}
private void pieceSelected(GamePiece piece)
{
boolean valid = model.check(piece);
view.setResult(piece.color, valid ? "Win!" : "Keep trying.");
}
}
MVP 아키텍처의 각 부분은 많은 작업을 수행하기 위해 다른 클래스 (다른 2 개 부분에 숨겨져 있음)에 위임 될 수 있습니다. Model, View 및 Presenter 클래스는 코드 기반 계층의 상위 부분에 불과합니다.
출처
https://stackoverflow.com/questions/22029808