我是桌面应用程序开发的新手,今年夏天我有一个非常大的项目。问题是代码必须非常清晰,所以当我更新代码时,我不会遇到麻烦。
因此,我想要一个良好的“关注点分离”。对我来说最困难的部分是视图 - 控制器分离。
现在,我已经阅读了很多教程,讨论等。我已经以3种不同的方式设计了一个迷你应用程序。该应用程序很简单:单击一个将标签转换为“Hello world”的按钮。
您如何看待这三种设计?
有没有更好的设计来满足我的期望?
View1.java:
public View1() {
initComponents();
this.controller = new Controller1(this);
}
private Controller1 controller;
public void updateLabel(String message){
this.jLabel1.setText(message);
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
this.controller.doSomething();
}
private void initComponents() {
...
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
...}
Controller1.java:
public class Controller1 {
public Controller1(View1 v){
this.view = v;
}
public void doSomething(){
this.view.updateLabel("Hello world");
}
private View1 view;
}
View2.java:
public View2() {
initComponents();
this.controller = new Controller2(this);
jButton1.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
controller.doSomething();
}
});
}
public void updateLabel(String message){
this.jLabel1.setText(message);
}
private Controller2 controller;
...
}
Controller2.java:
public class Controller2 {
public Controller2(View2 v){
this.view = v;
}
public void doSomething(){
this.view.updateLabel("Hello world");
}
private View2 view;
}
View3.java:
public View3() {
initComponents();
this.controller = new Controller3(this);
this.jButton1.addActionListener(this.controller.listener);
}
private Controller3 controller;
public void updateLabel(String message){
this.jLabel1.setText(message);
}
...}
Controller3.java:
public class Controller3 {
public Controller3(View3 v){
this.view = v;
this.listener = new MyListener(v);
}
private View3 view;
public MyListener listener;
}
MyListener.java:
public class MyListener implements ActionListener{
private View3 view;
public MyListener(View3 v){
this.view = v;
}
public void actionPerformed(java.awt.event.ActionEvent evt) {
this.view.updateLabel("Hello world");
}
}
答案 0 :(得分:10)
我不喜欢这些设计。您将控制器与视图紧密耦合。假设您希望将来更改控制器实现,因此您必须进入所有类并更改类。相反,你应该注入它。有许多lib可以通过Guice或Spring等注释为您完成此操作,但我不会参与其中。这是一个更好的设计。
public class View{
private Controller controller;
public View(Controller controller) {
this.controller = controller;
}
}
这是一个更清洁的设计,因为视图不必知道控制器的实现是什么。您可以稍后创建一个子类并将其传递给它。
所以现在通过上面的设计,我认为你可以看到你不应该将View传递给控制器。这又是耦合,这是不好的。相反,你可以传递一个onCallback类,它将在完成后执行。这是代码来代替它
jButton1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
controller.doSomething(new Runnable(){
public void run(){
updateLabel("Hello world");
}
});
}
});
然后在你的控制器中
public void doSomething(Runnable callback){
// do work
SwingUtilties.invokeLater(callback);
}
如果你看起来我建议删除任何类型的耦合。该视图不应该要求控制器,应该给予它。 Controller不应该知道应该只执行回调的视图。这很重要,因为如果您决定不使用Swing,那么您的控制器中的Swing包就不会具有所有这些依赖关系。
希望这一切都有帮助!
答案 1 :(得分:6)
确定哪种模式最佳取决于您正在解决的问题。 Swing is already an MVC framework,所以你必须考虑在其上添加另一层间接是值得的。
由于您不熟悉UI编程,我建议您首先将walking skeleton系统放在一起,然后根据您从中学到的内容,决定您的架构。精心设计的架构使测试和重用组件变得容易。 MVP和MVVM是两种众所周知的UI设计模式。
对于你的玩具问题,你可以像我一样实现MVP或MVVM。请记住,您通常也会在每个接口之间使用接口,如果可以改变,将在模型上有观察者。
<强> MVP 强>
public class Model {
public String getWhatIWantToSay() {
return "Hello World";
}
}
public class Presenter implements ActionListener {
private final View view;
private final Model model;
public Presenter(Model model, View view) {
this.model = model;
this.view = view;
view.addButtonListener(this);
}
public void actionPerformed(ActionEvent e) {
view.setText(model.getWhatIWantToSay());
}
}
public class View {
private JButton button = new JButton();
private JLabel label = new JLabel();
public void addButtonListener(ActionListener listener) {
button.addActionListener(listener);
}
public void setText(String text) {
label.setText(text);
}
}
<强> MVVP 强>
public class ModelView extends Observable {
private final Model model;
private String text = "";
public ModelView(Model model) {
this.model = model;
}
public void buttonClicked() {
text = model.getWhatIWantToSay();
notifyObservers();
}
}
public class View implements Observer {
private JButton button = new JButton();
private JLabel label = new JLabel();
private final ModelView modelView;
public View(final ModelView modelView) {
this.modelView = modelView;
modelView.addObserver(this);
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
modelView.buttonClicked();
}
});
}
public void update(Observable o, Object arg) {
label.setText(modelView.text);
}
}
答案 2 :(得分:3)
我认为设计2是满足您标准的最佳选择。
设计1的问题:在视图方面太复杂了。额外的方法使它看起来几乎就像它里面有一个控制器。实施简单的改变将变得复杂。
设计3的问题:这会对控制器造成太大影响。控制器不应该知道Swing事件发生了什么。在该设计中,如果您希望基于JList而不是JButton执行操作,则必须更改视图和控制器,这是不好的。
关于您的代码的其他评论:
java.awt.event.ActionListener()
。this.
,这只会增加噪音。答案 3 :(得分:0)
另一种设计方法可能是这样的:
<强>模型强>
package biz.tugay.toypro.model;
public interface LabelService {
String getDateInRandomLocale();
}
package biz.tugay.toypro.model;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;
public class LabelServiceImpl implements LabelService {
private final Locale availableLocalesJava[];
public LabelServiceImpl() {
this.availableLocalesJava = DateFormat.getAvailableLocales();
}
@Override
public String getDateInRandomLocale() {
final int randomIndex = ThreadLocalRandom.current().nextInt(0, availableLocalesJava.length);
final Locale locale = availableLocalesJava[randomIndex];
final Calendar calendar = Calendar.getInstance();
final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
return dateFormat.format(calendar.getTime());
}
}
查看强>
package biz.tugay.toypro.view;
import biz.tugay.toypro.model.LabelService;
import javax.swing.*;
public class DateInRandomLocaleLabel extends JLabel {
private final LabelService labelService;
public DateInRandomLocaleLabel(final LabelService labelService) {
this.labelService = labelService;
}
public void showDateInRandomLocale() {
final String dateInRandomLocale = labelService.getDateInRandomLocale();
setText(dateInRandomLocale);
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
public class RandomizeDateButton extends JButton {
public RandomizeDateButton() {
super("Hit Me!");
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
import java.awt.*;
public class DateInRandomLocalePanel extends JPanel {
public DateInRandomLocalePanel(final JLabel dateInRandomLocaleLabel, final JButton randomizeDateButton) {
final GridLayout gridLayout = new GridLayout(1, 2);
setLayout(gridLayout);
add(dateInRandomLocaleLabel);
add(randomizeDateButton);
}
}
package biz.tugay.toypro.view;
import javax.swing.*;
public class MainFrame extends JFrame {
public void init() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setSize(400, 50);
setVisible(true);
}
}
<强>控制器强>
package biz.tugay.toypro.controller;
import biz.tugay.toypro.view.DateInRandomLocaleLabel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RandomizeDateButtonActionListener implements ActionListener {
private DateInRandomLocaleLabel dateInRandomLocaleLabel;
@Override
public void actionPerformed(final ActionEvent e) {
dateInRandomLocaleLabel.showDateInRandomLocale();
}
public void setDateInRandomLocaleLabel(final DateInRandomLocaleLabel dateInRandomLocaleLabel) {
this.dateInRandomLocaleLabel = dateInRandomLocaleLabel;
}
}
最后我如何启动应用程序:
package biz.tugay.toypro;
import biz.tugay.toypro.controller.RandomizeDateButtonActionListener;
import biz.tugay.toypro.model.LabelService;
import biz.tugay.toypro.model.LabelServiceImpl;
import biz.tugay.toypro.view.DateInRandomLocaleLabel;
import biz.tugay.toypro.view.DateInRandomLocalePanel;
import biz.tugay.toypro.view.MainFrame;
import biz.tugay.toypro.view.RandomizeDateButton;
import javax.swing.*;
public class App {
public static void main(String[] args) {
final LabelService labelService = new LabelServiceImpl();
// View
final DateInRandomLocaleLabel dateInRandomLocaleLabel = new DateInRandomLocaleLabel(labelService);
final RandomizeDateButton randomizeDateButton = new RandomizeDateButton();
final DateInRandomLocalePanel dateInRandomLocalePanel = new DateInRandomLocalePanel(dateInRandomLocaleLabel, randomizeDateButton);
final MainFrame mainFrame = new MainFrame();
mainFrame.getContentPane().add(dateInRandomLocalePanel);
// Controller
final RandomizeDateButtonActionListener randomizeDateButtonActionListener = new RandomizeDateButtonActionListener();
// Bind Controller to the View..
randomizeDateButton.addActionListener(randomizeDateButtonActionListener);
// Bind View to the Controller..
randomizeDateButtonActionListener.setDateInRandomLocaleLabel(dateInRandomLocaleLabel);
// Show the main frame..
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
mainFrame.init();
}
});
}
}
我认为没有一个正确的答案,这取决于你想如何绑定你拥有的组件..
您可能会发现以下内容也很有用: