code

MVC 패턴과 스윙

codestyles 2020. 10. 26. 08:00
반응형

MVC 패턴과 스윙


"리얼 스윙 라이프"에서 가장 이해하기 어려운 디자인 패턴 중 하나는 MVC 패턴입니다. 이 사이트에서 패턴에 대해 논의하는 게시물을 꽤 많이 봤지만 여전히 Java Swing 애플리케이션에서 패턴을 활용하는 방법을 명확하게 이해하고 있다고 생각하지 않습니다.

테이블, 몇 개의 텍스트 필드 및 몇 개의 버튼이 포함 된 JFrame이 있다고 가정 해 보겠습니다. 아마도 TableModel을 사용하여 JTable을 기본 데이터 모델과 "브리지"할 것입니다. 그러나 필드 지우기, 필드 유효성 검사, 버튼 동작과 함께 필드 잠금을 담당하는 모든 기능은 일반적으로 JFrame에서 직접 이동합니다. 그러나 그것은 패턴의 컨트롤러와 뷰를 혼합하지 않습니까?

내가 볼 수있는 한, JTable (및 모델)을 볼 때 MVC 패턴이 "올바르게"구현되도록 관리하지만 전체 JFrame을 전체적으로 보면 상황이 흐려집니다.

나는 이것과 관련하여 다른 사람들이 어떻게 진행하는지 정말로 듣고 싶습니다. MVC 패턴을 사용하는 사용자에게 테이블, 필드 몇 개, 버튼 몇 개를 표시해야 할 때 어떻게해야합니까?


스윙에서의 MVC에 대해 내가 강력히 추천하는 책은 Freeman과 Freeman의 "Head First Design Patterns"입니다. MVC에 대한 매우 포괄적 인 설명이 있습니다.

간단한 요약

  1. 당신은 사용자이며 뷰와 상호 작용합니다. 보기는 모델에 대한 창입니다. 보기에 무언가를하면 (예 : Play 버튼 클릭)보기는 컨트롤러에 사용자가 한 작업을 알려줍니다. 그것을 처리하는 것이 컨트롤러의 일입니다.

  2. 컨트롤러는 모델에게 상태를 변경하도록 요청합니다. 컨트롤러는 사용자의 행동을 취하고 해석합니다. 버튼을 클릭하면 그것이 의미하는 바를 파악하고 해당 동작을 기반으로 모델을 조작하는 방법을 파악하는 것이 컨트롤러의 역할입니다.

  3. 컨트롤러는보기를 변경하도록 요청할 수도 있습니다. 컨트롤러가 뷰에서 작업을 받으면 결과적으로 뷰가 변경되도록 지시해야 할 수 있습니다. 예를 들어 컨트롤러는 인터페이스의 특정 버튼 또는 메뉴 항목을 활성화 또는 비활성화 할 수 있습니다.

  4. 모델은 상태가 변경되면 뷰에 알립니다. 사용자가 취한 조치 (예 : 버튼 클릭) 또는 기타 내부 변경 (예 : 재생 목록의 다음 노래 시작)을 기반으로 모델에서 무언가가 변경되면 모델은 해당 상태가 변경되었음을보기에 알립니다.

  5. 뷰는 모델에 상태를 요청합니다. 뷰는 모델에서 직접 표시하는 상태를 가져옵니다. 예를 들어, 모델이 새 노래가 재생되기 시작했음을보기에 알리면보기는 모델에서 노래 이름을 요청하여 표시합니다. 뷰는 컨트롤러가 뷰의 일부 변경을 요청한 결과로 모델에 상태를 요청할 수도 있습니다.

여기에 이미지 설명 입력 출처 ( "크림 같은 컨트롤러"가 무엇인지 궁금한 경우 컨트롤러가 크림색 중앙, 뷰가 상단 비스킷, 모델이 하단 비스킷 인 오레오 쿠키를 생각해보십시오.)

음, 관심이 있으시면 여기 에서 MVC 패턴에 대한 상당히 재미있는 노래를 다운로드 할 수 있습니다 !

Swing 프로그래밍에서 직면 할 수있는 한 가지 문제는 SwingWorker 및 EventDispatch 스레드를 MVC 패턴과 통합하는 것입니다. 프로그램에 따라 뷰 또는 컨트롤러는 SwingWorker를 확장하고 doInBackground()리소스 집약적 인 논리가 배치 된 메서드를 재정의해야 할 수 있습니다 . 이것은 전형적인 MVC 패턴과 쉽게 융합 될 수 있으며 Swing 애플리케이션의 전형적인 것입니다.

# 1 수정 :

또한 MVC를 다양한 패턴의 일종의 합성물로 간주하는 것이 중요합니다. 예를 들어, 컨트롤러가 전략 패턴을 사용할 수있는 동안 관찰자 패턴 (뷰가 모델에 관찰자로 등록되어야 함)을 사용하여 모델을 구현할 수 있습니다.

# 2 수정 :

추가로 귀하의 질문에 구체적으로 답변하고 싶습니다. 분명히 ActionListener를 구현하는 뷰에 테이블 버튼 등을 표시해야합니다. 당신에 actionPerformed()방법, 당신은 이벤트를 감지하고 컨트롤러 관련 법에 보내 (기억 - 뷰 컨트롤러에 대한 참조를 보유). 따라서 버튼을 클릭하면 뷰에 의해 이벤트가 감지되고 컨트롤러의 메서드로 전송되고 컨트롤러는 뷰에 직접 버튼 등을 비활성화하도록 요청할 수 있습니다. 다음으로 컨트롤러는 모델과 상호 작용하고 수정합니다 (대부분 getter 및 setter 메서드와 관찰자 등을 등록하고 알리는 다른 메서드가 있음). 모델이 수정되는 즉시 등록 된 옵저버에 대한 업데이트를 호출합니다 (귀하의 경우보기가됩니다). 따라서 이제보기가 자체적으로 업데이트됩니다.


데이터가 변경 될 때 모델에서 알림을받는 뷰라는 생각이 마음에 들지 않습니다. 해당 기능을 컨트롤러에 위임합니다. 이 경우 애플리케이션 로직을 변경하면 뷰의 코드를 방해 할 필요가 없습니다. 보기의 작업은 응용 프로그램 구성 요소 + 레이아웃에 대한 것입니다. 스윙 레이아웃은 이미 장황한 작업인데, 왜 애플리케이션 로직을 방해하게 놔두나요?

MVC에 대한 내 생각은 (현재까지 작업하고있는) 다음과 같습니다.

  1. 보기는 세 가지 중 가장 바보입니다. 컨트롤러와 모델에 대해 아무것도 모릅니다. 그 관심사는 스윙 부품의 보철과 레이아웃뿐입니다.
  2. 모델도 멍청하지만보기만큼 멍청하지는 않습니다. 다음 기능을 수행합니다.
    • ㅏ. setter 중 하나가 컨트롤러에 의해 호출되면 리스너 / 관찰자에게 알림을 보냅니다 (내가 말했듯이이 역할을 컨트롤러에 전달합니다). 나는 이미이 목적에 최적화되어 있기 때문에이를 달성하기 위해 SwingPropertyChangeSupport선호합니다 .
    • 비. 데이터베이스 상호 작용 기능.
  3. 매우 똑똑한 컨트롤러. 보기와 모델을 잘 알고 있습니다. 컨트롤러에는 두 가지 기능이 있습니다.
    • ㅏ. 사용자가 상호 작용할 때 뷰가 실행할 작업을 정의합니다.
    • 비. 모델을 듣습니다. 내가 말한 것처럼 모델의 setter가 호출되면 모델은 컨트롤러에 알림을 보냅니다. 이 알림을 해석하는 것은 컨트롤러의 역할입니다. 뷰에 대한 변경 사항을 반영해야 할 수도 있습니다.

코드 샘플

보기 :

내가 말했듯이 뷰를 만드는 것은 이미 장황하므로 직접 구현을 만드십시오. :)

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}

테스트 가능성을 위해 세 가지를 인터페이스하는 것이 이상적입니다. 모델 및 컨트롤러 구현 만 제공했습니다.

모델 :

public class MyImplementationOfModel implements Model{
    ...
    private SwingPropertyChangeSupport propChangeFirer;
    private String address;
    private String firstName;
    private String lastName;

    public MyImplementationOfModel() {
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }
    public void addListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }
    public void setAddress(String address){
        String oldVal = this.address;
        this.address = address;

        //after executing this, the controller will be notified that the new address has been set. Its then the controller's
        //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
        propChangeFirer.firePropertyChange("address", oldVal, address);
    }
    ...
    //some other setters for other properties & code for database interaction
    ...
}

컨트롤러 :

public class MyImplementationOfController implements PropertyChangeListener, Controller{

    private View view;
    private Model model;

    public MyImplementationOfController(View view, Model model){
        this.view = view;
        this.model = model;

        //register the controller as the listener of the model
        this.model.addListener(this);

        setUpViewEvents();
    }

