Sablonul de Proiectare MVC
Introducere
Modelul arhitectural Model-View-Controller (MVC) reprezintă unul dintre cele mai populare și eficiente șabloane de proiectare utilizate în dezvoltarea aplicațiilor cu interfețe grafice. Acest model facilitează organizarea codului prin separarea clară a responsabilităților între diferite componente ale aplicației.
Componente principale
MVC descompune obiectele aplicației în trei categorii distincte:
1. Model
Componenta Model are următoarele caracteristici și responsabilități:
Menține starea curentă a aplicației, a unei părți a aplicației sau a unui set de date
Poate desfășura anumite acțiuni specifice domeniului aplicației
Nu conține nicio informație cu privire la reprezentarea datelor (modul în care informațiile vor fi afișate către utilizator)
Oferă metode prin care poate fi interogat și modificat
Implementează un mecanism prin care componentele de tip View pot fi înregistrate pentru a primi notificări
Notifică toate componentele View înregistrate atunci când starea sa se modifică
2. View
Componenta View are următoarele caracteristici și responsabilități:
Reprezintă interfața grafică ce reflectă starea curentă a unui Model
Este responsabilă cu afișarea pe ecran a informațiilor
Preia de la utilizator eventualele acțiuni
Un Model poate avea înregistrate mai multe componente View
Primește notificări când starea Modelului se schimbă și se actualizează corespunzător
3. Controller
Componenta Controller are următoarele caracteristici și responsabilități:
Procesează și răspunde la evenimente
Preia acțiunile utilizatorului
Modifică starea componentei Model sau View conform acțiunilor utilizatorului
Realizează legăturile dintre View-uri și Model
Interpretează și traduce acțiunile utilizatorului în comenzi pentru Model
Fluxul de date în MVC

