MVVM架构学习总结

一、MVVM 架构概述

1. 相关概念说明

定义: MVVM(Model-View-ViewModel)是一种软件架构模式,已成为现代UI应用程序开发的主流模式之一。它通过将UI逻辑与业务逻辑分离,简化了开发过程,提高了代码的可维护性和可测试性。

MVVM架构组件关系图
它将应用程序分为三个核心组件:​

  • Model(模型):数据模型层,负责业务逻辑和数据访问,与UI完全无关。
  • ​View(视图):负责用户界面显示,但视图是被动的,它通过数据绑定从ViewModel获取数据并显示。
  • ​ViewModel(视图模型)​:连接View和Model的桥梁,负责处理视图逻辑和状态管理

MVVM的核心思想:通过数据绑定和命令实现View和ViewModel的松耦合。这种方式降低了直接操作UI元素的需要,使代码更易于维护和测试。

二、MVVM 核心概念

1. 数据绑定

数据绑定是MVVM模式的核心机制,它建立了View与ViewModel之间的自动同步关系。当ViewModel中的数据变化时,View会自动更新;同样,当用户在View中输入数据时,这些变化也会自动反映到ViewModel中。

数据绑定的实现主要依赖于以下几个关键技术:

  1. 数据劫持/代理:通过Object.defineProperty或Proxy等技术拦截对象属性的访问和修改。
  2. 发布-订阅模式:建立数据变化与UI更新之间的通知机制。
  3. 数据监听:观察数据变化并触发相应的更新操作。

以下是简化的数据绑定实现示例:
① 基础 Observable 类

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Observable {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    protected void notifyPropertyChange(String propertyName, Object oldValue, Object newValue) {
        support.firePropertyChange(propertyName, oldValue, newValue);
    }
}

② ViewModel 基类

public abstract class ViewModel extends Observable {
    // 通用的 ViewModel 逻辑可以放在这里
}

③ 具体 ViewModel 实现

public class UserViewModel extends ViewModel {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        String oldValue = this.name;
        this.name = name;
        notifyPropertyChange("name", oldValue, name);
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        int oldValue = this.age;
        this.age = age;
        notifyPropertyChange("age", oldValue, age);
    }
}

④ 数据绑定工具类

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class DataBinder {
    public static void bind(View view, ViewModel viewModel) {
        // 这里简化实现,实际应用中可能需要更复杂的绑定逻辑
        if (view instanceof TextView && viewModel instanceof UserViewModel) {
            TextView textView = (TextView) view;
            UserViewModel userViewModel = (UserViewModel) viewModel;

            // 初始值设置
            textView.setText(userViewModel.getName());

            // 监听ViewModel变化更新View
            userViewModel.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if ("name".equals(evt.getPropertyName())) {
                        textView.setText((String) evt.getNewValue());
                    }
                }
            });

            // 监听View变化更新ViewModel (简化版,实际需要处理用户输入事件)
            textView.addTextChangedListener(new TextWatcher() {
                @Override
                public void onTextChanged(String newText) {
                    userViewModel.setName(newText);
                }
            });
        }
    }
}

⑤ 简化的 View 组件 (模拟 Android TextView)

public class TextView {
    private String text;
    private TextWatcher textWatcher;

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
        System.out.println("TextView updated: " + text);
    }

    public void addTextChangedListener(TextWatcher textWatcher) {
        this.textWatcher = textWatcher;
    }

    // 模拟用户输入
    public void simulateUserInput(String newText) {
        this.text = newText;
        if (textWatcher != null) {
            textWatcher.onTextChanged(newText);
        }
    }

    public interface TextWatcher {
        void onTextChanged(String newText);
    }
}

⑥ 使用示例

public class MVVMDemo {
    public static void main(String[] args) {
        // 创建ViewModel
        UserViewModel userViewModel = new UserViewModel();
        userViewModel.setName("John Doe");
        userViewModel.setAge(30);

        // 创建View
        TextView textView = new TextView();

        // 绑定View和ViewModel
        DataBinder.bind(textView, userViewModel);

        // 测试ViewModel变化更新View
        userViewModel.setName("Jane Smith");  // 控制台会输出TextView更新

        // 测试View变化更新ViewModel
        textView.simulateUserInput("New Name"); // ViewModel的name会被更新
        System.out.println("ViewModel name: " + userViewModel.getName());
    }
}