    //code for setting the actions to be performed when the user interacts to the view.
    private void setUpViewEvents(){
        view.getBtnClear().setAction(new AbstractAction("Clear") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.setFirstName("");
                model.setLastName("");
                model.setAddress("");
            }
        });

        view.getBtnSave().setAction(new AbstractAction("Save") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                ...
                //validate etc.
                ...
                model.setFirstName(view.getTxtFName().getText());
                model.setLastName(view.getTxtLName().getText());
                model.setAddress(view.getTxtAddress().getText());
                model.save();
            }
        });
    }

    public void propertyChange(PropertyChangeEvent evt){
        String propName = evt.getPropertyName();
        Object newVal = evt.getNewValue();

        if("address".equalsIgnoreCase(propName)){
            view.getTxtAddress().setText((String)newVal);
        }
        //else  if property (name) that fired the change event is first name property
        //else  if property (name) that fired the change event is last name property
    }
}

MVC가 설정된 메인 :

public class Main{
    public static void main(String[] args){
        View view = new YourImplementationOfView();
        Model model = new MyImplementationOfModel();

        ...
        //create jframe
        //frame.add(view.getUI());
        ...

        //make sure the view and model is fully initialized before letting the controller control them.
        Controller controller = new MyImplementationOfController(view, model);

        ...
        //frame.setVisible(true);
        ...
    }
}

MVC 패턴은 사용자 인터페이스를 구조화하는 방법의 모델입니다. 따라서 모델, 뷰, 컨트롤러의 3 가지 요소를 정의합니다.

  • 모델 모델은 사용자에게 제공되는 무언가의 추상화입니다. 스윙에서는 GUI 모델과 데이터 모델을 차별화 할 수 있습니다. GUI 모델은 ButtonModel 과 같은 ui 구성 요소의 상태를 추상화합니다 . 데이터 모델은 TableModel 과 같이 UI가 사용자에게 제공하는 구조화 된 데이터를 추상화 합니다 .
  • 보기 보기는 사용자에게 데이터를 표시하는 UI 구성 요소입니다. 따라서 레이아웃, 그리기 등과 같은 모든 UI 종속 문제를 담당합니다. 예 : JTable .
  • 컨트롤러 컨트롤러 는 사용자 상호 작용 (마우스 동작, 마우스 클릭, 키 누르기 등)을 위해 실행되는 애플리케이션 코드를 캡슐화합니다. 컨트롤러는 실행을 위해 입력이 필요할 수 있으며 출력을 생성합니다. 그들은 모델에서 입력을 읽고 실행의 결과로 모델을 업데이트합니다. 또한 ui를 재구성 할 수도 있습니다 (예 : ui 구성 요소를 교체하거나 완전히 새로운보기를 표시). 그러나 컨트롤러가 호출하는 별도의 인터페이스로 재구성을 캡슐화 할 수 있기 때문에 ui 구성 요소에 대해 알면 안됩니다. 스윙에서 컨트롤러는 일반적으로 ActionListener 또는 Action에 의해 구현됩니다 .

  • 빨간색 = 모델
  • 녹색 =보기
  • 파란색 = 컨트롤러

여기에 이미지 설명 입력

Button클릭하면 ActionListener. ActionListener단지 다른 모델에 따라 달라집니다. 일부 모델을 입력으로 사용하고 다른 모델을 결과 또는 출력으로 사용합니다. 메서드 인수 및 반환 값과 같습니다. 모델은 업데이트 될 때 UI에 알립니다. 따라서 컨트롤러 로직이 ui 구성 요소를 알 필요가 없습니다. 모델 객체는 UI를 모릅니다. 알림은 관찰자 패턴에 의해 수행됩니다. 따라서 모델 개체는 모델이 변경되면 알림을 받고자하는 사람이 있다는 것만 알고 있습니다.

자바 스윙에는 모델과 컨트롤러를 구현하는 몇 가지 구성 요소가 있습니다. 예 : javax.swing.Action . UI 모델 (속성 : 인 에이블먼트, 작은 아이콘, 이름 등)을 구현하고 ActionListener를 확장하므로 컨트롤러 입니다.

자세한 설명, 예제 응용 프로그램 및 소스 코드 : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ .

240 줄 미만의 MVC 기본 사항 :

public class Main {

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame("MVC example");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setSize(640, 300);
        mainFrame.setLocationRelativeTo(null);

        PersonService personService = new PersonServiceMock();

        DefaultListModel searchResultListModel = new DefaultListModel();
        DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel();
        searchResultSelectionModel
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        Document searchInput = new PlainDocument();

