Wzór MVC i huśtawka

Jednym z wzorców projektowych, które najbardziej trudno mi zrozumieć w "prawdziwym życiu swingowym", jest wzór MVC. Przejrzałem sporo postów na tej stronie, które omawiają wzorzec, ale nadal nie czuję, że mam jasne zrozumienie, jak wykorzystać wzorzec w mojej (Java SWING) aplikacji.

Załóżmy, że mam JFrame, który zawiera tabelę, kilka pól tekstowych i kilka przycisków. Prawdopodobnie użyłbym TableModel do " bridge" JTable z bazowym modelem danych. Jednak wszystkie funkcje odpowiedzialne za czyszczenie pól, walidację pól, blokowanie pól wraz z akcjami przycisków zwykle trafiały bezpośrednio do JFrame. Czy to jednak nie łączy kontrolera z widokiem wzorca?

Z tego co widzę, udaje mi się zaimplementować wzorzec MVC "poprawnie", gdy patrzę na JTable (i model), ale robi się mętno, gdy patrzę na cały JFrame jako całość.

I ' d really like to hear jak inni się do tego odnoszą. Jak postępować, gdy trzeba wyświetlić tabelę, kilka pól i kilka przycisków do Użytkownika (za pomocą wzorca MVC)?

Author: lifebalance, 2011-03-07

7 answers

Książka, którą bardzo polecam dla MVC in swing to "Head First Design Patterns" autorstwa Freemana i Freemana. Mają bardzo wyczerpujące wyjaśnienie MVC.

Krótkie Podsumowanie

  1. Jesteś użytkownikiem-korzystasz z widoku. widok jest Twoim oknem do modelu. Gdy zrobisz coś z widokiem (np. kliknij na Przycisk Odtwórz) następnie Widok informuje kontroler, co zrobiłeś. To zadanie kontrolera do obsługi to.

  2. Kontroler prosi model o zmianę jego stanu. kontroler wykonuje Twoje działania i je interpretuje. Po kliknięciu na button, zadaniem kontrolera jest ustalenie, co to oznacza i jak model powinien być manipulowany na podstawie tego działania.

  3. Kontroler może również poprosić o zmianę widoku. gdy kontroler otrzyma akcję z widoku, może być konieczne poinformowanie widok do zmiany w wyniku. Na przykład, kontroler może umożliwić lub wyłączyć niektóre przyciski lub elementy menu w interfejsie.

  4. Model powiadamia widok o zmianie jego stanu. Kiedy coś się zmienia w modelu, na podstawie jakiegoś działania, które podjąłeś (jak kliknięcie przycisku) lub jakąś inną wewnętrzną zmianę (jak następna piosenka w playliście się rozpoczęła), model powiadamia o tym, że jego stan się zmienił.

  5. Widok pyta model o stan. widok pobiera stan wyświetlany bezpośrednio z modelu. Na przykład, gdy model informuje o rozpoczęciu odtwarzania nowej piosenki, widok pobiera nazwę utworu od modelu i wyświetla ją. Widok może zapytaj również o model stanu jako wynik kontrolera proszę o zmianę widoku.

Źródło (jeśli zastanawiasz się, co to jest "kremowy kontroler", pomyśl o ciastku Oreo, a kontroler jest kremowym centrum, widok jest górnym herbatnikiem, a model jest dolnym herbatnikiem.)

Jeśli jesteś zainteresowany, możesz pobrać dość zabawną piosenkę o wzorze MVC z tutaj !

Jeden problem, z którym możesz się spotkać z programowaniem Swing polega na połączeniu wątku SwingWorker i EventDispatch ze wzorem MVC. W zależności od programu widok lub kontroler może wymagać rozszerzenia SwingWorker i nadpisania metody doInBackground(), w której logika zasobochłonna jest miejsce. Można to łatwo połączyć z typowym wzorem MVC i jest typowe dla aplikacji Swing.

Edytuj #1:

Dodatkowo, ważne jest, aby rozważyć MVC jako rodzaj kompozycji różnych wzorów. Na przykład model może być zaimplementowany przy użyciu wzorca obserwatora (wymagającego zarejestrowania widoku jako obserwatora dla modelu), podczas gdy kontroler może używać wzorca strategii.

Edytuj #2:

Chciałbym dodatkowo odpowiedz konkretnie na swoje pytanie. Powinieneś wyświetlić przyciski tabeli itp. W widoku, co oczywiście zaimplementowałoby ActionListener. W Twojej metodzie actionPerformed() wykrywasz Zdarzenie i wysyłasz je do powiązanej metody w kontrolerze (pamiętaj - Widok zawiera odniesienie do kontrolera). Tak więc po kliknięciu przycisku, zdarzenie jest wykrywane przez Widok, wysyłane do metody kontrolera, kontroler może bezpośrednio poprosić widok o wyłączenie przycisku lub coś takiego. Następnie kontroler będzie interakcja z modelem i modyfikowanie go (który będzie miał głównie metody getter i setter, a niektóre inne do rejestracji i powiadamiania obserwatorów i tak dalej). Jak tylko model zostanie zmodyfikowany, wywoła aktualizację zarejestrowanych obserwatorów (będzie to widok w Twoim przypadku). W związku z tym Widok zostanie zaktualizowany.

 94
