在Passive View Model View Presenter模式中,谁负责显示视图?我找到了其他MVP版本的相关答案,但它们似乎不适用于被动视图版本。
我有一个使用Java Swing的具体示例。它非常简单,但基本上我们有一个SwingCustomersView
,它在内部构建一个带有表格(客户列表)的JPanel,以及一个显示当前所选客户年龄的标签。在表格中选择客户后,演示者将从模型中检索选定的客户年龄。我认为这个例子是MVP被动视图的正确实现,但如果我错了,请纠正我。
问题是我们如何引导这些类?例如,如果我们想在JFrame中显示SwingCustomersView
。如何做到这一点?我想象的是:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
}
这是初始接线,但尚未显示任何内容。我们如何实际显示视图? (1)launcher()
,(2)SwingCustomersView
或(3)CustomersPresenter
是否有责任显示视图?不幸的是,我不相信这些都是非常好的,你可以从我的下面的想法中看到。也许还有另一种方式?
让SwingCustomersView
扩展JFrame,并将其内部JPanel添加到自己的内容窗格中。然后我们可以这样做:
void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
view.setVisible(true); // Displays the view
}
但是在这种情况下,我们不会将presenter
实例用于任何事情。难道不奇怪吗?它只是用于布线,我们也可以删除变量,只需new CustomersPresenter(view, model)
。
让SwingCustomersView
在构造函数中使用Container
,它应该将void launcher() {
CustomersModel model = new CustomersModel();
JFrame frame = new JFrame("Some title");
SwingCustomersView view = new SwingCustomersView(frame.getContentPane());
CustomersPresenter presenter = new CustomersPresenter(view, model);
frame.pack();
frame.setVisible(true) // Displays the view
}
添加到内部JPanel中:
presenter
然而,与(1)相同的问题:CustomersPresenter
实例什么都不做。这看起来很奇怪。此外,使用(1)和(2)两者都可以在演示者连接之前显示视图,我想在某些情况下可能会导致奇怪的结果。
让void launcher() {
CustomersModel model = new CustomersModel();
SwingCustomersView view = new SwingCustomersView();
CustomersPresenter presenter = new CustomersPresenter(view, model);
presenter.show() // Displays the view
}
负责显示视图somwhow。然后我们可以这样做:
CustomersView
这将解决在施工后不使用它的问题。但是,如果不更改CustomersPresenter
接口或使public class CustomersModel {
private List<Customer> customers;
public CustomersModel() {
customers = new ArrayList<Customer>();
customers.add(new Customer("SomeCustomer", "31"));
customers.add(new Customer("SomeCustomer", "32"));
}
public List<Customer> getCustomers() {
return customers;
}
}
public class Customer {
public String name;
public String age;
public Customer(String name, String age) {
this.name = name;
this.age = age;
}
}
public interface CustomersView {
void addCustomerSelectionChangeListener(ItemListener listener);
void onNewActiveCustomer(String age);
void onNewCustomers(List<String> newCustomers);
}
public class SwingCustomersView implements CustomersView {
// Swing components here all put into a main JPanel
public void addCustomerSelectionChangeListener(ItemListener listener) {
// Add event listener to table
}
public void onNewActiveCustomer(String age) {
// Display age in label beneath table
}
public void onNewCustomers(List<String> newCustomers) {
// Display customers in table
}
}
public class CustomersPresenter {
private final CustomersView view;
private final CustomersModel model;
public CustomersPresenter(CustomersView view, CustomersModel model) {
this.view = view;
this.model = model;
initPresentationLogic();
populateView();
}
private void initPresentationLogic() {
view.addCustomerSelectionChangeListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
String selectedName = (String)e.getItem();
List<Customer> customers = model.getCustomers();
for (Customer c : customers)
if (c.name.equals(selectedName))
view.onNewActiveCustomer(c.age);
}
});
}
private void populateView() {
List<Customer> customers = model.getCustomers();
List<String> names = new ArrayList<String>();
for (Customer c : customers)
names.add(c.name);
// View will now populate its table, which in turn will call customerSelectionChangeListener
// so the view 'automagically' updates the selected contact age too
view.onNewCustomers(names);
}
}
过于依赖底层GUI实现,我就不知道如何做到这一点。此外,显示视图听起来不像演示逻辑,因此似乎不属于演示者。
{{1}}
答案 0 :(得分:7)
选项(3)一直以来。这是演示者“控制”视图的工作,包括使其可见。是的,您需要添加到视图的界面以允许这种情况发生,但这不是什么大问题。请记住,您可以使视图尽可能地被动。没有任何逻辑!
工作示例:
我偶然发现了this example使用MVC架构的简单Swing游戏。由于我使用MVP而不是MVC来编写我的Swing应用程序,所以我不能说具有权威性,如果这个例子是一个真实而纯粹的MVC例子。对我来说看起来没问题,而且作者trashgod在使用Swing时已经证明了自己在SO上,所以我会接受它是合理的。
作为练习,我决定使用MVP架构重写它。
驱动程序:
正如您在下面的代码中看到的,这非常简单。什么应该跳出来的是关注点的分离(通过检查构造函数):
模型类是独立的,不了解视图或演示者。
查看界面由独立的GUI类实现,它们都不具备模型或演示者的知识。
Presenter 类了解模型和视图。
代码:
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;
}
}
模型:主要是模型的工作是:
代码:
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
可测试,因为没有应用程序逻辑与Swing代码混合使用代码:
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组件,您可能希望:
这可能很快变得笨拙。作为解决方案(在该示例中未示出),为每个字段创建密钥,并且GUI使用其密钥注册每个组件(使用HashMap)。然后,而不是视图定义方法,如:
public void addResetActionListener(ActionListener listener);
// and then repeat for every field that needs an ActionListener
你会有一个方法:
public void addActionListener(SomeEnum someField, ActionListener listener);
其中“SomeEnum”是定义给定UI上所有字段的enum
。然后,当GUI接收到该调用时,它会查找相应的组件以调用该方法。所有这些繁重的工作都将在一个实现View的抽象超类中完成。
演示者:职责是:
代码(注意这里没有Swing):
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类只是代码库层次结构中的上层划分。