        PersonDetailsAction personDetailsAction = new PersonDetailsAction(
                searchResultSelectionModel, searchResultListModel);
        personDetailsAction.putValue(Action.NAME, "Person Details");

        Action searchPersonAction = new SearchPersonAction(searchInput,
                searchResultListModel, personService);
        searchPersonAction.putValue(Action.NAME, "Search");

        Container contentPane = mainFrame.getContentPane();

        JPanel searchInputPanel = new JPanel();
        searchInputPanel.setLayout(new BorderLayout());

        JTextField searchField = new JTextField(searchInput, null, 0);
        searchInputPanel.add(searchField, BorderLayout.CENTER);
        searchField.addActionListener(searchPersonAction);

        JButton searchButton = new JButton(searchPersonAction);
        searchInputPanel.add(searchButton, BorderLayout.EAST);

        JList searchResultList = new JList();
        searchResultList.setModel(searchResultListModel);
        searchResultList.setSelectionModel(searchResultSelectionModel);

        JPanel searchResultPanel = new JPanel();
        searchResultPanel.setLayout(new BorderLayout());
        JScrollPane scrollableSearchResult = new JScrollPane(searchResultList);
        searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER);

        JPanel selectionOptionsPanel = new JPanel();

        JButton showPersonDetailsButton = new JButton(personDetailsAction);
        selectionOptionsPanel.add(showPersonDetailsButton);

        contentPane.add(searchInputPanel, BorderLayout.NORTH);
        contentPane.add(searchResultPanel, BorderLayout.CENTER);
        contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH);

        mainFrame.setVisible(true);
    }

}

class PersonDetailsAction extends AbstractAction {

    private static final long serialVersionUID = -8816163868526676625L;

    private ListSelectionModel personSelectionModel;
    private DefaultListModel personListModel;

    public PersonDetailsAction(ListSelectionModel personSelectionModel,
            DefaultListModel personListModel) {
        boolean unsupportedSelectionMode = personSelectionModel
                .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
        if (unsupportedSelectionMode) {
            throw new IllegalArgumentException(
                    "PersonDetailAction can only handle single list selections. "
                            + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION");
        }
        this.personSelectionModel = personSelectionModel;
        this.personListModel = personListModel;
        personSelectionModel
                .addListSelectionListener(new ListSelectionListener() {

                    public void valueChanged(ListSelectionEvent e) {
                        ListSelectionModel listSelectionModel = (ListSelectionModel) e
                                .getSource();
                        updateEnablement(listSelectionModel);
                    }
                });
        updateEnablement(personSelectionModel);
    }

    public void actionPerformed(ActionEvent e) {
        int selectionIndex = personSelectionModel.getMinSelectionIndex();
        PersonElementModel personElementModel = (PersonElementModel) personListModel
                .get(selectionIndex);

        Person person = personElementModel.getPerson();
        String personDetials = createPersonDetails(person);

        JOptionPane.showMessageDialog(null, personDetials);
    }

    private String createPersonDetails(Person person) {
        return person.getId() + ": " + person.getFirstName() + " "
                + person.getLastName();
    }

    private void updateEnablement(ListSelectionModel listSelectionModel) {
        boolean emptySelection = listSelectionModel.isSelectionEmpty();
        setEnabled(!emptySelection);
    }

}

class SearchPersonAction extends AbstractAction {

    private static final long serialVersionUID = 4083406832930707444L;

    private Document searchInput;
    private DefaultListModel searchResult;
    private PersonService personService;

    public SearchPersonAction(Document searchInput,
            DefaultListModel searchResult, PersonService personService) {
        this.searchInput = searchInput;
        this.searchResult = searchResult;
        this.personService = personService;
    }

    public void actionPerformed(ActionEvent e) {
        String searchString = getSearchString();

        List<Person> matchedPersons = personService.searchPersons(searchString);

        searchResult.clear();
        for (Person person : matchedPersons) {
            Object elementModel = new PersonElementModel(person);
            searchResult.addElement(elementModel);
        }
    }

    private String getSearchString() {
        try {
            return searchInput.getText(0, searchInput.getLength());
        } catch (BadLocationException e) {
            return null;
        }
    }

}

class PersonElementModel {

    private Person person;

    public PersonElementModel(Person person) {
        this.person = person;
    }

    public Person getPerson() {
        return person;
    }

    @Override
    public String toString() {
        return person.getFirstName() + ", " + person.getLastName();
    }
}

interface PersonService {

    List<Person> searchPersons(String searchString);
}

class Person {

    private int id;
    private String firstName;
    private String lastName;

    public Person(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

}

class PersonServiceMock implements PersonService {

