跳到内容
CrossFly 是 100% 开源在 GitHub 关注我们
CrossFlyCrossFly
开始使用

本页面由葡萄牙语机器翻译,可能存在错误。葡萄牙语版本为准。

应用架构

CrossFly 不强制任何架构,也没有 DI 和响应式绑定(参见 状态与数据流)。这既是自由——也是责任。下面是建议采用(兼容而非强制)的、可扩展的约定。这是第 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

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 手动重新渲染:

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:

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> 来解析(或通过构造函数注入):

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 的字段中(导航时会丢失)。

另请参阅

这个页面有帮助吗?