- 浏览: 199578 次
文章分类
- 全部博客 (185)
- A Design Patterns (2)
- B Refactoring (0)
- C Test (2)
- D Software Engineering (0)
- E Other Tech Articles (4)
- F My Blog (0)
- G TechJie forum & QQ group (2)
- H Programmer (6)
- I 杂7杂8 (4)
- J Translations (0)
- [网站分类]1.首页原创精华.NET区(包含架构设计、设计模式)(对首页文章的要求:原创、高质量、经过认真思考并精心写作) (3)
- [网站分类]2..NET新手区(用于发表不合适发表在首页的.NET技术文章,包括小经验、小技巧) (14)
- [网站分类]3.非技术区(技术之外的文章,但不要涉及任何政治内容) (40)
- [网站分类]4.其他技术区 (9)
- [网站分类]5.企业信息化 (0)
- [网站分类]6.读书心得区(技术书籍阅读心得、书籍推荐) (8)
- [网站分类]7.提问区(.NET技术方面的提问) (6)
- [网站分类]8.技术转载区(.NET技术文章转载, 请注明原文出处) (24)
- [网站分类]9.求职招聘区(个人求职、企业招聘) (0)
- [网站分类]GIS技术 (0)
- [网站分类]SharePoint (0)
- [网站分类]博客园.NET俱乐部(俱乐部组织与活动方面的文章) (2)
- [网站分类]软件发布区(发布自己开发的代码、软件) (0)
- [网站分类]网站管理区(网站管理方面的疑问、建议、意见, 寻求管理员帮助) (0)
- [网站分类]业界新闻 (6)
最新评论
-
没有终点:
不懂 lz说的是啥 我太菜了
超简单SVN教程 -
韩悠悠:
超简单SVN教程 -
DraculaW:
orz...如果让他去写书很好奇这个作者会怎么解释vector ...
又见热书讨论《JavaScript征途》 -
gigix:
weiqingfei 写道为什么国内写书的都是烂人?
为什么高 ...
又见热书讨论《JavaScript征途》 -
weiqingfei:
为什么国内写书的都是烂人?为什么高手都不写书?
又见热书讨论《JavaScript征途》
Presentation Model - Martin's "GUI Architectures" series (3)
zz from www.martinfowler.com
To be translated...
Presentation Model
Represent the state and behavior of the presentation independently of the GUI controls used in the interface
Also Known As: Application Model
GUIs consist of widgets that contain the state of the GUI screen. Leaving the state of the GUI in widgets makes it harder to get at this state, since that involves manipulating widget APIs, and also encourages putting presentation behavior in the view class.
Presentation Model pulls the state and behavior of the view out into a model class that is part of the presentation. The Presentation Model coordinates with the domain layer and provides an interface to the view that minimizes decision making in the view. The view either stores all its state in the Presentation Model or synchonizes its state with Presentation Model frequently
Presentation Model may interact with several domain objects, but Presentation Model is not a GUI friendly facade to a specific domain object. Instead it is easier to consider Presentation Model as an abstract of the view that is not dependent on a specific GUI framework. While several views can utilize the same Presentation Model, each view should require only one Presentation Model. In the case of composition a Presentation Model may contain one or many child Presentation Model instances, but each child control will also have only one Presentation Model.
Presentation Model is known to users of Visual Works Smalltalk as Application Model
How it Works
The essence of a Presentation Model is of a fully self-contained class that represents all the data and behavior of the UI window, but without any of the controls used to render that UI on the screen. A view then simply projects the state of the presentation model onto the glass.
To do this the Presentation Model will have data fields for all the dynamic information of the view. This won't just include the contents of controls, but also things like whether or not they are enabled. In general the Presentation Model does not need to hold all of this control state (which would be lot) but any state that may change during the interaction of the user. So if a field is always enabled, there won't be extra data for its state in the Presentation Model.
Since the Presentation Model contains data that the view needs to display the controls you need to synchronize the Presentation Model with the view. This synchronization usually needs to be tighter than synchronization with the domain - screen synchronization is not sufficient, you'll need field or key synchronization.
To illustrate things a bit better, I'll use the aspect of the running example where the composer field is only enabled if the classical check box is checked.
Figure 1: Classes showing structure relevant to clicking the classical check box
Figure 2: How objects react to clicking the classical check box.
When someone clicks the classical check box the check box changes its state and then calls the appropriate event handler in the view. This event handler saves the state of the view to Presentation Model and then updates itself from the Presentation Model (I'm assuming a coarse-grained synchronization here.) The Presentation Model contains the logic that says that the composer field is only enabled if the check box is checked, so the when the view updates itself from the Presentation Model, the composer field control changes its enablement state. I've indicated on the diagram that the Presentation Model would typically have a property specifically to mark whether the composer field should be enabled. This will, of course, just return the value of the isClassical property in this case - but the separate property is important because that property encapsulates how the Presentation Model determines whether the composer field is enabled - clearly indicating that that decision is the responsibility of the Presentation Model.
This small example is illustrates the essence of the idea of the Presentation Model - all the decisions needed for presentation display are made by the Presentation Model leaving the view to be utterly simple.
[TBD: Add something briefly on how Visual Works does this.]
Probably the most annoying part of Presentation Model is the synchronization between Presentation Model and view. It's simple code to write, but I always like to minimize this kind of boring repetitive code. Ideally some kind of framework could handle this, which I'm hoping will happen some day with technologies like .NET's data binding.
A particular decision you have to make with synchronization in Presentation Model is which class should contain the synchronization code. Often, this decision is largely based on the desired level of test coverage and the chosen implementation of Presentation Model. If you put the syncronization in the view, it won't get picked up by tests on the Presentation Model. If you put it in the Presentation Model you add a dependency to the view in the Presentation Model which means more coupling and stubbing. You could add a mapper between them, but adds yet more classes to coordinate. When making the decision of which implementation to use it is important to remember that although faults do occur in synchronization code, they are usually easy to spot and fix (unless you use fine-grained synchronization).
An important implementation detail of Presentation Model is whether the View should reference the Presentation Model or the Presentation Model should reference the View. Both implementations provide pros and cons.
A Presentation Model that references a view generally maintains the syncronization code in the Presentation Model. The resulting view is very dumb. The view contains setters for any state that is dynamic and raises events in response to user actions. The views implement interfaces allowing for easy stubbing when testing the Presentation Model. The Presentation Model will observe the view and respond to events by changing any appropriate state and reloading the entire view. As a result the syncronization code can be easily tested without needing the actual UI class.
A Presentation Model that is referenced by a view generally maintains the syncronication code in the view. Because the syncronization code is generally easy to write and easy to spot errors it is recommended that the testing occur on the Presentation Model and not the View. If you are compelled to write tests for the view this should be a clue that the view contains code that should belong in the Presentation Model. If you prefer to test the syncronization, a Presentation Model that references a view implementation is recommended.
When to use it
Presentation Model is a pattern that pulls presentation behavior from a view. As such it's an alternative to to Supervising Controller and Passive View. It's useful for allowing you to test without the UI, support for some form of multiple view and a separation of concerns which may make it easier to develop the user interface.
Compared to Passive View and Supervising Controller, Presentation Model allows you to write logic that is completely independent of the views used for display. You also do not need to rely on the view to store state. The downside is that you need a synchronization mechanism between the presentation model and the view. This synchronization can be very simple, but it is required. Separated Presentation requires much less synchronization and Passive View doesn't need any at all.
Example: Running Example (View References PM) (C#)
Here's a version of the running example, developed in C# with Presentation Model.
Figure 3: The album window.
I'll start discussing the basic layout from the domain model outwards. Since the domain isn't the focus of this example, it's very uninteresing. It's essentially just a data set with a single table holding the data for an album. Here's the code for setting up a few test albums. I'm using a strongly typed data set.
public static DsAlbum AlbumDataSet() { DsAlbum result = new DsAlbum(); result.Albums.AddAlbumsRow(1, "HQ", "Roy Harper", false, null); result.Albums.AddAlbumsRow(2, "The Rough Dancer and Cyclical Night", "Astor Piazzola", false, null); result.Albums.AddAlbumsRow(3, "The Black Light", "Calexico", false, null); result.Albums.AddAlbumsRow(4, "Symphony No.5", "CBSO", true, "Sibelius" ); result.AcceptChanges(); return result; }
The Presentation Model wraps this data set and provides properties to get at the data. There's a single instance of the Presentation Model for the whole table, corresponding to the single instance of the window. The Presentation Model has fields for the data set and also keeps track of which album is currently selected.
class PmodAlbum... public PmodAlbum(DsAlbum albums) { this._data = albums; _selectedAlbumNumber = 0; } private DsAlbum _data; private int _selectedAlbumNumber;
PmodAlbum provides properties to get at the data in the data set. Essentially I provide a property for each bit of information that the form needs to display. For those values which are just pulled directly out of the data set, this property is pretty simple.
class PmodAlbum... public String Title { get {return SelectedAlbum.Title;} set {SelectedAlbum.Title = value;} } public String Artist { get {return SelectedAlbum.Artist;} set {SelectedAlbum.Artist = value;} } public bool IsClassical { get {return SelectedAlbum.IsClassical;} set {SelectedAlbum.IsClassical = value;} } public String Composer { get { return (SelectedAlbum.IsComposerNull()) ? "" : SelectedAlbum.Composer; } set { if (IsClassical) SelectedAlbum.Composer = value; } } public DsAlbum.AlbumsRow SelectedAlbum { get {return Data.Albums[SelectedAlbumNumber];} }
The title of the window is based on the album title. I provide this through another property.
class PmodAlbum... public String FormTitle { get {return "Album: " + Title;} }
I have a property to see if the composer field should be enabled.
class PmodAlbum... public bool IsComposerFieldEnabled { get {return IsClassical;} }
This is just a call to the public IsClassical property. You may wonder why the form doesn't just call this directly - but this is the essence of the encapsulation that the Presentation Model provides. PmodAlbum decides what the logic is for enabling that field, the fact that it's simply based on a property is known to the Presentation Model but not to the view.
The apply and cancel buttons should only be enabled if the data has changed. I can provide this by checking the state of that row of the dataset, since data sets record this information.
class PmodAlbum... public bool IsApplyEnabled { get {return HasRowChanged;} } public bool IsCancelEnabled { get {return HasRowChanged;} } public bool HasRowChanged { get {return SelectedAlbum.RowState == DataRowState.Modified;} }
The list box in the view shows a list of the album titles. PmodAlbum provides this list.
class PmodAlbum... public String[] AlbumList { get { String[] result = new String[Data.Albums.Rows.Count]; for (int i = 0; i < result.Length; i++) result[i] = Data.Albums[i].Title; return result; } }
So that covers the interface that PmodAlbum presents to the view. Next I'll look at how I do the synchronization between the view and the Presentation Model. I've put the synchronization methods in the view and am using coarse-grained synchronization. First I have a method to push the state of the view into the Presentation Model.
class FrmAlbum... private void SaveToPmod() { model.Artist = txtArtist.Text; model.Title = txtTitle.Text; model.IsClassical = chkClassical.Checked; model.Composer = txtComposer.Text; }
This method is very simple, just assigning the mutable parts of the view to the Presentation Model. The load method is a touch more complicated.
class FrmAlbum... private void LoadFromPmod() { if (NotLoadingView) { _isLoadingView = true; lstAlbums.DataSource = model.AlbumList; lstAlbums.SelectedIndex = model.SelectedAlbumNumber; txtArtist.Text = model.Artist; txtTitle.Text = model.Title; this.Text = model.FormTitle; chkClassical.Checked = model.IsClassical; txtComposer.Enabled = model.IsComposerFieldEnabled; txtComposer.Text = model.Composer; btnApply.Enabled = model.IsApplyEnabled; btnCancel.Enabled = model.IsCancelEnabled; _isLoadingView = false; } } private bool _isLoadingView = false; private bool NotLoadingView { get {return !_isLoadingView;} }
private void SyncWithPmod() { if (NotLoadingView) { SaveToPmod(); LoadFromPmod(); } }
The complication here is avoiding a infinite recursion since synchronizing causes fields on the form to udpate which triggers synchronization.... I guard against that with a flag.
With these synchronization methods in place, the next step is just to call the right bit of synchronization in event handlers for the controls. Most of the time this easy, just call SyncWithPmod
when data changes.
class FrmAlbum... private void txtTitle_TextChanged(object sender, System.EventArgs e){ SyncWithPmod(); }
Some cases are more involved. When the user clicks on a new item in the list we need to navigate to a new album and show its data.
class FrmAlbum... private void lstAlbums_SelectedIndexChanged(object sender, System.EventArgs e){ if (NotLoadingView) { model.SelectedAlbumNumber = lstAlbums.SelectedIndex; LoadFromPmod(); } }
class PmodAlbum... public int SelectedAlbumNumber { get {return _selectedAlbumNumber;} set { if (_selectedAlbumNumber != value) { Cancel(); _selectedAlbumNumber = value; } } }
Notice that this method abandons any changes if you click on the list. I've done this awful bit of usability to keep the example simple, the form should really at least pop up a confirmation box to avoid losing the changes. [TBD: Consider adding this to the example.]
The apply and cancel buttons delegate what to do to the Presentation Model.
class FrmAlbum... private void btnApply_Click(object sender, System.EventArgs e) { model.Apply(); LoadFromPmod(); } private void btnCancel_Click(object sender, System.EventArgs e){ model.Cancel(); LoadFromPmod(); }
class PmodAlbum... public void Apply () { SelectedAlbum.AcceptChanges(); } public void Cancel() { SelectedAlbum.RejectChanges(); }
So although I can move most of the behavior to the Presentation Model, the view still retains some intelligence. For the testing aspect of Presentation Model to work better, it would be nice to move more. Certainly you can move more into the Presentation Model by moving the synchronization logic there, at the expense of having the Presentation Model know more about the view.
Example: Data Binding Table Example (C#)
As I first looked at Presentation Model in the .NET framework, it seemed that data binding provided excellent technology to make Presentation Model work simply. So far limitations in the current version of data binding holds it back from places that I'm sure it will eventually go. One area where data binding can work very well is with read-only data, so here is an example that shows this as well as how tables can fit in with a Presentation Model design.
Figure 4: A list of albums with the rock ones highlighted.
This is just a list of albums. The extra behavior is that each rock album should have it's row colored in cornsilk.
I'm using a slightly different data set to the other example. Here is the code for some test data.
public static AlbumList AlbumGridDataSet() { AlbumList result = new AlbumList(); result.Albums.AddAlbumsRow(1, "HQ", "Roy Harper", "Rock"); result.Albums.AddAlbumsRow(2, "Lemonade and Buns", "Kila", "Celtic"); result.Albums.AddAlbumsRow(3, "Stormcock", "Roy Harper", "Rock"); result.Albums.AddAlbumsRow(4, "Zero Hour", "Astor Piazzola", "Tango"); result.Albums.AddAlbumsRow(5, "The Rough Dancer and Cyclical Night", "Astor Piazzola", "Tango"); result.Albums.AddAlbumsRow(6, "The Black Light", "Calexico", "Rock"); result.Albums.AddAlbumsRow(7, "Spoke", "Calexico", "Rock"); result.Albums.AddAlbumsRow(8, "Electrica", "Daniela Mercury", "Brazil"); result.Albums.AddAlbumsRow(9, "Feijao com Arroz", "Daniela Mercury", "Brazil"); result.Albums.AddAlbumsRow(10, "Sol da Libertade", "Daniela Mercury", "Brazil"); Console.WriteLine(result); return result; }
The presentation model in this case reveals its internal data set as a property. This allows the form to data bind directly to the cells in the data set.
private AlbumList _dsAlbums; internal AlbumList DsAlbums { get {return _dsAlbums;} }
To support the highlighting, the presentation model provides an additional method that indexes into the table.
internal Color RowColor(int row) { return (Albums[row].genre.Equals("Rock")) ? Color.Cornsilk : Color.White; } private AlbumList.AlbumsDataTable Albums { get {return DsAlbums.Albums;} }
This method is similar to the ones in a simple example, the difference being that methods on table data need cell coordinates to pick out parts of the table. In this case all we need is a row number, but in general we may need row and column numbers.
From here on I can use the standard data binding facilities that come with visual studio. I can bind table cells easily to data in the data set, and also to data on the Presentation Model.
Getting the color to work is a little bit more involved. This is straying a little bit away from the main thrust of the example, but the whole thing gets its complication becuase there's no way to have row by row highlighting on the standard WinForms table control. In general the answer to this need is to buy a third party control, but I'm too cheap to do this. So for the curious here's what I did (the idea was mostly ripped off from http://www.syncfusion.com/FAQ/WinForms/). I'm going to assume you're familiar with the guts of WinForms from now on.
Essentially I made a subclass of DataGridTextBoxColumn
which adds the color highlighting behavior. You link up the new behavior by passing in a delegate that handles the behavior.
class ColorableDataGridTextBoxColumn... public ColorableDataGridTextBoxColumn (ColorGetter getcolorRowCol, DataGridTextBoxColumn original) { _delGetColor = getcolorRowCol; copyFrom(original); } public delegate Color ColorGetter(int row); private ColorGetter _delGetColor;
The constructor takes the original DataGridTextBoxColumn as well as the delegate. What I'd really like to do here is to use the decorator pattern to wrap the original but the original, like so many classes in WinForms, is all sealed up. So instead I copy over all the properties of the original into my subclass. This won't work is there are vital properties that can't be copied because you can't read or write to them. It seems to work here for now.
class ColorableDataGridTextBoxColumn... void copyFrom (DataGridTextBoxColumn original) { PropertyInfo[] props = original.GetType().GetProperties(); foreach (PropertyInfo p in props) { if (p.CanWrite && p.CanRead) p.SetValue(this, p.GetValue(original, null), null) ; } }
Fortunately the paint method is virtual (otherwise I would need a whole new data grid.) I can use it to insert the appropriate background color using the delegate.
class ColorableDataGridTextBoxColumn... protected override void Paint(System.Drawing.Graphics g, System.Drawing.Rectangle bounds, System.Windows.Forms.CurrencyManager source, int rowNum, System.Drawing.Brush backBrush, System.Drawing.Brush foreBrush, bool alignToRight) { base.Paint(g, bounds, source, rowNum, new SolidBrush(_delGetColor(rowNum)), foreBrush, alignToRight); }
To put this new table in place, I replace the columns of the databable in the page load after the controls have been built on the form.
class FrmAlbums... private void FrmAlbums_Load(object sender, System.EventArgs e){ bindData(); replaceColumnStyles(); } private void replaceColumnStyles() { ColorableDataGridTextBoxColumn.ReplaceColumnStyles(dgsAlbums, new ColorableDataGridTextBoxColumn.ColorGetter(model.RowColor)); }
class ColorableDataGridTextBoxColumn... public static void ReplaceColumnStyles(DataGridTableStyle grid, ColorGetter del) { for (int i = 0; i < grid.GridColumnStyles.Count; i++) { DataGridTextBoxColumn old = (DataGridTextBoxColumn) grid.GridColumnStyles[0]; grid.GridColumnStyles.RemoveAt(0); grid.GridColumnStyles.Add(new ColorableDataGridTextBoxColumn(del, old)); } }
It works, but I'll admit it's a lot more messy than I would like. If I were doing this for real, I'd want to look into a thrid party control. However I've seen this done in a production system and it worked just fine.
Significant Revisions
19 Jul 04: First public release.
13 May 04: First TW publication.
发表评论
-
Rhino Mocks To The Rescure
2006-11-23 09:17 800http://developernotes.com/archi ... -
Some high level concepts
2006-11-23 09:25 961http://structuremap.sourceforge ... -
Singletons Are Evil
2006-11-23 09:56 827http://c2.com/cgi/wiki?Singleto ... -
Best and Worst Practices for Mock Objects
2006-11-23 10:00 774http://codebetter.com/blogs/jer ... -
小函数:让你的函数变短的九个好处
2006-12-04 22:15 1137Small Methods: Nine Benefits ... -
UML序列图(zz)
2007-03-19 11:28 1293级别: 初级 Donald BellIBM 2005 年 2 ... -
重写方法不被认为是在类上声明的
2007-03-22 14:54 580当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两 ... -
One Assertion Per Test(zz)
2007-05-18 10:15 2180zz from :http://www.artima.co ... -
One Expectation Per Test(zz)
2007-05-18 10:18 813zz From: http://jupitermoonbeam ... -
Supervising Controller - Martin's "GUI Architectures" series (2)
2007-05-31 12:16 847zz from www.martinfowler.com To ... -
Presentation Model - Martin's "GUI Architectures" series (3)
2007-05-31 12:18 888zz from www.martinfowler.com To ... -
Passive View - Martin's "GUI Architectures" series (4)
2007-05-31 12:19 823zz from www.martinfowler.com To ... -
Rhino Mocks To The Rescure
2006-11-23 09:17 926http://developernotes.com/archi ... -
Some high level concepts
2006-11-23 09:25 885http://structuremap.sourceforge ... -
Singletons Are Evil
2006-11-23 09:56 721http://c2.com/cgi/wiki?Singleto ... -
Best and Worst Practices for Mock Objects
2006-11-23 10:00 607http://codebetter.com/blogs/jer ... -
小函数:让你的函数变短的九个好处
2006-12-04 22:15 818Small Methods: Nine Benefits ... -
UML序列图(zz)
2007-03-19 11:28 1631级别: 初级 Donald BellIBM 2005 年 2 ... -
重写方法不被认为是在类上声明的
2007-03-22 14:54 814当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两 ... -
One Assertion Per Test(zz)
2007-05-18 10:15 801zz from :http://www.artima.co ...
相关推荐
在分析提供的文件内容后,以下是关于“phy-interface-pci-express-sata-usb30-architectures-3-1.pdf v5.2”的详细知识点说明。 首先,文件标题“phy-interface-pci-express-sata-usb30-architectures-3.1.pdf v5.2...
64 and IA-32 Architectures Software Developer’s Manual. A detailed understanding of Intel 64 and IA-32 processors is often required. In many cases, knowledge of the underlying microarchitectures is ...
PIPE协议,PHY Interface For the PCI Express, SATA, USB 3.1, DisplayPort, and Converged I0 Architectures Version 5.2.1
##### 3. 实现案例与评估 Eric Charles Quinnell在论文中详细描述了几种不同应用场景下的FMA架构实现方案,并进行了实验评估。这些案例涵盖了从基本的算术运算到复杂的科学计算,展示了FMA架构在不同场景下的优势和...
《64-ia-32-architectures-software-developer-manual》是Intel公司发布的一本详尽的开发者指南,主要关注64位和32位架构的指令集。这本书对于理解Intel处理器的工作原理、优化代码以及进行系统级编程至关重要。在...
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3B: System Programming Guide, Part 2
标题中的“64-ia-32-architectures-software-developer-vol-2b-manual.zip”指的是Intel公司发布的64位IA-32架构软件开发人员手册的第二卷B部分。这个压缩包包含了一份PDF文档,详细阐述了与Intel处理器相关的编程和...
This document contains the following: Volume 1: Describes the architecture and programming...Volume 4: Describes the model-specific registers of processors supporting IA-32 and Intel® 64 architectures.
Addison Wesley - The UML Profile for Framework Architectures.chm
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3A: System Programming Guide, Part 1
Volume 1: Describes the architecture and programming environment of processors supporting ...Volume 4: Describes the model-specific registers of processors supporting IA-32 and Intel® 64 architectures.
SC3 - Challenges and Architectures.pdf SC4 - Emerging Topics in Phased Arrays and the Path to THz.pdf Session 01 - Plenary.pdf Session 02 - Processors.pdf Session 03 - Nyquist Rate ADCs.pdf ...
接着,提及的“Intel® 64 and IA-32 Architectures Software Developer’s Manual”是由多个卷组成的详细手册系列。每一卷覆盖了架构和编程方面的特定内容。在本部分描述中,提到了七卷具体的顺序号(Order Number...
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2A: Instruction Set Reference, A-M
The book is organized into four ... the third section deals with advanced topics and detailed network architectures; and in the fourth section two appendices provide important supporting material.
文档标题为"pipe6.0_phy-interface-pci-express-sata-usb30-architectures-3-1.pdf",这表明文档是关于PHY(物理层)接口的。它详细介绍了与PCI Express、SATA(串行高级技术附件)、USB 3.1、DisplayPort和USB4架构...
《Intel 64 and IA-32 Architectures Software Developer’s Manual》是Intel公司为开发者提供的一份详尽的硬件和系统编程指南,涵盖了从基本架构到指令集参考、再到系统编程指南等多个方面,是理解Intel处理器及...