【问题标题】:Is this a proper MVC design ? Advices on MVC design an communication to thread这是一个正确的 MVC 设计吗?关于 MVC 设计与线程的通信的建议
【发布时间】:2014-05-31 18:42:04
【问题描述】:

最后一天,我阅读了很多关于 MVC 模式的教程和博客。现在我非常了解这个概念,但在我看来,每个教程都展示了另一个概念来在 Java 中实现这种模式。因此,我决定编写自己的应用程序,然后向技能较高的程序员寻求建议。 (边做边学是我的事)。

所以我不想对我最初的想法说太多话,但我会告诉你我的代码:

主类:

public class MainClass {enter code here
    public static void main(String[] args){

        MainController controller = new MainController();
        controller.initView();
    }
}

控制器类:

public class MainController implements ActionListener{

private ExtractorStatics stat;
private MainView mainview;
private BusinessExtractor bExtractor;
private InfoboxTextPane infobox;
private BufferedImage logoGS;

public MainController(){
     stat = new Statics(); 
     model = new Model();
     mainview   = new MainView();
}
public void initView(){
    if(mainview!=null){
        mainview.setActionListener(this);
        mainview.setItemListener(new ComboBoxItemListener(this));
        mainview.setVisible(true);
    }
}

@Override
public void actionPerformed(ActionEvent event) {
    String command = event.getActionCommand();
    if(command.equalsIgnoreCase(stat.SCAN_ACTION_COMMAND)){
        this.quickScanButtonAction();
    }
}


private void quickScanButtonAction(){
    infobox = mainview.getInfobox();
    ProcessingInformation information = model.quickScan();
    InputStream informationStream = information.getInformationStream();
    BufferedReader infoReader = new BufferedReader(
                new InputStreamReader(informationStream));
    String line;
    try {
    while ((line = infoReader.readLine()) != null) {
                infobox.appendLine(line);
            }

            infoReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("End reached");
    }
    else
        infobox.appendLine("Bitte Eingabe überprüfen.");
}
public void comboBoxChanged() {
    //do some fancy stuff
}
   }

 class ComboBoxItemListener implements ItemListener{
        MainController mc;
    public ComboBoxItemListener(MainController mc){
        this.mc=mc;
    }
    @Override
    public void itemStateChanged(ItemEvent e) {
        mc.comboBoxChanged();
    }   
}

MainView Class:

    public class MainView extends JFrame {


    private static final long serialVersionUID = 559229524422932258L;
    private JPanel contentPane;
    private JTextField txt_stichwort,txt_loc;
    private JButton btn_quickscan;
    private JTable table;
    private JLabel label;
    public  InfoboxTextPane txtpn_infobox;
    private String lineSep;
    private final JComboBox<String> combobox;
    /**
     * Create the frame.
     */
    public MainView() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException
                | IllegalAccessException | UnsupportedLookAndFeelException e3) {
            // TODO Auto-generated catch block
            e3.printStackTrace();
        }

        lineSep=System.lineSeparator();
        Statics stats=new Statics();



        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100, 100, 800, 620);
        contentPane = new JPanel();
        contentPane.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null, null));
        setContentPane(contentPane);
        contentPane.setLayout(null);


        txt_subject= new JTextField();
        txt_stichwort.setBounds(88, 47, 318, 20);
        pan_suche.add(txt_stichwort);
        txt_stichwort.setColumns(10);

        combobox = new JComboBox<String>();
        combobox.setBounds(88, 16, 318, 20);
        pan_suche.add(combobox);
        combobox.setModel(new DefaultComboBoxModel(new String[] {"Item1", "Item2"}));

        txt_loc = new JTextField();
        txt_loc.setBounds(88, 79, 318, 20);
        pan_suche.add(txt_loc);
        txt_loc.setColumns(10);



        btn_quickscan = new JButton("Quick Scan");
        btn_quickscan.setActionCommand(stats.SCAN_ACTION_COMMAND);
        btn_quickscan.setBounds(10, 23, 130, 30);
        pan_dos.add(btn_quickscan);

        //a few more buttons that have other action commands defined by statics
        //more labels and other GUI components


    }
    public InfoboxTextPane getInfobox(){
        return this.txtpn_infobox;
    }
    public String getSearchSubject(){
        return this.txt_stichwort.getText();
    }

    public String getSearchLocation(){
        return this.txt_loc.getText();
    }
    public String getSearchWebsite(){
        return (String)this.combobox.getSelectedItem();
    }
    public JComboBox<String> getComboBox(){
        return this.combobox;
    }
    public JLabel getLogoLabel(){
        return this.label;
    }

    public void setActionListener(ActionListener al){
        try {

            btn_quickscan.addActionListener(al);
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }

    public void setItemListener(ItemListener il){
        this.combobox.addItemListener(il);
    }
    }

