第2章第4节界面MVC(下)
稍好点的设计
上面讲解的示例,通过一个函数就完成了一个简单的数据展示和修改功能,但是代码杂糅混乱,是非常难以维护的代码。本小节我们通过不同的类和文件来实现不同角色的功能,让各个角色和模块按照职能彼此隔离,然后通过Controller来组装和拼接各个功能模块。
首先重复上面的步骤创建一个新的Activity。我们实现与上一小节同样的功能,只是换了一下代码内部的结构设计。
在我们的新的代码结构下,我们的View不再是通过代码来实现,而是通过xml布局文件和视图工具来实现。上一小节中,通过代码来设置页面布局,除非需要实现动态效果,否则一般人不会这样做,我们只是拿来作为一个极端的例子讲解。现实中我们遇到的大多数情况是,既有通过布局文件来设置页面布局,又有代码编写的动态布局,往往是杂糅在一起,这样的代码更难以维护。
Android studio帮我们开发了一个所见即所得的界面布局工具,建议大家多尝试一下,不过有个缺点就是不太稳定,我现在必须一边编辑布局文件,一边修改画布。如图:
这里不再复制布局文件的代码,大家可以自己尝试一下,一边修改布局文件,一边拖动画布的控件。我已经把所有代码上传到github,大家可以下载下来看一下,欢迎批评指正。
我们把View这个角色的工作交给了xml去实现,这样不管数据是如何获取,不管是哪个Controller来控制,他都可以独立于其他角色和模块而存在。
再来说Model部分,本节讲到的2个案例,其实都简化了Model部分的设计和功能,在后面的章节中,我们会通过网络或者数据库来提取数据和保存数据,这个过程和动作都应该归纳到Model这个角色,现在我们简单的把数据放到一个Student类里面,模拟一个弱化了的Model。
public class Student implements Serializable{
String name; String idCard;
int age; int grade;
......
这个类只是一个简单的Java bean,简单的get 和set方法,不再详细描述。在界面跳转之前,我们创建一个Student的对象,将数据保存到对象之后,传输给新界面,以此模拟数据的获取过程
public void jumpToGood(View view){
Intent intent = new Intent(this, MVCActivity.class);
Student student = new Student("李三", "wx002", 17,1);
intent.putExtra("student", student);
startActivity(intent);
}
在此过程中,我们的Model角色的工作就全部封装到了Studen这个类中,并且实例化一个名为student对象。这样关于此数据的修改和获取,都交给了一个独立的相对封闭的对象,分离了数据与android控件的直接关联。
最后是Controller部分,其实最牛的代码设计,应该是基础元素和功能非常简单和明晰,而复杂的上层的功能 ,只需要基础功能的简单拼接和组合。拼接复杂功能的过程和拼接积木的过程是类似的,顶级的架构设计,模块之间不勾联,复杂功能只需要模块之间简易的组合一下,接口简洁易操作。
而Controller角色的任务,就是组合和控制这些基础和简单的模块,组合出用户需要的界面,并控制与用户的互动过程。
public class MVCActivity extends AppCompatActivity {
Student student;
EditText vName,vIdCard,vAge, vGrade;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvc);
loadData();
loadViews();
paintViewByData();
}
private void loadData() {
student = (Student) getIntent().getSerializableExtra("student");
}
private void paintViewByData() {
vName.setText(student.getName());
vIdCard.setText(student.getIdCard());
vAge.setText(""+student.getAge());
vGrade.setText(""+student.getGrade());}
private void loadViews() {
vName = (EditText) findViewById(R.id.text_name);
vIdCard = (EditText) findViewById(R.id.text_idCard);
vAge = (EditText) findViewById(R.id.text_age);
vGrade = (EditText) findViewById(R.id.text_grade);
}
首先是加载数据和View视图,通过创建student成员变量来获取从其他界面传递过来的数据,然后把获取的数据赋值给对应的控件。
系统在运行的时候,会将xml的布局文件解析到内存中,在Controller里面可以直接调用 findViewById来获取布局文件中的控件,然后将student对象中对应的数据赋值到对应的控件上,这个过程最能体现Controller角色的工作了:加载数据,加载视图,把数据赋值在视图上。
还有一个过程是控制用户将数据输入到View,然后Controller将数据交付给指定的Model。
public void actionMod(View view){
student.setName(vName.getText().toString());
student.setIdCard(vIdCard.getText().toString());
student.setAge(Integer.valueOf(vAge.getText().toString()));
student.setGrade(Integer.valueOf(vGrade.getText().toString()));
//TODO::调用数据库存储或者网络存储
Toast.makeText(this, "修改完成", Toast.LENGTH_SHORT).show();
finish();
}
这里的 actionMod方法,在xml的布局文件中,我们指定了“修改”按钮在按下时对应的动作就是调用此函数。此方法中Controller调用了Model的方法,将View中的数据获取并赋值给Model的实例对象。此过程也充分的说明了Model,Controller,View三者的责任划分和各自角色特点。
一点设计经验
通过以上2个简单的示例,我们可以看出清晰的MVC设计,可以将一个工作细分给不同的角色,每个角色只关注自己的任务实现并提供友好的接口给调用者使用。这样的设计,好处除了代码清晰,逻辑关系明确之外,还可以非常方便的把代码分离并模块化。在软件工程里面,这就是所有架构师孜孜以求的高内聚低耦合境界。
本小节所谓的一点设计经验也是围绕耦合与内聚这个概念做的代码 上的优化,简单的理解耦合就是代码之间的关联关系,比如我们在第二个示例里面的OnCreate方法里面,把三个步骤写成了三个方法:
loadData(),loadViews(),paintViewByData()。但如果把三个方法删除,把三个方法的内容都放到OnCreate方法里面仍然是正确的,但是所有的代码逻辑都集成或者说耦合到了一个方法内,如果需要调整代码,那么整个模块的代码逻辑都要重新梳理,每个新增或者修改的地方都要考虑对其他代码逻辑的影响。这就叫高耦合。
内聚简单理解就是为了达到某一个目的,将相关逻辑代码放到同一个方法体内。还是以示例二中的OnCreate代码为例。如果我们把这个创建过程分为四个方法,每个方法都做类似的事情,第一个方法加载学生姓名的数据,加载学生姓名的展示控件,然后给控件赋值;第二个方法再处理学号,以此类推,虽然也降低了OnCreate方法的耦合,但是每个方法都几乎在重复类似的工作并且每个方法都操作Model和View这两个角色。因此这样的结构就是我们说的低内聚。
要做到高内聚低耦合,最好的办法就是将逻辑上单一但不重复的工作聚合到相同的代码结构内,不管是方法内还是其他类型结构。从逻辑上拆分工作,将每个工作所需要的资源整合到一起,形成工作模块,然后再将工作模块简单的组合,完成复杂的工作任务。
MVC的设计也是为了将代码逻辑解耦合,解耦合意味着工作有了明确的分工,那么将每个工作需要的代码逻辑放到相同的模块,就做到高内聚。因此良好的MVC设计可以降低耦合提高内聚。
总结
在这里讲解MVC的设计模式,除了让大家养成良好的代码编写习惯,理解代码分层的架构。更重要的是,要让初学者们理解,代码是如何将用户触碰界面的动作转化为可以用计算机处理的逻辑。
在第一章第2节中我们提到的客户端架构部分,里面的UI界面,数据和控制逻辑,就是本节讲解的内容。通过MVC的简单设计,我们将简单的任务分解,将逻辑关联紧密的代码聚合在一起,然后通过简单的组合,将相互之间关联很少的代码模块组装成为复杂的功能,这样既方便自己代码管理,又方便代码接管者维护。
其实在OnCreate方法里面,我们应该将加载的数据赋值给某一对象作为返回值,然后将加载的所有view封装到一个类或者某结构体中,作为加载视图方法的返回值,然后将返回的数据和结构体作为参数,传递给paintXX函数,这样更能够降低方法之间的耦合性,但是因为我们的示例过于简单,如果再整理出那么多复杂的结构体来实现这样简单的功能,反而显得臃肿和繁琐了,因此并不是一味追求高内聚低耦合就是对的,应该是做到恰到好处,如果为了追求某一设计原则而让代码臃肿繁琐,就顾此失彼了。
代码下载地址:https://github.com/9992800/paradiseInAnadem
欢迎关注微信号