    private List<Person> personDB;

    public PersonServiceMock() {
        personDB = new ArrayList<Person>();
        personDB.add(new Person(1, "Graham", "Parrish"));
        personDB.add(new Person(2, "Daniel", "Hendrix"));
        personDB.add(new Person(3, "Rachel", "Holman"));
        personDB.add(new Person(4, "Sarah", "Todd"));
        personDB.add(new Person(5, "Talon", "Wolf"));
        personDB.add(new Person(6, "Josephine", "Dunn"));
        personDB.add(new Person(7, "Benjamin", "Hebert"));
        personDB.add(new Person(8, "Lacota", "Browning "));
        personDB.add(new Person(9, "Sydney", "Ayers"));
        personDB.add(new Person(10, "Dustin", "Stephens"));
        personDB.add(new Person(11, "Cara", "Moss"));
        personDB.add(new Person(12, "Teegan", "Dillard"));
        personDB.add(new Person(13, "Dai", "Yates"));
        personDB.add(new Person(14, "Nora", "Garza"));
    }

    public List<Person> searchPersons(String searchString) {
        List<Person> matches = new ArrayList<Person>();

        if (searchString == null) {
            return matches;
        }

        for (Person person : personDB) {
            if (person.getFirstName().contains(searchString)
                    || person.getLastName().contains(searchString)) {
                matches.add(person);
            }

        }
        return matches;
    }

}

별도의 일반 Java 클래스에서 모델을 생성하고 다른 클래스에서 컨트롤러를 생성 할 수 있습니다.

그런 다음 그 위에 Swing 구성 요소를 가질 수 있습니다. JTable뷰 중 하나가 될 것입니다 (테이블 모델은 사실상 뷰의 일부가됩니다. "공유 모델"에서로만 변환됩니다 JTable).

테이블이 편집 될 때마다 테이블 모델은 "메인 컨트롤러"에게 무언가를 업데이트하도록 지시합니다. 그러나 컨트롤러는 테이블에 대해 아무것도 알아야합니다. : 통화가 더 같아야 그래서 updateCustomer(customer, newValue),하지 updateCustomer(row, column, newValue).

Add a listener (observer) interface for the shared model. Some components (e.g. your table) could implement it directly. Another observer could be the controller that coordinates button availability etc.


That's one way to do it, but of course you can simplify or extend it if its an overkill for your use case.

You can merge the controller with model and have the same class process updates and maintain component availability. You even can make the "shared model" a TableModel (though if it's not only used by the table, I would recommend at least providing a friendlier API that doesn't leak table abstractions)

On the other hand, you can have complex interfaces for updates (CustomerUpdateListener, OrderItemListener, OrderCancellationListener) and dedicated controller (or mediator) only for coordination of different views.

It depends on how complicated your problem is.


For proper separation, you would typically have a controller class that the Frame class would delegate to. There are various ways to set up the relationships between the classes - you could implement a controller and extend it with your main view class, or use a standalone controller class that the Frame calls when events occur. The view would typically receive events from the controller by implementing a listener interface.

Sometimes one or more parts of the MVC pattern are trivial, or so 'thin' that it adds unnecessary complexity to separate them out. If your controller is full of one line calls, having it in a separate class can end up obfuscating the underlying behaviour. For instance, if the all of the events you are handling are related to a TableModel and are simple add and delete operations you might choose to implement all of the table manipulation functions within that model (as well as the callbacks necessary to display it in the JTable). It's not true MVC, but it avoids adding complexity where it isn't needed.

However you implement it, remember to JavaDoc your classes, methods and packages so that the components and their relationships are properly described!


I have found some interesting articles about implementing MVC Patterns, which might solve your problem.


If you develop a program with a GUI, mvc pattern is almost there but blurred.

Disecting model, view and controller code is difficult, and normally is not only a refactor task.

You know you have it when your code is reusable. If you have correctly implemented MVC, should be easy to implement a TUI or a CLI or a RWD or a mobile first design with the same functionality. It's easy to see it done than do it actually, moreover on an existing code.

실제로 모델, 뷰 및 컨트롤러 간의 상호 작용은 다른 격리 패턴 (관찰자 또는 리스너)을 사용하여 발생합니다.

이 게시물은 직접 비 MVC 패턴 ( Q & D 에서 할 것 )부터 재사용 가능한 최종 구현에 이르기까지 자세히 설명한다고 생각합니다 .

http://www.austintek.com/mvc/

참고 URL : https://stackoverflow.com/questions/5217611/the-mvc-pattern-and-swing

반응형