模型类:

public class Model {
private ExecutorService exeService;

public Model(){
    exeService =Executors.newCachedThreadPool();
}

public ProcessingInformation quickScan(){ 
    QuickScanRoutine qs = new QuickScanRoutine();
    Future<String> result = exeService.submit(qs);
    return qs.getProcessingInformation(); 
}

}

例程类(可调用):

public class QuickScanRoutine implements Callable<String> {
    private ProcessingInformation pi;
    private BufferedWriter writer;



public QuickScanRoutine(){
    pi = new ProcessingInformation();
    PipedOutputStream pos = new PipedOutputStream();
    PipedInputStream pis;
    writer = new BufferedWriter(new OutputStreamWriter(pos));

    try {
        pis = new PipedInputStream(pos);
        pi.setInformationStream(pis);
    } catch (IOException e) {
        e.printStackTrace();
    }

}

public ProcessingInformation getProcessingInformation(){
    return this.pi;
}

@Override
public String call() throws Exception {

    //ofcourse this isnt the real purpose of this Class but I want to     `demonstrate writing to the stream`

    for(int i =0; i<100;i++){
        writer.write("Hello");
        writer.newLine();
        writer.flush();
    }
    writer.close();
    return "Routine has been executed sucessfully!";
}

}

处理信息:

public class ProcessingInformation {
PipedInputStream informationStream,resultDataStream;

    public void setInformationStream(PipedInputStream info) {
        this.informationStream = info;
    }
    public void setResultDataStream(PipedInputStream data){ 
//I will need this stream for further tasks
        this.resultDataStream = data;
    }

    public InputStream getInformationStream(){
    if(informationStream!=null){
        return informationStream;
    }
    else
        return null;
}

public InputStream getResultDataStream(){
    if(informationStream!=null){
        return informationStream;
    }
    else
        return null;
}
}

我尝试尽可能缩短代码,但尝试将其保留在上下文中,以便您理解整个结构。 我的主要问题是:

  • 这是正确的 MVC 设计吗?
  • 这是从模型线程到视图的合适方式吗? (管道流)

这些将是我的主要问题,但我很感激并愿意接受您的所有建议。

【问题讨论】:

  • 看看我的Java Swing File Browser 文章。我说明了一种使用 MVC 模式编写 Swing 应用程序的方法。
  • 感谢您提供此链接,非常感谢。

标签: java multithreading design-patterns model-view-controller


【解决方案1】:

我同意 Abhi Beckert 在某些方面的回答 - 通常如下:

  1. 绝对不允许模型与模型以外的任何东西进行通信。
  2. 视图应该只与其他视图对象通信,而不知道控制器。

此外,Controller 必须了解 View 的组成,因为这会耦合 Controller 和 View。这种耦合意味着如果您需要更改视图表示或为此向用户提供各种数据表示的选择,您将不得不为每个表示编写一个新的控制器。让我通过一个例子来具体说明这一点。

当您在 MS-Word 等文字处理器中打开 Word 文档时,页面可以有多种布局,例如纵向、横向、打印和 Html。在这些布局中的每一个中,可见的用户元素都是不同的。不过数据是一样的。因此,解决问题的最合乎逻辑的方法是:

  • 让控制器向模型发送命令以更新适当的视图。
  • 让控制器查询模型以获取数据,并将获取的数据简单地传递给视图进行渲染。

我浏览了您发布的代码,并确定您的控制器知道视图的内容,例如MainController 调用 MainView.getinfobox()。根据上面的解释,你会想要改变它。

关于与使用 PipedStream 发送数据相关的第二个问题,我认为这在技术上没有任何问题 - 因为流也是数据的容器。但是,到目前为止,我个人还没有看到或遇到过遵循这种模式的设计或代码 sn-p。

希望对你有帮助