说明

这个简化示例展示了MVVM架构的核心数据绑定概念:

  • ​ViewModel​ 继承自 Observable,当数据变化时通知观察者
  • ​View​ (TextView) 提供UI展示和用户交互
  • DataBinder​ 负责将View和ViewModel双向绑定
  • ViewModel → View: 通过属性变化监听
  • View → ViewModel: 通过用户输入事件监听

实际框架(如Android的Data Binding库)会使用更复杂的实现,包括注解处理、表达式解析等,但这个示例展示了基本原理。

  • 车载应用常用框架:Android的Data Binding、Jetpack Compose,Qt的QML绑定

2. 命令模式实现

命令是MVVM模式中处理用户交互的主要方式。命令将UI事件(如按钮点击)绑定到ViewModel中的方法上,实现了用户操作与业务逻辑的解耦。

典型的命令实现通常包括:

  • 可执行状态管理(CanExecute)
  • 执行操作(Execute)
  • 可执行状态变更通知(CanExecuteChanged)

以下是简化的命令模式实现示例:
① 命令接口

public interface Command {
    void execute();
}

② 基础 ViewModel 类 (带命令支持)

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.HashMap;
import java.util.Map;

public class ViewModel {
    private PropertyChangeSupport support = new PropertyChangeSupport(this);
    private Map<String, Command> commands = new HashMap<>();

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    protected void notifyPropertyChange(String propertyName, Object oldValue, Object newValue) {
        support.firePropertyChange(propertyName, oldValue, newValue);
    }

    public void registerCommand(String name, Command command) {
        commands.put(name, command);
    }

    public Command getCommand(String name) {
        return commands.get(name);
    }

    public void executeCommand(String name) {
        Command command = commands.get(name);
        if (command != null) {
            command.execute();
        }
    }
}

③ 具体 ViewModel 实现

public class LoginViewModel extends ViewModel {
    private String username;
    private String password;
    private String status;

    public LoginViewModel() {
        // 注册登录命令
        registerCommand("login", new LoginCommand());
    }

    // 属性 getter/setter
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        String oldValue = this.username;
        this.username = username;
        notifyPropertyChange("username", oldValue, username);
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        String oldValue = this.password;
        this.password = password;
        notifyPropertyChange("password", oldValue, password);
    }

    public String getStatus() {
        return status;
    }

    private void setStatus(String status) {
        String oldValue = this.status;
        this.status = status;
        notifyPropertyChange("status", oldValue, status);
    }

    // 登录命令内部类
    private class LoginCommand implements Command {
        @Override
        public void execute() {
            // 模拟登录逻辑
            if (username != null && !username.isEmpty() && 
                password != null && !password.isEmpty()) {
                setStatus("登录中...");
                
                // 模拟异步操作
                new Thread(() -> {
                    try {
                        Thread.sleep(1000); // 模拟网络请求
                        setStatus("登录成功!");
                    } catch (InterruptedException e) {
                        setStatus("登录失败: " + e.getMessage());
                    }
                }).start();
            } else {
                setStatus("用户名和密码不能为空");
            }
        }
    }
}

④ 简化的 View 组件

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class LoginView {
    private LoginViewModel viewModel;

    public LoginView(LoginViewModel viewModel) {
        this.viewModel = viewModel;
        setupBindings();
    }

    private void setupBindings() {
        // 监听ViewModel属性变化
        viewModel.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                Object newValue = evt.getNewValue();
                
                if ("status".equals(propertyName)) {
                    System.out.println("状态更新: " + newValue);
                }
            }
        });
    }

    // 模拟用户输入
    public void setUsername(String username) {
        viewModel.setUsername(username);
    }

    public void setPassword(String password) {
        viewModel.setPassword(password);
    }

    // 模拟按钮点击
    public void onLoginButtonClick() {
        viewModel.executeCommand("login");
    }
}

⑤ 使用示例

public class CommandPatternDemo {
    public static void main(String[] args) {
        // 创建ViewModel
        LoginViewModel viewModel = new LoginViewModel();
        
        // 创建View并绑定ViewModel
        LoginView loginView = new LoginView(viewModel);
        
        // 模拟用户操作
        loginView.setUsername("admin");
        loginView.setPassword("123456");
        
        // 触发登录命令
        loginView.onLoginButtonClick(); // 输出: 状态更新: 登录中... (1秒后) 状态更新: 登录成功!
        
        // 测试空用户名密码
        loginView.setUsername("");
        loginView.setPassword("");
        loginView.onLoginButtonClick(); // 输出: 状态更新: 用户名和密码不能为空
    }
}

