# Model-View-ViewModel (MVVM) Pattern

## Problem

In UI applications, the view layer needs to stay synchronized with underlying data. Manually wiring up event handlers and update calls between the view and model creates fragile, hard-to-test code. The view accumulates business logic, and the model accumulates presentation concerns.

## Solution

**MVVM** introduces a **ViewModel** between the Model and the View:

- **Model** — Domain data and business rules. No knowledge of the UI.
- **View** — UI presentation. Binds to ViewModel properties and commands.
- **ViewModel** — Exposes model data in a form suitable for the view, with change notification. Contains presentation logic and commands but no direct reference to the view.

Data binding (or an observer mechanism) keeps the View and ViewModel in sync automatically.

## When to Use

- Rich client or SPA applications with two-way data binding (WPF, SwiftUI, Vue.js, Angular).
- When you want to unit-test presentation logic without instantiating UI components.
- When multiple views need different presentations of the same model.

## When to Avoid

- Server-rendered pages where MVC is simpler and more natural.
- Extremely simple UIs with no presentation logic.
- When the data binding infrastructure is unavailable or would need to be built from scratch.

## Pseudocode

```
CLASS Model:
    properties: data fields

CLASS ViewModel:
    model: Model
    observers: []

    PROPERTY display_value:
        RETURN transform(model.field)

    METHOD update(new_value):
        model.field = new_value
        NOTIFY observers

CLASS View:
    BIND display_label TO viewmodel.display_value
    ON button_click: viewmodel.update(input_value)
```

## Python

```python
class Observable:
    """Simple observer mixin for property change notification."""

    def __init__(self) -> None:
        self._listeners: list = []

    def subscribe(self, listener) -> None:
        self._listeners.append(listener)

    def notify(self, prop: str, value: object) -> None:
        for listener in self._listeners:
            listener(prop, value)


class TemperatureModel:
    """Model: stores temperature in Celsius."""

    def __init__(self, celsius: float = 0.0) -> None:
        self.celsius = celsius


class TemperatureViewModel(Observable):
    """ViewModel: exposes Celsius and Fahrenheit, handles conversion."""

    def __init__(self, model: TemperatureModel) -> None:
        super().__init__()
        self._model = model

    @property
    def celsius(self) -> float:
        return self._model.celsius

    @property
    def fahrenheit(self) -> float:
        return self._model.celsius * 9 / 5 + 32

    @property
    def display(self) -> str:
        return f"{self.celsius:.1f}C = {self.fahrenheit:.1f}F"

    def set_celsius(self, value: float) -> None:
        self._model.celsius = value
        self.notify("celsius", value)

    def set_fahrenheit(self, value: float) -> None:
        self._model.celsius = (value - 32) * 5 / 9
        self.notify("fahrenheit", value)


class TemperatureView:
    """View: binds to ViewModel and displays temperature."""

    def __init__(self, vm: TemperatureViewModel) -> None:
        self._vm = vm
        self._vm.subscribe(self._on_change)

    def _on_change(self, prop: str, value: object) -> None:
        print(f"    [View] Property '{prop}' changed -> {self._vm.display}")

    def render(self) -> None:
        print(f"    [View] {self._vm.display}")


def main() -> None:
    model = TemperatureModel(0.0)
    vm = TemperatureViewModel(model)
    view = TemperatureView(vm)

    print("--- Initial render ---")
    view.render()

    print("\n--- Set Celsius to 100 ---")
    vm.set_celsius(100.0)

    print("\n--- Set Fahrenheit to 72 ---")
    vm.set_fahrenheit(72.0)

    print("\n--- Set Celsius to -40 (same in both scales) ---")
    vm.set_celsius(-40.0)

    print("\n--- Final render ---")
    view.render()


if __name__ == "__main__":
    main()
```

**Output:**
```
--- Initial render ---
    [View] 0.0C = 32.0F

--- Set Celsius to 100 ---
    [View] Property 'celsius' changed -> 100.0C = 212.0F

--- Set Fahrenheit to 72 ---
    [View] Property 'fahrenheit' changed -> 22.2C = 72.0F

--- Set Celsius to -40 (same in both scales) ---
    [View] Property 'celsius' changed -> -40.0C = -40.0F

--- Final render ---
    [View] -40.0C = -40.0F
```

## JavaScript

```javascript
class Observable {
  constructor() {
    this._listeners = [];
  }

  subscribe(listener) {
    this._listeners.push(listener);
  }

  notify(prop, value) {
    for (const listener of this._listeners) {
      listener(prop, value);
    }
  }
}

class TemperatureModel {
  constructor(celsius = 0) {
    this.celsius = celsius;
  }
}

class TemperatureViewModel extends Observable {
  constructor(model) {
    super();
    this._model = model;
  }

  get celsius() {
    return this._model.celsius;
  }

  get fahrenheit() {
    return this._model.celsius * 9 / 5 + 32;
  }

  get display() {
    return `${this.celsius.toFixed(1)}C = ${this.fahrenheit.toFixed(1)}F`;
  }

  setCelsius(value) {
    this._model.celsius = value;
    this.notify("celsius", value);
  }

  setFahrenheit(value) {
    this._model.celsius = (value - 32) * 5 / 9;
    this.notify("fahrenheit", value);
  }
}

class TemperatureView {
  constructor(vm) {
    this._vm = vm;
    this._vm.subscribe((prop, _value) => {
      console.log(`    [View] Property '${prop}' changed -> ${this._vm.display}`);
    });
  }

  render() {
    console.log(`    [View] ${this._vm.display}`);
  }
}

// --- Main ---
const model = new TemperatureModel(0);
const vm = new TemperatureViewModel(model);
const view = new TemperatureView(vm);

console.log("--- Initial render ---");
view.render();

console.log("\n--- Set Celsius to 100 ---");
vm.setCelsius(100.0);

console.log("\n--- Set Fahrenheit to 72 ---");
vm.setFahrenheit(72.0);

console.log("\n--- Set Celsius to -40 (same in both scales) ---");
vm.setCelsius(-40.0);

console.log("\n--- Final render ---");
view.render();
```

**Output:**
```
--- Initial render ---
    [View] 0.0C = 32.0F

--- Set Celsius to 100 ---
    [View] Property 'celsius' changed -> 100.0C = 212.0F

--- Set Fahrenheit to 72 ---
    [View] Property 'fahrenheit' changed -> 22.2C = 72.0F

--- Set Celsius to -40 (same in both scales) ---
    [View] Property 'celsius' changed -> -40.0C = -40.0F

--- Final render ---
    [View] -40.0C = -40.0F
```

## Related Patterns

- **MVC** — MVC uses a controller for user input; MVVM replaces it with a ViewModel and relies on data binding.
- **MVP** — MVP uses a presenter that directly manipulates the view; MVVM uses declarative bindings and avoids direct view references.
- **Observer** — the ViewModel notifies the View of property changes via the observer (property-change notification) pattern.
- **Command** — MVVM commonly exposes user actions as Command objects that the View binds to.