【讨论】:

  • 在 MVC 应用程序中,视图可以直接从模型中读取数据。是的,视图必须有模型的实例,但模型仍然与视图解耦。
  • @GilbertLeBlanc 我同意,但我的意思是控制器必须知道关于视图的组成的任何事情。
  • 通常,我将框架(视图)类的一个实例和模型类的一个实例传递给需要知道的许多控制器中的每一个。框架类具有更新/刷新视图的方法。
  • 能否请您看一下我对这个问题的回答,因为太长了才发表评论。
【解决方案2】:

我不是 Java 程序员,但我很了解 MVC:

如果在我看来你的模型类应该做更多的工作。您的控制器和视图中似乎有一些模型代码。

此外,除了模型的其他部分之外,模型永远不能与任何东西进行通信。

视图也是如此——它应该只与其他视图对象通信。

所有通信代码稍后进入控制器。模型和视图应该有任意回调等,由控制器层中的对象配置和使用。

MVC 的目的是让您的模型和视图完全独立于您的其余代码。它们的外部依赖项应该为零。

【讨论】:

  • 但是模型只是通过getter方法进行通信。所以控制器得到它。然后另一个模型类写入流。这也被弃用了吗?我虽然 MVC 的全部意义在于您可以单独使用该模型。就像从任何其他 UserInterface 或控制台调用 quickscan() 方法一样?还是我弄错了?
  • @user3187049 是的,在您的代码中,模型未连接。但是,您的控制器中有“模型”代码并查看代码。所有这些代码都需要移动到模型类(或多个类)中,并且您需要以不知道控制器或视图的方式移动它。
  • 请注意,“模型”不是您应用中的数据。 “模型”是对数据进行操作的所有代码。所以,你的代码现在的样子,你的控制器是模型的一部分,因为它里面有模型代码。你应该把它分开。
  • 此外,一如既往,规则是用来打破的。 MVC 是一种指南/最佳实践,但如果你有充分的理由(例如:性能),你可以打破规则。
【解决方案3】:

我认为我对模型类的理解有误。现在我认为模型只是希望视图呈现的数据。 但之前我认为模型是我的应用程序的业务逻辑。所以我现在的理解是:

视图包含所有图形元素并具有控制器的实例,因为视图需要将控制器实现为在表单上执行的操作的侦听器。 (按钮点击等)但视图不应包含任何可能的逻辑。

控制器不应该包含真正的逻辑,它只是处理表单上的操作并调用模型中的方法。因此控制器需要同时拥有视图和模型的实例。

模型包含所有希望在视图中显示的数据,并调用业务逻辑的方法来操作这些数据或执行其他内部流程。

  • 我的理解对吗?

关于 MVC 模式的其他几个问题:

  • 在教程中,我看到了 MVC 模式的实现,其中视图是观察者,模型是可观察者(观察者模式)。这对我来说似乎是合乎逻辑的,因为模型代表了应在视图中显示的数据。并且视图需要对模型数据的变化做出反应。但这是实现模型和视图之间关系的合适方式吗?
  • 一个表单可以有多个模型类吗?例如,一个模型用于表格,另一个模型用于文本窗格?
  • 在控制器中实现按钮操作等所需的所有接口并通过 setter 方法将它们设置为视图中的按钮是否是一种合适的方式,甚至是最佳方式?
  • 你说普拉哈拉德:

我浏览了您发布的代码,并确定您的控制器知道视图的内容,例如MainController 调用 MainView.getinfobox()。根据上面的解释,你会想要改变它。

这是否意味着控制器根本不应该操纵表单?我现在认为视图只是通过模型提供的数据来操纵自己,因为视图“观察”模型的数据而不是改变自己。那是对的吗 ?

【讨论】:

  • Stack Overflow 是一个问答网站。这里没有人可以指导您学习 MVC。您可以在其他网站上聘请导师来帮助您学习 MVC。
  • 好吧对不起,但我真的不希望有人辅导我,但我阅读了很多关于该主题的教程和文本,现在我仍然有一些问题。这就是为什么我问了具体的问题,其中大多数甚至是肯定的。所以我只是恳请有人回答这些问题。因为阅读博客和教程对我有很大帮助,但这些都是我留下的问题。
  • 模型不仅仅是数据,模型是数据和所有接触数据的代码。
猜你喜欢
  • 1970-01-01
  • 2011-04-25
  • 2010-11-04
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多