MVC 패턴과 스윙


80

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

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

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

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


2
다음은 관련 입니다.
trashgod

- 다른 사람이이 파티에와 주셔서 스윙 순수한 MVC 아닙니다 - 그것은 개념에서 무거운를 차용하지만 함께 "보기와 컨트롤러"를 "붕괴"
MadProgrammer

답변:


106

스윙에서 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를 다양한 패턴의 일종의 합성물로 간주하는 것이 중요합니다. 예를 들어, 컨트롤러가 전략 패턴을 사용할 수있는 동안 Observer 패턴을 사용하여 모델을 구현할 수 있습니다 (View가 모델에 대한 관찰자로 등록되어야 함).

# 2 수정 :

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


실제로 책을 읽었지만 SWING에 패턴을 적용하기가 어려웠습니다. 또한 JFrame이 뷰와 컨트롤러를 모두 나타내는 것으로 볼 수 있다는 것을 몇 군데 읽었습니다.
sbrattla 2011 년

... JFrame은 리프가 아니라 구성 요소입니다. 일반적으로 컨트롤러에 의해 수행 된 업데이트는 나머지를 처리하는 JFrame으로 전송되므로 컨트롤러가 컨트롤러라는 착각을 줄 수 있지만 실제로는 모델을 변경하지 않았기 때문에 그렇지 않습니다. 보기 만. JFrame이 어떻게 든 직접 모델을 변경했다면 잘못하고 있습니다.
Dhruv Gairola 2011 년

... 다시, 여기서 키워드는 "직접"입니다. 귀하의 경우 테이블에서 마우스 클릭을 듣고 테이블 모델을 수정하는 컨트롤러의 메서드에 논리를 보낼 수 있습니다.
Dhruv Gairola 2011 년

2
@DhruvGairola 두 번째 포인트 설명은 세 번째 포인트에 대한 것이고 세 번째 포인트 설명은 동일한 중복 설명을 갖습니다. 제발 정정 해 주시겠습니까?
Naruto Biju Mode

그 노래는 고전이야! =D
aaiezza

36

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

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

4
흥미롭지 만 단일 엔티티 모델이 여러보기에 표시되는 경우 효율성이 떨어집니다. 그러면 설계가 단일 모델을 처리하지만 모든 관련보기를 관리하는 "큰 컨트롤러"로 이어질 수 있습니다. 뷰가 여러 "소형 모델"엔터티에 전달 된 정보를 표시하기 때문에 "대형 모델"로의 집계 덕분에 "소형 모델"집합을 재사용하려고하면 더욱 까다로워집니다.
Yves Martin

1
@onepotato 방금 코드를 시도했습니다. 버튼을 누르면 setUpViewEvents ()에서 코드를 실행하여 실행할 수 있습니다. 그러나 model.setSomething (123)을 수행하면 propertyChange의 코드가 실행되지 않습니다. Object newVal = evt.getNewValue (); 바로 아래에 println을 넣었습니다. 인쇄되지 않습니다.
AmuletxHeart 2014 년

10
이것은 MVC 아키텍처 패턴이 아니라 밀접하게 관련된 MVP (Model-View-Presenter) 패턴입니다. 일반적인 MVC 에서보기가 변경되었을 때보 기에 "좋아하지 않는 것"을 정확히 알리는 것은 모델의 역할 입니다. 에서 봐 이 그림은 일반적인 MVC 작업에서 어떻게 상호 작용을 볼 수 있습니다.
MaxAxeHax

25

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/ .

260 라인 미만의 MVC 기본 사항 :

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

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

MVC 기본 스크린 캐스트


4
나는이 대답은 하나 같이 언급에 대한 ActionController나는 모든 생각 사실 EventListener입니다 컨트롤러를 ..
nachokk

@nachokk 예, 그렇습니다. 내가 말했듯이 A controller encapsulates the application code that is executed in order to an user interaction. 마우스 이동, 구성 요소 클릭, 키 누르기 등은 모두 사용자 상호 작용입니다. 더 명확하게하기 위해 내 답변을 업데이트했습니다.
René Link

2

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

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

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

공유 모델에 대한 리스너 (관찰자) 인터페이스를 추가합니다. 일부 구성 요소 (예 : 테이블)는이를 직접 구현할 수 있습니다. 또 다른 관찰자는 버튼 가용성 등을 조정하는 컨트롤러 일 수 있습니다.


이것이 한 가지 방법이지만 물론 사용 사례에 과잉 인 경우 단순화하거나 확장 할 수 있습니다.

컨트롤러를 모델과 병합하고 동일한 클래스 프로세스를 업데이트하고 구성 요소 가용성을 유지할 수 있습니다. "공유 모델"을 만들 수도 있습니다 TableModel(테이블에서만 사용되는 것이 아니라면 적어도 테이블 추상화를 유출하지 않는 친숙한 API를 제공하는 것이 좋습니다).

반면에, 당신이 (업데이트 복잡한 인터페이스를 가지고 있습니다 CustomerUpdateListener, OrderItemListener,OrderCancellationListener )와 전용 컨트롤러 (또는 중재인) 단지 다른 관점의 조정합니다.

문제가 얼마나 복잡한 지에 따라 다릅니다.