Author: Dhruv Gairola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-17 05:10:40

Nie podoba mi się pomysł, że widok jest Tym, który jest powiadamiany przez model, gdy jego DANE się zmieniają. Chciałbym przekazać tę funkcjonalność do kontrolera. W takim przypadku, jeśli zmienisz logikę aplikacji, nie musisz ingerować w kod widoku. Zadanie widoku dotyczy tylko aplikacji components + layout nothing more not less. Layouting w swingu jest już dość gadatliwym zadaniem, dlaczego pozwalać mu ingerować w logikę aplikacji?

Mój pomysł na MVC (który obecnie praca z, jak na razie tak dobrze) jest :

    Widok jest najgłupszy z trzech. Nie wie nic o sterowniku i modelu. Jego problemem jest tylko prostetyka i układ elementów huśtawki. Model jest również głupi, ale nie tak głupi jak widok. Spełnia następujące funkcje.
  • a. gdy jeden z jego seterów zostanie wywołany przez kontroler, uruchomi powiadomienie do jego słuchaczy/obserwatorów (tak jak powiedziałem, chciałbym tę rolę do kontroler). Preferuję SwingPropertyChangeSupport do osiągnięcia tego, ponieważ jest już zoptymalizowany do tego celu.
  • B. funkcjonalność interakcji z bazą danych.
Bardzo inteligentny kontroler. Bardzo dobrze zna widok i model. Kontroler posiada dwie funkcjonalności:
  • A. definiuje akcję wykonywaną przez Widok, gdy użytkownik wchodzi w interakcję z nim.
  • B. słucha modelu. Tak jak powiedziałem, kiedy seter modelu jest wywołany, model odpali powiadomienie do kontrolera. Zadaniem kontrolera jest interpretacja tego powiadomienia. Może to wymagać odzwierciedlenia zmiany w widoku.

Próbka Kodu

Widok:

Tak jak mówiłem Tworzenie widoku jest już gadatliwe, więc po prostu stwórz własną implementację:)

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

Jest to idealne połączenie trzech dla celów testowalności. Podałem tylko implementację modelu i kontrolera.

Model :

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

Kontroler:

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

Main, gdzie ustawiony jest 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);
        ...
    }
}
 31
Author: Bnrdo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-21 01:06:58

Wzorzec MVC jest modelem struktury interfejsu użytkownika. Dlatego definiuje model 3 elementów, Widok, Kontroler:

  • Model model jest abstrakcją czegoś, co jest prezentowane użytkownikowi. W swing masz zróżnicowanie modeli gui i modeli danych. GUI modeluje abstrakcyjny stan komponentu ui, takiego jak ButtonModel. Modele danych abstrakcyjne dane strukturyzowane, które interfejs użytkownika przedstawia użytkownikowi jak TableModel .
  • widok widok jest komponentem interfejsu użytkownika, który odpowiada za prezentację danych użytkownikowi. W ten sposób jest odpowiedzialny za wszystkie kwestie zależne od interfejsu użytkownika, takie jak układ, rysunek, itp. Np. JTable .
  • Controller kontroler zawiera kod aplikacji, który jest wykonywany w celu interakcji użytkownika (ruch myszy, kliknięcie myszką, naciśnięcie klawisza itp.). Kontrolery mogą potrzebować danych wejściowych do ich wykonania i wytwarzają dane wyjściowe. Czytali swoje wprowadzanie danych z modeli i aktualizowanie modeli w wyniku wykonania. Mogą również zrestrukturyzować interfejs użytkownika (np. wymienić komponenty interfejsu lub pokazać całkowicie nowy widok). Nie mogą jednak wiedzieć o komponentach interfejsu użytkownika, ponieważ można zamknąć restrukturyzację w osobnym interfejsie, który tylko wywołuje kontroler. W swing kontroler jest zwykle zaimplementowany przez Actionlistener lub Action.

Przykład

  • Red = model
  • Zielony = view
  • Niebieski = kontroler

Tutaj wpisz opis obrazka

Po kliknięciu Button wywołuje ActionListener. ActionListener zależy tylko od innych modeli. Wykorzystuje niektóre modele jako wejście, a inne jako wynik lub wyjście. To tak jak argumenty metody i zwracane wartości. Modele informują interfejs użytkownika, gdy zostaną zaktualizowane. Nie ma więc potrzeby, aby logika kontrolera znała komponent interfejsu użytkownika. Obiekty modelu nie znają interfejsu użytkownika. Zgłoszenia dokonuje się wzorem obserwatora. Tak więc model obiekty wiedzą tylko, że jest ktoś, kto chce zostać powiadomiony, jeśli model ulegnie zmianie.

