Controllers and MVC
The model-view-controller (MVC) design pattern is kind of the granddaddy of design patterns that is used to organize an application’s classes into specific groups (or, rather, layers) in order to provide better modularization of code and separation of concerns. The model layer contains objects that are responsible for representing the application’s data and business logic. The view layer contains objects that are responsible for visualizing model objects and other data and handling user interactions that get converted into application specific actions. The controller layer contains objects representing the application’s glue code, so to speak. By “glue”, controllers, in the traditional MVC sense, are responsible for a few things:
- Connecting views with model objects
- Handling actions sent by views
- Managing the application’s current state or states
- Updating model objects and views when responding to actions and state changes
- Acting as a delegate
- Handling events raised by other mechanisms that make up the application
In other words, the glue code contained within controllers is, in a nutshell, what is responsible for managing the application logic. Therefore, based on the defined roles models, views and controllers play in traditional MVC, you often get a picture that looks like this:
(//byMe: 为什么在view 可以和model通信的情况下,还需要经过controller来update model呢?
原因就在model这块的粒度画的太粗了,和view 直接通信的那部分model 属于部分ui的data,比如界面上的红色,checkbox选中和没选中对应的name的true or false;这些都是直接关联的。
另外一部分是要经过计算才知道的,比如有10的多选的checkbox,你选了4个,我要告诉用户还有6个可选, 你在输入栏里输入某些关键字,自动补齐的data是从服务端传过来的,这些都是computed data,不是和ui data直接关联,而是要通过计算得到的!
那么这部分的做“计算” 业务逻辑最好不要放在view当中, 而是要放在controller当中,controller帮你计算,读写model ;
比如上面的例子当中controller 计算还剩下6个,更新model当中的remaining number,并使得界面显示这个数字
所以把model 分成ui data 和computed data 就想通了,还有就是一些根本就不需要显示的数据,但是需要计算之后返回给server的,比如form当中的一些hidden的input ,也都属于computed data的范围
)
In SproutCore, controllers take on another but very important responsibility, and that is to act as a direct mediator between views and model objects. Compared to traditional MVC where views are more directly coupled to model objects, views and model objects in SproutCore are intended to be completely decoupled from each other via a controller. There are two primary reasons for this. The first is to insulate change between views and model objects. The second is to allow controllers to be able to transform and filter information passed between views and models. Therefore, looking at SproutCore’s MVC architecture, you instead get the following picture:
This idea of placing a controller directly between views and models is not original to SproutCore. Instead, SproutCore takes this controller concept right from the play book of Apple’sCocoa framework.
Compared to traditional type-safe languages that are rigid with respect to how methods and properties are invoked and accessed, both SproutCore and Cocoa remove this rigid nature by using KVC/KVO. This is why a view can work with a controller due to the get()
and set()
methods in order to access some backingcontent’s data and not care about the underlying type. (Granted with SproutCore and it being built on JavaScript, type checking is already pretty fluid). In addition, since bindings provide seamless and automatic flow of data propagation, controllers are designed to leverage that in order to filter and transform data that pass through them.
Mediating and Coordinating Controllers
Knowing that SproutCore and Cocoa add another role to controllers, the idea of controllers then take on two specific forms. That ofmediating controllers andcoordinating controllers.(//byMe: Sproutcore的model 和 view是不直接打交道的,对于view来说它只能通知controller, view 的observer也只能是controller!)
Mediating controllers are responsible for mediating the exchange of data between views and models, or, more generally, any kind of backingcontent, not just model objects.
SproutCore comes stock with three mediating controllers: SC.ObjectController
,SC.ArrayController
and SC.TreeController
. BothSC.ArrayController
and SC.TreeController
already provide built-in data transformation and filtering logic so that you don’t have to write the code yourself. Oh, and as you may have guessed, these three mediating controllers directly orginate from Cocoa’s controller equivalents.
Coordinating controllers do everything else other than mediate the flow of data. (它做中转数据流以外的事情,即所有的逻辑加工操作)
This means that coordinating controllers contain the rest of the application logic, which is to say, they handle actions forwarded by views,act as a delegate based on a specific interface protocol, manage an application’s states, and set mediating controllers’ backingcontent where necessary.
Whereas all mediating controllers having some backingcontent, coordinating controllers do not,which is why it is perfectly legitimate to make a coordinating controller a regular old SC.Object
. In fact a SproutCore application’s root application object (SC.Application
) can itself be a coordinating controller.
( //byme: 后来研读 了developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html
才知道为什么那么划分成Mediating 和Coordinating controller,前面讲了划分成为ui data 和computed data 两种,那么这两种controller也分别对应这两种data的, 因为这里的模式是严格的MVC三层,不能跨层次调用,和传统的MVC不一样。所以controller还承担了ui data (model part1) 到 view 的映射关系(绑定), 这部分独立出来的叫mediating controller(mc), 它就像个管子上联view,下联model (view binding 它,它再binding model; 产生了model变,view也变的效果,反过来也是)
另外一个是处理复杂的业务逻辑的coordinating controller (cc) ,它使用的机制在cocoa当中叫做 Target-Action Mechanism , 我自己的理解就是event-handler的变体或者说别名,that is, an object such as a button, slider, or text field—to send a message to another object that can interpret the message and handle it as an application-specific instruction. The receiving object, or the target, is usually a custom controller object. The message—named an action message—is determined by a selector, a unique runtime identifier of a method ), 这部分view发送指令(action), controller 提供handler 去处理,产生computed data 在model 当中,这部分如果需要显示出来(比如上面的搜索关键字,然后补齐),就使用mediating的机制reflect 在view当中,如果不需要显示,那么就直接更新相关data就好了,
这也就是为什么cocoa要修改原始的mvc的原因,要复用binding这种机制 ! 而且 computed data 分成4种:
1)需要计算后reflect到view的,不需要更新model;
2)计算后reflect到view 的,也需要更新model
3) 不需要 reflect到view 的, 只需要更新model
4)都不需要,只作为状态保持
对于 1 2 3, binding的机制就发挥作用了, 1) 因为v和c 已经binding了,此时cc只需要更新该c上binding的value就行,由于binding存在,它会更新v最终; 2) 类似,更新对应的c.value , 会自动更新m.value 和v.value 3)也类似, 更新
此时看上去mc负责把view的ui data和model的data(包括computed data) 映射到同一个controller.value1 和 value2 上 , cc负责get 这个controller.value1 or value2, 然后再set controller.value3, cc根本不用管这个value1 value2 value3 究竟是映射到哪个view还是model上,它只知道来了数据就要处理,此时,view 需要发送action给controller 来达到实现
也就是实现上mc和cc可能是位于同一个controller上,但是逻辑上它们是分开的,所以下面也说了This is why it is so common to see mediating controllers often containing logic that belongs to a coordinating controller.
我还有个比较变态的理解,就是mc就像windows 共享文件机制一样,负责几个A B C 目录分别从哪些机器映射而来,cc 只管对A B C进行读写操作,逻辑加工;最后数据被mc 同步
在sproutcore当中,认为coordinating controller 还是不够好,特别是app有状态的时候,于是变成了statechart,使用了event-->state-->handler 的形式去处理,我认为就是机制上增加了状态 ,变成了一个状态机,按照状态机的形式去处理
Controller Madness
As with all reality, things can get messy, and the idea of mediating controllers and coordinating controllers often get intertwined. This is why it is so common to see mediating controllers often containing logic that belongs to a coordinating controller. Not to say that this is a bad thing to do, but the lack of separation of concerns can reduce a controller’s cohesiveness and possibly make things harder to maintain.
In another case, coordinating controllers have a tendency to quickly fall victim in how its logic is organized to handle actions and manage an application’s state. Managing an application’s state either gets spread out across many coordinating controllers or gets all jumbled up into a few monolithic coordinating controllers. In addition, the idea of an application’s states are typically not centralized into individual, cohesive objects, but instead end up being represented as types of primitive values that are checked when a controller’s various methods are invoked. And often, those methods turn into brutish monsters containing many if-else or switch-case statements to know what state the application is currently in. Again, this all leads towards hard to maintain code.
Here Comes Statecharts to the Rescue!
The idea of separating application logic between mediating controllers and coordinating controller is a good idea in principle, but as discussed, reality steps in and can make things more challenging. Then what to do? Well, we recognized that we want to try and keep mediating controller code separate from coordinating controller code. As well, we realize we need a better way of organizing the coordinating controller logic so that we can better represent an application’s various states as cohesive, individual objects, and reduce, or simply remove, having to check what state we are in in order to perform some action.
Separating mediating logic from coordinating logic is more a matter of practice and discipline in assuring you have controllers to properly handle each instead of smashing the logic together. Therefore, to start, all your mediating controllers should be updated so they contain no coordinating logic, which will mean that the majority of your mediating controllers end up being very small with respect to the amount of code they contain. But what now happens to the coordinating logic? Where does it all go? Do you make a lot of coordinating controllers? The answer to all of that is statecharts.
To put it simply, statecharts supplant much of a coordinating controller’s use. Statecharts have the advantage over coordinating controllers since an application’s states are represented by actual objects and get organized in a hierarchy to provide state clustering, abstraction, and proper transitioning between states, among other benefits. In addition, all the checks a coordinating controller have to do to know what state the application is in and what logic to execute is effectively removed. This is because each state in a statechart knows what actions it is responsible for handling. If a state becomes a current state of the application then it will only handle those actions it is responsible for (or one of its parent states) and ignore all other actions. Therefore no need to check what the current state is.
Where statecharts take over for coordinating controllers, they do not for mediating controllers. A statechart’s states are not responsible for mediating the exchange of data between models and views via the use of bindings. Therefore you continue to make use of SproutCore’s stock mediating controllers.
What About Using the State Design Pattern?
Some of you reading this might be taking a bit of a pause and thinking to yourself that instead of using statecharts you could continue to use coordinating controllers and manage all the state and action complexity via theGang of Four‘s (GoF)state design pattern. That’s certainly an interesting idea, so let’s take a look at this approach.
The GoF’s state design pattern is used to decouple an object from its internal state so that the object’s state can be switched without changing the object’s type. To put it another way, some or all of the logic that is executed based on the object’s current state gets pulled out into separate state objects. This instead of keeping all that logic together and using various conditional checks to know what logic to execute. This provides a nice separation of concerns and keeps specific state logic grouped together to offer better cohesion and maintainable code. The following figure provides an illustration of how the GoF’s state design pattern works.
As you can see from the figure above, all of object foo
‘s logic that represents stateA goes into theStateA
class, and all of the logic that represents stateB goes into theStateB
class. Both states derive from a common state that thefoo
object interacts with. Whenfoo
switches state, the current state object will get switched. Therefore any object that interacts withfoo
,foo
‘s interface remains exactly the same and only its underlying logic changes. The following code provides an basic example of how the state design pattern could be implemented:
While the state design pattern is definitely useful, it starts to break down when used by itself under various scenarios, such as: Where states need to be nested and grouped within other states (parent states); there is a need for concurrent states that are active at the same time; when you want to forward actions across a number of states; and handling anything beyond basic state transitioning. You can obviously accomplish all of these goals by enhancing your state logic and combining it with other design patterns, such as the composite pattern, thechain of responsibility pattern, and so on. In addition, you can also make the solution more generic so that it can be applied across many coordinating controllers with less effort. But guess what? When you do all that work, you are basically recreating the statechart framework that already does all of that for you. In some sense, you can think of the statechart framework as being kind of like the state design pattern on steroids!
Where Coordinating Controllers Still Fit In
Earlier on I noted that statecharts supplant much of a coordinating controller’s use. What exactly do I mean by “much”? One area where coordinating controllers are still useful is when they act as adelegate for some other object — most commonly views. A view can make use of a delegate in order to provide some external object with the ability to help direct how parts of the view will operate or to handle more complex actions. The most common example of this is the collection view and how it uses a delegate to help handle things like drag-and-drop or how selected items are to be deleted.
A view that makes use of a delegate will often have a dedicated coordinating controller to take on the role. But if the statechart is now managing the application’s state and raised actions, how does a coordinating controller acting as a delegate for a view fit in? The answer to that is that the controller itself will either fully or partially delegate out to the statechart. Remember that when a view delegates beyond using a simple target-action delegate pair, which a statechart can already handle, it is often providing more information about what the delegate can do and how it should respond. Therefore, the coordinating controller acting as a delegate needs to translate that information into an action which a current state or states in a statechart can then handle. In some cases the translation is minimal and in other cases, such as using drag and drop, the translation can be more involved, depending on what you’re are trying to do.
As a basic example of a coordinating controller acting as a delegate and sending actions on to a statechart, let’s look at the following code:
First, the MyApp.userListDelegateController
is just a basicSC.Object
that mixes in theSC.CollectionViewDelegate
mixin. The controller in this particular case is handling the delegated action of deleting users from a list. In order for the statechart to handle the action, the controller is responsible for processing the given arguments supplied tocollectionViewDeleteContent
to then be passed to the statechart’s current states that will handle thedeleteSelectedUsers
action. The controller itself is not responsible for any other application logic — that is left to the states. The controller simply processes the arguments and then sends on an action to the statechart. This provides a clean separation of concerns and makes code more maintainable. The state that handles the action can then decide what to do, such as showing a confirmation dialog to the user asking if they are sure they want to delete the selected users.
In Conclusion
I ended up providing you with a detailed explanation about the differences between controllers and statecharts and what they are ideally used for when building your SproutCore application. However, although detailed, the central premise is that controllers can be split into two fundamental roles: mediating and coordinating. SproutCore comes stock with mediating controllers that you can use right away, and most of the application logic that did belong to coordinating controllers can now be shifted over to statecharts in order to better organize and manage an application’s states and handle actions. That being said, coordinating controllers still have a place when acting as a delegate and sending events to a statechart.
In the mean time, happy SproutCore coding!
-FC
-------------------------------------------
关于 Mediating Controllers vs Coordinating Controllers 参考 byme
http://developer.apple.com/library/mac/#documentation/General/Conceptual/CocoaEncyclopedia/Model-View-Controller/Model-View-Controller.html
https://developer.apple.com/library/mac/#documentation/General/Conceptual/DevPedia-CocoaCore/ControllerObject.html
http://developer.apple.com/library/ios/#DOCUMENTATION/Cocoa/Conceptual/CocoaFundamentals/CocoaDesignPatterns/CocoaDesignPatterns.html
https://github.com/DominikGuzei/sproutcore-statecharts-usecase <== 这个例子要好好看看