MVC
MVC: 巨石型控制器
相信每一个程序猿都会宣称自己掌握
上面两篇论文中对于an abstraction in the form of data in a computing system.
,即为计算系统中数据的抽象表述,而capable of showing one or more pictorial representations of the Model on screen and on hardcopy.
,即能够将模型中的数据以某种方式表现在屏幕上的组件。而a special controller ... that permits the user to modify the information that is presented by the view.
,即主要负责对模型进行修改并且最终呈现在界面上。
从我的个人理解来看,Model View Controller (MVC) is one of the most quoted (and most misquoted) patterns around.
,将
根据上述定义,我们可以看到
- 用户交互输入了某些内容
Controller 将用户输入转化为Model 所需要进行的更改Model 中的更改结束之后,Controller 通知View 进行更新以表现出当前Model 的状态
根据上述流程,我们可知经典的
- View、Controller、
Model 中皆有ViewLogic 的部分实现 Controller 负责控制View 与Model ,需要了解View 与Model 的细节。View 需要了解Controller 与Model 的细节,需要在侦测用户行为之后调用Controller ,并且在收到通知后调用Model 以获取最新数据Model 并不需要了解Controller 与View 的细节,相对独立的模块
Observer Pattern: 自带观察者模式的MVC
上文中也已提及,
-
用户交互输入了某些内容
-
Controller 将用户输入转化为Model 所需要进行的更改 -
View 作为Observer 会监听Model 中的任意更新,一旦有更新事件发出,View 会自动触发更新以展示最新的Model 状态
可知其与经典的
iOS
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)
上面这种写法直接将
import UIKit
struct Person { // Model
let firstName: String
let lastName: String
}
class GreetingViewController : UIViewController { // View + Controller
var person: Person!
let showGreetingButton = UIButton()
let greetingLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
}
func didTapButton(button: UIButton) {
let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
self.greetingLabel.text = greeting
}
// layout code goes here
}
// Assembling of MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;
上面这种代码一看就很难测试,我们可以将生成viewDidLoad、didTapButton
等等较为费时的操作。再按照我们上文提及的优秀的架构的几个方面来看
Distribution:View 与Model 是分割开来了,不过View 与Controller 是紧耦合的Testability: 因为较差的职责分割导致貌似只有Model 部分方便测试- 易用性
: 因为程序比较直观,可能容易理解。
Android
此部分完整代码在这里,笔者在这里节选出部分代码方便对照演示。
TextView mCounterText;
Button mCounterIncrementButton;
int mClicks = 0;
public void onCreate(Bundle b) {
super.onCreate(b);
mCounterText = (TextView) findViewById(R.id.tv_clicks);
mCounterIncrementButton = (Button) findViewById(R.id.btn_increment);
mCounterIncrementButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mClicks++;
mCounterText.setText(""+mClicks);
}
});
}
后来
@Bind(R.id.tv_clicks) mCounterText;
@OnClick(R.id.btn_increment)
public void onSubmitClicked(View v) {
mClicks++;
mCounterText.setText("" + mClicks);
}
后来
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="counter" type="com.example.Counter"/>
<variable name="counter" type="com.example.ClickHandler"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{counter.value}"/>
<Buttonandroid:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{handlers.clickHandle}"/>
</LinearLayout>
</layout>
后来Anvil这样的受
MVC
- 声明
View 中的组件对象或者Model 对象
private Subscription subscription;
private RecyclerView reposRecycleView;
private Toolbar toolbar;
private EditText editTextUsername;
private ProgressBar progressBar;
private TextView infoTextView;
private ImageButton searchButton;
- 将组件与
Activity 中对象绑定,并且声明用户响应处理函数
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar = (ProgressBar) findViewById(R.id.progress);
infoTextView = (TextView) findViewById(R.id.text_info);
//Set up ToolBar
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//Set up RecyclerView
reposRecycleView = (RecyclerView) findViewById(R.id.repos_recycler_view);
setupRecyclerView(reposRecycleView);
// Set up search button
searchButton = (ImageButton) findViewById(R.id.button_search);
searchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadGithubRepos(editTextUsername.getText().toString());
}
});
//Set up username EditText
editTextUsername = (EditText) findViewById(R.id.edit_text_username);
editTextUsername.addTextChangedListener(mHideShowButtonTextWatcher);
editTextUsername.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String username = editTextUsername.getText().toString();
if (username.length() > 0) loadGithubRepos(username);
return true;
}
return false;
}
});
- 用户输入之后的更新流程
progressBar.setVisibility(View.VISIBLE);
reposRecycleView.setVisibility(View.GONE);
infoTextView.setVisibility(View.GONE);
ArchiApplication application = ArchiApplication.get(this);
GithubService githubService = application.getGithubService();
subscription = githubService.publicRepositories(username)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(application.defaultSubscribeScheduler())
.subscribe(new Subscriber<List<Repository>>() {
@Override
public void onCompleted() {
progressBar.setVisibility(View.GONE);
if (reposRecycleView.getAdapter().getItemCount() > 0) {
reposRecycleView.requestFocus();
hideSoftKeyboard();
reposRecycleView.setVisibility(View.VISIBLE);
} else {
infoTextView.setText(R.string.text_empty_repos);
infoTextView.setVisibility(View.VISIBLE);
}
}
@Override
public void onError(Throwable error) {
Log.e(TAG, "Error loading GitHub repos ", error);
progressBar.setVisibility(View.GONE);
if (error instanceof HttpException
&& ((HttpException) error).code() == 404) {
infoTextView.setText(R.string.error_username_not_found);
} else {
infoTextView.setText(R.string.error_loading_repos);
}
infoTextView.setVisibility(View.VISIBLE);
}
@Override
public void onNext(List<Repository> repositories) {
Log.i(TAG, "Repos loaded " + repositories);
RepositoryAdapter adapter =
(RepositoryAdapter) reposRecycleView.getAdapter();
adapter.setRepositories(repositories);
adapter.notifyDataSetChanged();
}
});