W Javie swing istnieją pewne komponenty, które implementują również model i kontroler. Np. javax.swing.Akcja . Implementuje model interfejsu użytkownika (właściwości: enablement, mała ikona, nazwa, itp.) i jest kontrolerem, ponieważ rozszerza ActionListener.

A szczegółowe wyjaśnienie, przykładowa aplikacja i Kod źródłowy : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/.

Podstawy MVC w mniej niż 240 liniach:

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

}
 21
Author: René Link,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-22 14:48:15

Można utworzyć model w osobnej, zwykłej klasie Javy, a kontroler w innej.

Wtedy możesz mieć elementy Swing na górze. JTable będzie jednym z widoków (a model tabeli będzie de facto być częścią widoku - to tylko przekłada się z" dzielonego modelu " na JTable).

Za każdym razem, gdy tabela jest edytowana, jej model tabeli mówi "main controller", aby coś zaktualizować. Kontroler nie powinien jednak wiedzieć nic o tabeli. Więc rozmowa powinna wyglądać bardziej jak: updateCustomer(customer, newValue), nie updateCustomer(row, column, newValue).

Dodaje interfejs listener (observer) dla modelu współdzielonego. Niektóre komponenty (np. twoja tabela) mogą zaimplementować ją bezpośrednio. Innym obserwatorem może być kontroler koordynujący dostępność przycisków itp.


Jest to jeden ze sposobów, aby to zrobić, ale oczywiście można go uprościć lub rozszerzyć, jeśli jest to przesada dla Twojego przypadku użycia.

Można połączyć kontroler z modelem i mieć aktualizacje procesów tej samej klasy i utrzymać dostępność komponentów. Możesz nawet sprawić, że "model współdzielony" będzie TableModel (chociaż jeśli nie jest używany tylko przez tabelę, polecam przynajmniej dostarczenie bardziej przyjaznego API, które nie wycieka abstrakcji tabel)

Z drugiej strony, możesz mieć złożone interfejsy do aktualizacji (CustomerUpdateListener, OrderItemListener, OrderCancellationListener) i dedykowany kontroler (lub mediator) tylko do koordynacji różnych poglądów.

To zależy jak skomplikowany jest Twój problem.
 2
Author: Konrad Garus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-07 09:19:23

W celu właściwego rozdzielenia, zazwyczaj masz klasę kontrolera, do której delegowałaby Klasa Frame. Istnieją różne sposoby konfigurowania relacji między klasami - można zaimplementować kontroler i rozszerzyć go o główną klasę widoku lub użyć autonomicznej klasy kontrolera, którą ramka wywołuje w przypadku wystąpienia zdarzeń. Widok zazwyczaj odbierałby zdarzenia z kontrolera poprzez implementację interfejsu słuchacza.

Czasami jedna lub więcej części wzorca MVC jest trywialne, lub tak "cienkie", że dodaje niepotrzebną złożoność, aby je oddzielić. Jeśli kontroler jest pełen wywołań jednej linii, posiadanie go w osobnej klasie może skończyć się zaciemnieniem podstawowego zachowania. Na przykład, jeśli wszystkie zdarzenia, które obsługujesz, są powiązane z Modelem tabeli i są prostymi operacjami dodawania i usuwania, możesz zaimplementować wszystkie funkcje manipulacji tabelami w tym modelu (a także wywołania zwrotne niezbędne do wyświetlenia go w tabeli JTable). To nieprawda. MVC, ale unika dodawania złożoności tam, gdzie nie jest to potrzebne.

Jakkolwiek to zaimplementujesz, pamiętaj o JavaDoc swoich klasach, metodach i pakietach, aby komponenty i ich relacje były poprawnie opisane!

 0
Author: AndyT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-03-07 09:56:41

Znalazłem kilka ciekawych artykułów na temat implementacji wzorców MVC, które mogą rozwiązać twój problem.

 0
Author: T Ambre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-20 19:07:21

Jeśli tworzysz program z GUI, wzór mvc jest prawie tam, ale niewyraźny.

Usuwanie kodu modelu, widoku i kontrolera jest trudne i zwykle nie jest tylko zadaniem refaktoringu.

Wiesz, że masz go, gdy twój kod jest wielokrotnego użytku. Jeśli poprawnie zaimplementowałeś MVC, powinno być łatwo zaimplementować TUIlub CLIlub RWDlub mobile first design z tą samą funkcjonalnością. Łatwo to zobaczyć niż zrobić właściwie, co więcej na istniejącym kodzie.

W rzeczywistości interakcje między modelem, widokiem i kontrolerem zachodzą przy użyciu innych wzorców izolacji (jako obserwator lub słuchacz)

Myślę, że ten post wyjaśnia to szczegółowo, od bezpośredniego wzorca non MVC (jak zrobisz na Q & D) do ostatecznej implementacji wielokrotnego użytku:

Http://www.austintek.com/mvc/

 0
Author: albfan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-10 21:27:08