说明

  • 命令接口 (Command)​:
    • 定义了 execute() 方法,封装具体的操作逻辑
  • ViewModel 增强:
    • 添加了命令注册和执行功能
    • 维护一个命令映射表 (commands)
    • 提供 registerCommand(), getCommand() 和 executeCommand() 方法
  • ​具体命令实现:
    • LoginCommand 作为内部类实现,可以访问 ViewModel 的所有成员
    • 封装了登录业务逻辑,包括验证和异步操作
  • ​View 交互:
    • View 通过调用 executeCommand() 触发命令
    • 命令执行结果通过属性变更通知 View 更新
  • 优势:
    • 将业务逻辑封装在命令中,与 UI 解耦
    • 便于单元测试命令逻辑
    • 支持命令的组合、撤销等扩展功能

3. 观察者模式

ViewModel通过观察者模式通知View更新,常见实现方式:

  • LiveData (Android)
  • Observable (RxJava, Kotlin Flow)
  • Signals & Slots (Qt框架)

三、车载应用中的MVVM实现

1. 车载环境特点

  • 硬件资源有限
  • 高安全性和稳定性要求
  • 多屏幕、多交互方式(触摸、旋钮、语音)
  • 严格的性能要求

2. 推荐技术栈​

  • Android Automotive​:ViewModel + LiveData + Data Binding
  • Qt Automotive​:QML + C++ ViewModel
  • 跨平台方案​:Flutter + Provider/Bloc

3. 车载特有考虑

采用 Data Binding 作为示例:
① ViewModel:负责管理UI相关的数据,生命周期感知,配置变化时数据不丢失。

// ViewModel
public class ClimateViewModel extends ViewModel {
    private MutableLiveData<Integer> temperature = new MutableLiveData<>();
    
    public ClimateViewModel() {
        temperature.setValue(22); // 默认温度
    }
    
    public LiveData<Integer> getTemperature() {
        return temperature;
    }
    
    public void increaseTemp() {
        if (temperature.getValue() != null && temperature.getValue() < 30) {
            temperature.setValue(temperature.getValue() + 1);
        }
    }
}

LiveData:生命周期感知的数据持有者,自动管理订阅避免内存泄漏。
Data Binding:在布局文件中直接绑定ViewModel数据,减少样板代码。

② Activity类

public class ClimateActivity extends AppCompatActivity {
    private ActivityClimateBinding binding;
    private ClimateViewModel viewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // Data Binding初始化
        binding = DataBindingUtil.setContentView(this, R.layout.activity_climate);
        
        // 获取ViewModel
        viewModel = new ViewModelProvider(this).get(ClimateViewModel.class);
        
        // 绑定ViewModel到布局
        binding.setViewModel(viewModel);
        binding.setLifecycleOwner(this);
        
        // 传统方式观察LiveData(可选)
        viewModel.getTemperature().observe(this, temp -> {
            // 这里可以执行额外的UI更新逻辑
            Log.d("Climate", "Temperature updated: " + temp);
        });
    }
}

③ 布局文件 (activity_climate.xml)

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="viewModel"
            type="com.example.automotive.ClimateViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{String.valueOf(viewModel.temperature)}"
            android:textSize="24sp"/>
            
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Increase"
            android:onClick="@{() -> viewModel.increaseTemp()}"/>
    </LinearLayout>
</layout>

四、MVVM在车载开发中的最佳实践

1. 视图层设计原则

  • 保持View尽可能简单
  • 使用声明式UI(如Jetpack Compose/QML)
  • 处理多种输入方式(触摸、旋钮、语音)

2. ViewModel设计要点

  • 避免在ViewModel中持有View引用
  • 使用状态容器管理复杂UI状态
  • 考虑车载环境的生命周期管理

3. 模型层优化

  • 实现硬件抽象层(HAL)隔离业务逻辑
  • 使用Repository模式统一数据源
  • 考虑车载网络不稳定的缓存策略

参考文章:https://blog.youkuaiyun.com/O_____V_____O/article/details/147440989

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值