Подтвердить что ты не робот

3 Конструкции качаний: что лучше?

Я новичок в разработке настольных приложений, и у меня есть довольно большой проект, который выйдет этим летом. Дело в том, что код должен быть очень ясным, поэтому я не буду входить (сильно) в проблему, когда обновляю его.

В результате я хочу хорошее "разделение проблем". И самая сложная часть для меня - разделение View-Controller.

Теперь я прочитал много учебников, дискуссий и т.д. И я разработал мини-приложение тремя разными способами. Приложение прост: нажмите кнопку, которая преобразует ярлык в "Hello world".

Что вы думаете об этих трех проектах?

Есть ли лучший дизайн для удовлетворения моих ожиданий?

Конструкция 1

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;
}

Конструкция 2

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;
}

Конструкция 3

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");
            }
}
4b9b3361

Ответ 1

Мне не нравится какой-либо из этих проектов. Вы подключаете контроллер к виду плотно. Скажем, вы хотели изменить реализацию контроллера в будущем, поэтому вам придется идти на все классы и менять класс. Вместо этого вы должны сделать инъекцию. Существует множество библиотек, которые могут сделать это для вас через аннотации, такие как Guice или Spring, но я не буду вдаваться в них. Вот лучший дизайн.

public class View{
private Controller controller;
   public View(Controller controller) {
       this.controller = controller;
   }
}

Это гораздо более чистый дизайн, потому что представление не обязательно должно знать, что такое реализация контроллера. Затем вы можете создать подкласс и передать его.

Итак, теперь с приведенным выше дизайном, я думаю, вы можете видеть, что вы не должны передавать представление на контроллер. Это снова соединение, которое не очень хорошо. Вместо этого вы можете передать класс onCallback, который будет выполнен, когда это будет сделано. Вот код для undersand it

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);
}

Если вы посмотрите именно то, что я предложил, это удаление какой-либо связи. В представлении не следует запрашивать контроллер, он должен быть указан. Контроллер не должен знать о представлении, он должен просто выполнить обратный вызов. Это важно, потому что, если вы решили не использовать Swing, тогда у вас не было бы всех этих зависимостей с пакетом Swing в вашем контроллере.

Надеюсь, это все поможет!

Ответ 2

Выбор того, какой шаблон лучше всего, зависит от проблемы, которую вы решаете. Swing уже является средой MVC, поэтому вам придется подумать, стоит ли добавить еще один слой косвенности поверх этого.

Поскольку вы новичок в программировании пользовательского интерфейса, я предлагаю вам сначала сгруппировать ходячий скелет вашей системы, а затем на основе того, что вы узнали из что, решите свою архитектуру. Хорошо спроектированная архитектура позволяет легко тестировать и повторно использовать компоненты. MVP и MVVM - два известных способа проектирования шаблонов для UIs.

Для вашей игрушечной проблемы вы можете реализовать 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);
    }
}

Ответ 3

Я думаю, что дизайн 2 - ваш лучший вариант для соответствия вашим критериям.

Проблемы с дизайном 1: Слишком сложная сторона. Дополнительные методы делают его похожим на то, что в нем есть контроллер. Простые изменения станут сложными для реализации.

Проблемы с дизайном 3: Это слишком сильно нажимает на контроллер. Контроллер не должен знать, какие события Swing происходят. В этом проекте, если вы хотите, чтобы действие произошло на основе JList вместо JButton, вам нужно изменить представление и плохой контроллер.

Другие комментарии к вашему коду:

  • Используйте операторы импорта, поэтому вам не нужно включать пакет класса в код, как в: java.awt.event.ActionListener().
  • Вы используете this. в нескольких местах, если это не нужно, и это просто добавляет шум.
  • Как указывает Амир, у вас очень плотная связь между вашим видом и контроллером, что не является необходимым.

Ответ 4

Другой подход к дизайну может быть примерно таким:

Model

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();
            }
        });
    }
}

И вот как выглядит приложение: введите описание изображения здесь

введите описание изображения здесь

введите описание изображения здесь

Я думаю, что нет только одного правильного ответа, это зависит от того, как вы хотите связать компоненты, которые у вас есть.

Возможно, вы найдете следующее полезное: