# 应用架构（MVVM、仓储、服务定位器）

> 当 CrossFly 不强制架构、不提供 DI 也不提供响应式绑定时，如何组织应用结构。

Source: https://crossfly.dev/zh/docs/best-practices/arquitetura-de-app — CrossFly documentation. Full docs as one file: https://crossfly.dev/llms-full.txt

# 应用架构

CrossFly **不强制任何架构**，也**没有 DI 和响应式绑定**（参见 [状态与数据流](/docs/concepts/state-and-data-flow)）。这既是自由——也是责任。下面是**建议采用**（兼容而非强制）的、可扩展的约定。这是第 32 章的任务型摘选。

## 建议的包结构
```
app/
 ├─ ui/screens/     → uma tela = uma subclasse de Window
 ├─ viewmodel/      → estado + lógica de apresentação por tela
 ├─ data/repository → fachada sobre dev.crossfly.sqlite / rede
 └─ service/        → serviços de app (singletons) + service locator
```

## 一个屏幕 = 一个 `Window`
```java
public class ClientesScreen extends Window {
   private final ClientesVM vm = new ClientesVM(Services.get(ClienteRepository.class));
   @Override public void onShow() throws Exception {
      super.onShow();               // 在此构建 UI（bounds 有效）
      // … 连接 UI ↔ vm …
      vm.observe(this::render);     // 状态变化时重新渲染
      vm.carregar();
   }
   @Override public void onRemove() { vm.dispose(); super.onRemove(); }  // 释放资源
}
```

## 轻量的 ViewModel（不含 `setState`）
由于没有响应式绑定，ViewModel 保存状态并暴露一个 `observe(Runnable)`；View **手动重新渲染**：
```java
public class ClientesVM {
   private final ClienteRepository repo;
   private Runnable onChange;
   public List<Cliente> clientes = List.of();
   public ClientesVM(ClienteRepository repo) { this.repo = repo; }
   public void observe(Runnable r) { this.onChange = r; }
   public void carregar() {
      new Thread(() -> {
         List<Cliente> dados = repo.listar();
         Component.onUI(() -> { clientes = dados; if (onChange != null) onChange.run(); });
      }).start();
   }
   public void dispose() { onChange = null; }
}
```

## 基于 SQLite 的 Repository
将数据访问隔离在一个门面中——UI 永远不直接讲 SQL：
```java
public class ClienteRepository {
   public List<Cliente> listar() {
      try (SqliteDb db = SqliteDb.open("app.db");
           SqliteCursor c = db.query("SELECT id, nome FROM cliente")) {
         List<Cliente> out = new ArrayList<>();
         while (c.next()) out.add(new Cliente(c.getLong(0), c.getString(1)));
         return out;
      }
   }
}
```

## 用简单的服务定位器代替 DI
旧的 `Registry` 已被**移除**。用你自己的 `Map<Class,Object>` 来解析（或通过构造函数注入）：
```java
public final class Services {
   private static final Map<Class<?>, Object> reg = new HashMap<>();
   public static <T> void put(Class<T> k, T v) { reg.put(k, v); }
   public static <T> T get(Class<T> k) { return k.cast(reg.get(k)); }
}
// 在 bootstrap 中：Services.put(ClienteRepository.class, new ClienteRepository());
```

## 最佳实践
- **在 `onShow()` 中构建 UI**，在 `onRemove()` 中释放资源。
- 状态放在 ViewModel 中；变更通过 `onUI` 回到绘制线程；View 将状态转换为组件。
- Repository 隐藏 SQLite/网络；屏幕不了解 SQL。

## 常见错误
- 寻找 Flutter 那样的 DI/可观察对象——它们并不存在；使用服务定位器 + 手动 `observe(...)`。
- 把 SQL/HTTP 直接放进 `Window`（造成耦合并阻塞 UI）。
- 将应用状态保存在 `Window` 的字段中（导航时会丢失）。

## 另请参阅
- 概念：[状态与数据流](/docs/concepts/state-and-data-flow) · [状态管理](/docs/data-and-services/state-management)
