[Extracts from Pattern Oriented Software Architecture Buschmann et al.]
The Model-View-Controller architectural pattern (MVC) divides an interactive application into three components. The model contains the core functionality and data. Views display information to the user. Controllers handle user input. Views and controllers together comprise the user interface. A change-propagation mechanism ensures consistency between the user interface and the model.
Context:
Interactive applications with a flexible human-computer interface.
Problem:
User interfaces are especially prone to change requests. The user interface platform of long-lived systems thus represents a moving target.Different users place conflicting requirements on the user interface. Consequently, support for several user interface paradigms should be easily incorporated.
The following forces influence the solution:
- The same information is presented differently in different ways.
- The display and behavior of the application must reflect data manipulations immediately. Changes to the user interface should be easy and even possible at run-time.
- Supporting different 'look and feel' standards or porting the user interface should not affect code in the core of the application.
Solution:
Model:The model component encapsulates core data and functionality. The model is independent of specific output representations or input behavior.
View:View components display inform~tion to the user. A view obtains the data from the model. There can be multiple views of the model.
Controller:Each view has an associated controller component. Controllers receive input, usually as events. Events are translated to service requests for the model or the view. The user interacts with the system solely through controllers.
[Extracts from Pattern Oriented Software Architecture Buschmann et al.]The Layered architectural pattern helps to structure applications that can be decomposed into groups of subtasks in which each group of subtasks Is at a particular level of abstraction.
Context:
Large system that requires decomposition.Problem:
Imagine that you are designing a system whose dominant characteristic is a mix of low and high-level issues, where high-level operations rely on the lower-level ones. A typical pattern of communication flow consists of requests moving from high to low level, and answers to requests, incoming data or notification about events traveling in the opposite direction. Such systems often also require some horizontal structuring that is orthogonal to their vertical subdivision. This is the case where several operations are on the same level of abstraction but are largely independent of each other. - Late source code changes should not ripple through the system They should be confined to one component and not affect others. Interfaces should be stable, and may even be prescribed by a standards body.
- Parts of the system should be exchangeable.
- Similar responsibilities should be grouped to help understandability and maintainability.
- Crossing component boundaries may impede performance, for example when a substantial amount of data must be transferred over several boundaries, or where there are many boundaries to cross.
Solution:
From a high-level viewpoint the solution is extremely simple. Structure your system into an appropriate number of layers and place them on top of each other. Start at the lowest level of abstraction-ca11 it Layer 1. This is the base of your system. Work your way up the abstraction ladder by putting Layer J on top of Layer J -1 until you reach the top level of functionality-callit Layer N.The following scenarios are archetypes for the dynamic behavior of layered applications.- Scenario I : probably the best-known one. A client issues a request to Layer N. Since Layer N cannot cany out the request on its own, it calls the next Layer N -1 for supporting subtasks. Layer N -1 provides these, in the process sending further requests to Layer N -2, and so on until Layer 1 is reached. Here, the lowest-level services are finally performed. If necessary, replies to the different requests are passed back up from Layer 1 to Layer 2, from Layer 2 to Layer 3, and so on until the final reply arrives at Layer N.
- Scenario II : illustrates bottom-up communication-a chain of actions starts at Layer 1, for example when a device driver detects input. The driver translates the input into an internal format and reports it to Layer 2, which starts interpreting it, and so on. In this way data moves up through the layers until it arrives at the highest layer. Whiletop-down information and control flow are often described as 'requests', bottom-up calls can be termed 'notifications'.
- Scenario III : describes the situation where requests only travel through a subset of the layers. A top-level request may only go to the next lower level N -1 if this level can satisfy the request.
- Scenario IV : describes a situation similar to Scenario III. An event is detected in Layer 1, but stops at Layer 3 instead of traveling all the way up to Layer N.
- Scenario V : involves two stacks of N layers communicating with each other. This scenario is well-known from communication protocols where the stacks. are known as 'protocol stacks'.
Implementation:- Define the abstraction crieterion for grouping tasks into layers.
- Determine the number of abstraction levels according to your abstraction criterion.
- Name the layers and assign tasks to each of them. The task of the highest layer is the overall system task, as perceived by the client. The tasks of all other layers are to be helpers to higher layers.
- Specify the services. The most important implementation principle is that layers are strictly separated from each other, in the sense that no componel:lt may spread over more than one layer.
- Refine the layering. Iterate over steps 1 to 4. It is usually not possible to define an abstraction criterion precisely before thinking about the implied layers and their services.
- Specify an interfacefor each layer. If Layer J should be a 'black box' for Layer J+1, desIgn a flat interface that offers all Layer J's services, and perhaps encapsulate this interface in a Facade object. The Known Uses section describes flat interfaces further. A 'whitebox' approach is that in which Layer J+ 1 sees the internals of Layer J. The last figure in the Structure section shows a .gray-box' approach, a compromise between black and white box approaches. Here Layer J+ 1 is aware of the fact that Layer J consists of three components, and addresses them separately. but does not see the internal workings of individual components.
- Structure individual layers.
- Specify the communication between adjacent layers. The most often used mechanism for inter-Iayer communication is the push model. When Layer J invokes a service of Layer J -1, any required information is passed as part of the service call. The reverse is known as the pull model and occurs when the lower layer fetches available information from the higher layer at its own discretion.
- Decouple adjacent layers. There are many ways to do this. Often an upper layer is aware of the next lower layer, but the lower layer is unaware of the identity of its users. This implies a one-way coupling only: changes in Layer J can ignore the presence and identity of Layer J+ 1 provided that the interface and semantics of the Layer J services being changed remain stable. Such a one-way coupling is perfect when requests travel top-down, as illustrated in Scenario 1, as return values are sufficient to transport the results in the reverse direction. For bottom-up communication, you can use callbacks and still preserve a top-down one-way coupling. Here the upper layer registerscallback functions with the lower layer.
- Design an error-handling strategy. Error handling can be rather expensive for layered architectures with respect to processing time and, notably, programming effort. An error can either be handled in the layer where it occurred or be passed to the next higher layer. In the latter case, the lower layer must transform the error into an error description meaningful to the higher layer. As a rule of thumb, try to handle errors at the lowest layer possible. This prevents higher layers from being swamped with many different errors and voluminous error-handl1ng code.