전체보기의 약 90 %는 사용자가 편집 할 요소를 선택할 수있는 테이블로 구성됩니다. 지금까지 제가 한 것은 모든 CRUD 작업이 통과하는 데이터 모델이 있다는 것입니다. 저는 TableModel을 사용하여 데이터 모델을 JTable에 적용합니다. 따라서 요소를 업데이트하려면 table.getModel (). getModel (). update (Element e)를 호출합니다. 즉, JTable 종류는 현재 컨트롤러입니다. 모든 버튼 동작은 별도의 클래스에 배치되고 (다른 컨텍스트에서 재사용) 기본 모델의 메서드를 통해 작업을 수행합니다. 실행 가능한 디자인입니까?
sbrattla 2011 년

1

적절한 분리를 위해 일반적으로 Frame 클래스가 위임 할 컨트롤러 클래스가 있습니다. 클래스 간의 관계를 설정하는 방법에는 여러 가지가 있습니다. 컨트롤러를 구현하고 기본 뷰 클래스로 확장하거나 이벤트가 발생할 때 프레임이 호출하는 독립형 컨트롤러 클래스를 사용할 수 있습니다. 뷰는 일반적으로 리스너 인터페이스를 구현하여 컨트롤러에서 이벤트를 수신합니다.

때로는 MVC 패턴의 하나 이상의 부분이 사소하거나 '얇아'서 분리하기 위해 불필요한 복잡성을 추가합니다. 컨트롤러가 한 줄 호출로 가득 찬 경우 별도의 클래스에두면 기본 동작을 혼란스럽게 만들 수 있습니다. 예를 들어, 처리중인 모든 이벤트가 TableModel과 관련이 있고 간단한 추가 및 삭제 작업 인 경우 해당 모델 내에서 모든 테이블 조작 함수를 구현하도록 선택할 수 있습니다. JTable). 진정한 MVC는 아니지만 필요하지 않은 곳에 복잡성을 추가하지 않습니다.

그러나 그것을 구현하든, 컴포넌트와 그 관계가 적절하게 설명 될 수 있도록 클래스, 메소드 및 패키지를 JavaDoc에 기억하십시오!


@AndyT 대부분의 설명은 좋지만 모델을 컨트롤러와 결합하는 것에 대한 조언에 문제가 있습니다. 갑자기 컨트롤러를 바꾸고 싶으면 어떻게하나요? 이제 모델을 컨트롤러와 연결했으며 모델도 수정해야합니다. 코드는 더 이상 확장 할 수 없습니다. 컨트롤러가 짧더라도 모델과 결합하지 않을 것입니다. 또는보기.
Dhruv Gairola 2011 년

나는 동의하지 않을 것입니다-그것은 당신의 응용 프로그램에 많이 달려 있습니다. 모델이 List 객체보다 더 정교하지 않고 컨트롤러가 요소를 추가하고 제거하는 것 이상을 수행하지 않는 경우 세 개의 개별 클래스 (모델이 JTable과 함께 작동하도록 List 모델, Controller 및 어댑터)를 만드는 것은 과도합니다. 알 수없는 미래의 필요를 위해 shim 클래스를 제거하는 것보다 다른 컨트롤러가 필요한 경우에 리팩토링하는 것이 더 쉽습니다.
AndyT 2011 년

@AndyT는 동의했습니다. 아마도 응용 프로그램이 작은 경우 이것이 가장 빠른 방법 일 수 있습니다. 그러나 확장 성을 위해 (같은 프로그래머가 추가하지 않은 경우 고려) 단점으로 작용할 수 있습니다.
Dhruv Gairola 2011 년

2
@AndyT : 소프트웨어를 개발한지 얼마나되었는지는 모르겠지만 귀하의 게시물은 KISS 원칙을 받아들 였음을 보여줍니다. 너무 똑똑하지만 경험이없는 자바 개발자들은 성경처럼 디자인 패턴을 받아들입니다 (디자인 패턴은 고급 잘라 내기 및 붙여 넣기 프로그래밍에 지나지 않습니다). 대부분의 경우 별도의 컨트롤러와 뷰 클래스를 구축하여 순수한 접근 방식을 취하는 것은 원래 개발자가 아닌 다른 사람이 수백 줄의 코드보다 큰 프로그램에 대한 유지 관리를 악몽으로 만드는 데 도움이됩니다. 의심 스러우면 간단하게 유지하세요!
bit-twiddler 2011 년

1
@AndyT : 깨달음의 길은 냄비 구멍, 뱀 기름 판매원, 독선 자들로 가득 차 있습니다. 그러나 일을 단순하게 유지하도록 가르치기 위해 지속적인 시간 동안 자신의 배변에 빠져야하는 것만 큼 좋은 것은 없습니다. 디자인 패턴에는 문제가 없습니다. 그러나 디자인 패턴을 아는 것은 소프트웨어 디자인을 아는 것과 다릅니다. 요리 책 접근 방식을 사용하여 구축 된 획기적인 소프트웨어 제품은 없습니다. 요구 사항을 충족하고 유지 관리가 쉬운 고성능 소프트웨어를 설계하는 것은 여전히 ​​마스터하는 데 수년이 필요한 예술 형식입니다.
bit-twiddler 2011 년


0

당신이 함께 프로그램을 개발하는 경우 GUI , MVC 패턴은 거의하지만 희미합니다.

모델, 뷰 및 컨트롤러 코드를 분석하는 것은 어렵고 일반적으로 리팩터링 작업이 아닙니다.

코드를 재사용 할 수있을 때 가지고 있다는 것을 알고 있습니다. MVC를 올바르게 구현했다면 동일한 기능을 가진 TUI , CLI 또는 RWD 또는 모바일 우선 설계 를 쉽게 구현할 수 있어야합니다 . 실제로 수행하는 것보다 기존 코드에서 수행되는 것을 쉽게 볼 수 있습니다.

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.