Fluxul tipic de date în MVC funcționează astfel:
Un eveniment este captat de Controller (de obicei o acțiune a utilizatorului)
Controller-ul determină schimbarea componentei Model sau a componentei View
Când Controller-ul schimbă starea Modelului, toate componentele View sunt notificate automat
Componentele View își actualizează afișarea preluând datele necesare din Model
În mod similar, când Controller-ul schimbă o componentă View, aceasta extrage datele necesare din Model pentru a fi afișate
Implementare în Java
Pentru implementarea modelului MVC în Java, se pot folosi clasa java.util.Observable
și interfata java.util.Observer
. Aceste două componente permit realizarea unui mecanism de notificare automată a obiectelor Observer atunci când se produce o schimbare în cadrul unui obiect Observable. Desi aceste doua clasa sunt deprecated în Java 9, ele sunt încă utilizate în aplicațiile existente și sunt utile pentru a înțelege conceptele de bază ale arhitecturii MVC.
Pași de implementare:
Implementarea Modelului:
Extindeți clasa
java.util.Observable
Apelați metodele
setChanged()
șinotifyObservers()
pentru a notifica observatorii când starea modelului se modifică
Implementarea View-urilor:
Implementați interfața
java.util.Observer
Implementați metoda
update(Observable o, Object arg)
pentru a reacționa la modificările modeluluiÎnregistrați View-urile ca observatori ai modelului
Implementarea Controller-ului:
Creați o clasă ce gestionează interacțiunea dintre Model și View
Implementați mecanisme pentru a prelua acțiunile utilizatorului și a le transmite modelului
Exemplu 1: Aplicația Termometru
Exemplul următor prezintă o aplicație ce simulează un termometru care citește temperaturi din mediul înconjurător și le afișează atât în mod text, cât și grafic.
Componenta Model: Clasa Thermometer
import java.util.Observable;
public class Thermometer extends Observable implements Runnable {
static int MAX_VALUE=100;
static int MIN_VALUE=0;
double temp=30;
Thread t;
boolean active = true;
boolean paused = false;
public void start(){
if(t==null){
t = new Thread(this);
t.start();
}
}
public void run(){
while(active){
if(paused){
synchronized(this){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
double d = Math.random()*4;
double x = Math.random();
if(x<0.5) d = -1*d;
if(temp+d<MAX_VALUE&&temp+d>MIN_VALUE){
temp= temp+d;
this.setChanged();
this.notifyObservers();
}
try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}
public void setPause(boolean p){
synchronized (this) {
if(p==true){
paused = true;
}else{
paused = false;
notify();
}
}
}
public double getTemperature(){
return temp;
}
public boolean isPaused() {
return paused;
}
}
Componente View: TemperatureTextView și TemperatureCanvasView
import java.awt.FlowLayout;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;
public class TemperatureTextView extends JPanel implements Observer{
JTextField jtfTemp;
JLabel jtlTemp;
JButton action;
TemperatureTextView(){
this.setLayout(new FlowLayout());
jtfTemp = new JTextField(20);
jtlTemp = new JLabel("Temperature");
action = new JButton("Enable-Disable");
add(action);add(jtlTemp);add(jtfTemp);
}
public void update(Observable o, Object arg) {
String s = ""+((Thermometer)o).getTemperature();
jtfTemp.setText(s);
}
public void addEnableDisableListener(TemperatureController.EnableDisableListener listener) {
action.addActionListener(listener);
}
}
import java.awt.*;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;
public class TemperatureCanvasView extends JPanel implements Observer{
private static final int width = 20;
private static final int top = 20;
private static final int left = 100;
private static final int right = 250;
private static final int height = 200;
private double crtTemp;
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.black);
g.drawRect(left,top, width, height);
g.setColor(Color.red);
g.fillOval(left-width/2, top+height-width/3,width*2, width*2);
g.setColor(Color.black);
g.drawOval(left-width/2, top+height-width/3,width*2, width*2);
g.setColor(Color.white);
g.fillRect(left+1,top+1, width-1, height-1);
g.setColor(Color.red);
long redtop = (long)(height*(crtTemp-Thermometer.MAX_VALUE)/(Thermometer.MIN_VALUE-Thermometer.MAX_VALUE));
g.fillRect(left+1, top + (int)redtop, width-1, height-(int)redtop);
g.setColor(Color.BLUE);
}
public void update(Observable o, Object arg) {
crtTemp = ((Thermometer)o).getTemperature();
repaint();
}
}
Componenta Controller: TemperatureController
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TemperatureController {
Thermometer t;
TemperatureTextView tview;
public TemperatureController(Thermometer t, TemperatureTextView tview, TemperatureCanvasView tcanvasView){
t.addObserver(tview);
t.addObserver(tcanvasView);
this.t = t;
this.tview = tview;
tview.addEnableDisableListener(new EnableDisableListener());
}
class EnableDisableListener implements ActionListener{
public void actionPerformed(ActionEvent e) {
t.setPause(!t.isPaused());
}
}
}
Clasa principală a aplicației: TemperatureApp
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.*;
public class TemperatureApp extends JFrame{
TemperatureApp(TemperatureTextView tview, TemperatureCanvasView tcanvasView){
setLayout(new BorderLayout());
tcanvasView.setPreferredSize(new Dimension(300,300));
add(tview,BorderLayout.NORTH);
add(tcanvasView,BorderLayout.CENTER);
pack();
setVisible(true);
}
public static void main(String[] args) {
Thermometer t = new Thermometer();
t.start();
TemperatureCanvasView tcanvasView = new TemperatureCanvasView();
TemperatureTextView tview = new TemperatureTextView();
TemperatureController tcontroler = new TemperatureController(t,tview,tcanvasView);
new TemperatureApp(tview,tcanvasView);
}
}

Exemplu 2: Aplicație Calculator
Acest exemplu demonstrează o variantă a șablonului MVC în care componentele View și Controller sunt integrate în cadrul aceleiași clase.
Clasa Model: CalcModel
class CalcModel {
private static final String INITIAL_VALUE = "1";
private BigInteger m_total;
public CalcModel() {
reset();
}
public void reset() {
m_total = new BigInteger(INITIAL_VALUE);
}
public void multiplyBy(String operand) {
m_total = m_total.multiply(new BigInteger(operand));
}
public void setValue(String value) {
m_total = new BigInteger(value);
}
public String getValue() {
return m_total.toString();
}
}
Clasa View-Controller integrată: CalcViewController
class CalcViewController extends JFrame {
private static final String INITIAL_VALUE = "1";
// Componenta Model
private CalcModel m_logic;
// Componente View
private JTextField m_userInputTf = new JTextField(5);
private JTextField m_totalTf = new JTextField(20);
private JButton m_multiplyBtn = new JButton("Multiply");
private JButton m_clearBtn = new JButton("Clear");
CalcViewController() {
// Inițializare Model
m_logic = new CalcModel();
m_logic.setValue(INITIAL_VALUE);
// Inițializare componente View
m_totalTf.setText(m_logic.getValue());
m_totalTf.setEditable(false);
// Layout-ul componentelor
JPanel content = new JPanel();
content.setLayout(new FlowLayout());
content.add(new JLabel("Input"));
content.add(m_userInputTf);
content.add(m_multiplyBtn);
content.add(new JLabel("Total"));
content.add(m_totalTf);
content.add(m_clearBtn);
// Adăugare ascultători pentru evenimente
m_multiplyBtn.addActionListener(new MultiplyListener());
m_clearBtn.addActionListener(new ClearListener());
// Finalizare layout și setare parametri fereastră
this.setContentPane(content);
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setTitle("Simple Calc - Presentation-Model");
}
// Clase interne pentru handler-e de evenimente (Controller)
class MultiplyListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String userInput = "";
try {
userInput = m_userInputTf.getText();
m_logic.multiplyBy(userInput);
m_totalTf.setText(m_logic.getValue());
} catch (NumberFormatException nfex) {
JOptionPane.showMessageDialog(CalcViewController.this,
"Bad input: '" + userInput + "'");
}
}
}
class ClearListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
m_logic.reset();
m_totalTf.setText(m_logic.getValue());
}
}
}
Clasa principală a aplicației: Calc
public class Calc {
public static void main(String[] args) {
JFrame presentation = new CalcViewController();
presentation.setVisible(true);
}
}
Avantajele utilizării MVC
Separarea responsabilităților - fiecare componentă are un rol clar definit
Reutilizarea codului - mai multe View-uri pot utiliza același Model
Dezvoltare paralelă - echipele pot lucra simultan la componente diferite
Testare facilitată - componentele pot fi testate separat
Întreținere simplificată - modificările într-o componentă au un impact minim asupra celorlalte
Variante ale arhitecturii MVC
Există mai multe variante ale arhitecturii MVC, printre care:
MVC clasic
MVP (Model-View-Presenter)
MVVM (Model-View-ViewModel)
MVC cu View și Controller integrate (cum am văzut în Exemplul 2)
Concluzie
Șablonul arhitectural Model-View-Controller reprezintă o soluție elegantă pentru organizarea codului în aplicațiile cu interfețe grafice. Prin separarea clară a responsabilităților, MVC facilitează dezvoltarea, testarea și întreținerea aplicațiilor complexe.
Exemplu simplificat
Mai jos este prezentat un exemplu minimal de aplicație MVC în Java cu Swing, care implementează un calculator simplu de adunare.
Model (CalculatorModel.java)
package model;
import java.util.ArrayList;
import java.util.List;
public class CalculatorModel {
private int result;
private List<ModelObserver> observers = new ArrayList<>();
public interface ModelObserver {
void modelUpdated();
}
public void addObserver(ModelObserver observer) {
observers.add(observer);
}
private void notifyObservers() {
for (ModelObserver observer : observers) {
observer.modelUpdated();
}
}
public void add(int num1, int num2) {
result = num1 + num2;
notifyObservers();
}
public int getResult() {
return result;
}
}
View (CalculatorView.java)
package view;
import model.CalculatorModel;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionListener;
public class CalculatorView extends JFrame implements CalculatorModel.ModelObserver {
private JTextField num1Field = new JTextField(10);
private JTextField num2Field = new JTextField(10);
private JButton addButton = new JButton("Adună");
private JLabel resultLabel = new JLabel("Rezultat: 0");
private CalculatorModel model;
public CalculatorView(CalculatorModel model) {
this.model = model;
model.addObserver(this);
setTitle("Calculator MVC");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(300, 200);
setLayout(new FlowLayout());
add(new JLabel("Primul număr:"));
add(num1Field);
add(new JLabel("Al doilea număr:"));
add(num2Field);
add(addButton);
add(resultLabel);
setVisible(true);
}
public void addCalculateListener(ActionListener listener) {
addButton.addActionListener(listener);
}
public int getFirstNumber() {
try {
return Integer.parseInt(num1Field.getText());
} catch (NumberFormatException e) {
return 0;
}
}
public int getSecondNumber() {
try {
return Integer.parseInt(num2Field.getText());
} catch (NumberFormatException e) {
return 0;
}
}
@Override
public void modelUpdated() {
resultLabel.setText("Rezultat: " + model.getResult());
}
}
Controller (CalculatorController.java)
package controller;
import model.CalculatorModel;
import view.CalculatorView;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class CalculatorController {
private CalculatorModel model;
private CalculatorView view;
public CalculatorController(CalculatorModel model, CalculatorView view) {
this.model = model;
this.view = view;
this.view.addCalculateListener(new CalculateListener());
}
class CalculateListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
int num1 = view.getFirstNumber();
int num2 = view.getSecondNumber();
model.add(num1, num2);
}
}
}
Main (Main.java)
import model.CalculatorModel;
import view.CalculatorView;
import controller.CalculatorController;
public class Main {
public static void main(String[] args) {
// Creăm componentele MVC
CalculatorModel model = new CalculatorModel();
CalculatorView view = new CalculatorView(model);
CalculatorController controller = new CalculatorController(model, view);
}
}
Fluxul de execuție
Utilizatorul introduce două numere în câmpurile de text din View.
Utilizatorul apasă butonul "Adună".
View-ul trimite acest eveniment către Controller prin ActionListener.
Controller-ul obține valorile introduse din View și apelează metoda
add
din Model.Model-ul execută operația de adunare și notifică observatorii (inclusiv View-ul) despre schimbare.
View-ul se actualizează pentru a afișa noul rezultat.
Last updated