[转载]pureMVC实践

[转载]pureMVC实践 – 挫鸟 – 博客园.

MVC模式是针对有相对复杂的用户交互应用的一种设计模式。由于产品迭代速度快,用户界面往往会发生重大变更,而业务逻辑也经常会因为用户的反馈而修正。

应用采用事件驱动,用户操作视图,视图产生相应事件,事件触发事件处理函数,事件处理函数执行业务逻辑,修改模型数据,模型数据通知视图数据已经修改,视图根据模型数据修正本身的表现。

然而糟糕的设计中,界面部分代码和事件处理函数往往混杂在一起,模型数据到处都可以引用修改,各部分耦合严重,当有某部分有变更时,往往发现牵扯的代码很多,小心翼翼的修改完每一处以后,又带来了未知数量的BUG。

在MVC模式中,严格把领域模型,控制器,和视图解耦,使得每一部分的变更都尽可能小的影响其他部分,在中大型项目中,也使技术水平不一的程序员可以在框架制约下,写出更具一致性的代码(个人认为这一点比前者带来的好处更诱人)。

下图是传统MVC模式中模块之间的通信图:

如果你尝试过在项目中引入MVC模式,会发现很多时候,你会有职责划分的困扰:

    1. 事件触发后,并不单纯只是通过控制器修改模型数据,有时需要修改视图的展现,甚至只是修改视图的展现,那这部分职责也要划分到控制器中去吗?(那对于用户交互复杂的应用来讲,控制器的职责未免过多了吧?)
    2. 视图通过事件处理函数引发控制器执行逻辑,则必须要保存会控制器的引用,如果把所有的逻辑统统写到事件处理函数中呢?(好吧,假如你要更换界面库呢?)
    3. 模型数据可能有多种,每种模型数据也有多种可以执行的操作,那控制器到底要做到多大粒度?(一对多?多对多?)
    4. 模型更新后对视图的通知,由模型保存视图的引用来进行通知?通过事件机制来通知?(不管如何又带来了视图和模型之间的耦合)

想象一下在一个多人协作的项目组中,部分程序员负责构建领域模型,部分程序员 负责构建业务逻辑,部分程序员负责界面展现,全部资源按照特长各尽其职,但是不管是糟糕的设计,还是传统的MVC模式,所有部分的工作都必须在得到其他部 分工作的支持之后才能完成。然后不管是项目进度还是办公室又变成了一片狼藉。

针对传统MVC的问题,pureMVC将各部分进一步解耦,并以最简单的形式把经典设计模式应用到设计中,在代码构建中,为小中型项目提供了构建基础。当然,任何收益都是有代价的,对于pureMVC所引入的问题,本文将会在文章末尾和大家讨论。

为了解决上面提到的问题1和2,pureMVC引入Mediator模式解耦视图和控制器,Mediator负责接收视图事件,执行必要的视图展现逻辑和通过控制器来执行业务逻辑。

针对问题3,pureMVC引入了Command来形成自然而然的对应关系,每个Command对应某一操作,通过简单的修改,我们还可以得到附加的收获,支持撤销和重做。

针对问题4,pureMVC通过通知机制来解决传统事件机制的一些问题,程序员只需要注册通知和命令的对应关系,对每种可接收通知的类型,罗列它们感兴趣的通知类型并编写通知处理就可以了。

pureMVC还在控制器和数据模型之间引入了Proxy来解耦,这样可以自然的解决远程数据的存取,也为模型数据改变通知的发出找到了责任对象(由Proxy验证域逻辑,执行操作,并发出模型数据更新通知)。

下面是pureMVC框架的模块示意图:

这样,用户操作通过Mediator,执行了必要的展现逻辑之后,发出通知;通知触发一个命令来执行相应的业务逻辑修改模型数据;模型数据发出通知,通知所有感兴趣的视图根据修改过的数据来更新视图。

这样,负责不同部分的同事只要事先约定系统中有哪些通知,并约定一些命名规则,就可以并行进行自己的工作,单元测试并按计划交付整合了。

下面是pureMVC新引入模式的通信图:

说了这么多,可能有些同行还是摸不清思路,下面我们用一个实际的构建过程来说明一个基于pureMVC框架的应用是如何运作的。

pureMVC是一个开源项目,该框架在不同平台下的代码都可以在官方网站找到,需要的同行可以找来配合本文阅读。

假设我们在.NET平台下用C#开发一个动画编辑器项目,在对动画每一帧的编辑行为中,自然的划分出了以下职责对象:

  • Control:用户视图组件,负责呈现已经编辑完成的帧并接受用户操作
  • FrameEditMediator:负责接受事件,执行必要的视图展现逻辑更在必要时形成命令修改帧数据,接受数据模型更改通知并更新视图展现

public Edit()
: base("Edit")
{
size = new Size(240, 320);
scale = 1;

BufferedPanel panel = new BufferedPanel();
panel.Location = new System.Drawing.Point(0, 0);
panel.Size = size;
panel.TabIndex = 0;
panel.Paint += new System.Windows.Forms.PaintEventHandler(this.frameEditor_Paint);
panel.MouseDown += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseDown);
panel.MouseMove += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseMove);
panel.MouseUp += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseUp);
panel.MouseWheel += new System.Windows.Forms.MouseEventHandler(this.frameEditor_MouseWheel);
m_viewComponent = panel;
}

public override IList ListNotificationInterests()
{
List intereste = new List();
intereste.Add(Shelf.SelectedChanged);
return intereste;
}

public override void HandleNotification(INotification notification)
{
switch (notification.Name)
{
case Shelf.SelectedChanged:
{
Panel panel = (Panel)m_viewComponent;
IProxy proxy;

proxy = Facade.RetrieveProxy("Group");
group = (Group)proxy.Data;

proxy = Facade.RetrieveProxy("Animation");
animation = (Animation)proxy.Data;

proxy = Facade.RetrieveProxy("Frame");
frame = (Frame)proxy.Data;

panel.Refresh();
}
break;
default:
break;
}
}

private void frameEditor_MouseDown(object sender, MouseEventArgs e)
{
Panel p = (Panel)(sender);

if (e.Button == MouseButtons.Left)
{
p.Focus();
}
}

private void frameEditor_MouseMove(object sender, MouseEventArgs e)
{

}

private void frameEditor_MouseUp(object sender, MouseEventArgs e)
{

}

private void frameEditor_Paint(object sender, PaintEventArgs e)
{
//todo:
}

private void frameEditor_MouseWheel(object sender, MouseEventArgs e)
{
//todo:
}

private Size size;
private int scale;

private Group group;
private Animation animation;
private Frame frame;
}

AddSegmentCommand:对每一帧在指定位置添加指定图形切片的命令

{
public AddSegmentCommand()
{

}

public override void Execute(INotification notification)
{
//todo:
}
}

FrameProxy:验证命令合法性,对真正的模型数据执行修改,当修改成功后,发出数据更新的通知

{
public const String SegmentAdded = "FrameProxy.SegmentAdded";
public const String SegmentRemoved = "FrameProxy.SegmentRemoved";
public const String SegmentOrderChanged = "FrameProxy.SegmentOrderChanged";

public FrameProxy()
: base("Frame", null)
{ }

public void AddSegment(int templateIdx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.AddSegment(templateIdx);
Facade.SendNotification(SegmentAdded);
}
}

public void RemoveSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.RemoveSegment(idx);
Facade.SendNotification(SegmentRemoved);
}
}

public void UpSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.UpSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void DownSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.DownSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void TopSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.TopSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}

public void BottomSegment(int idx)
{
if (m_data != null)
{
Frame frame = (Frame)m_data;
frame.DownSegment(idx);
Facade.SendNotification(SegmentOrderChanged);
}
}
}

Frame:真正的帧数据

class Frame
{
public Frame()
{
segments = new ArrayList();
rects = new ArrayList();
Delay = 1;
}

public Segment AddSegment(int templateIdx)
{
Segment sec = new Segment(templateIdx);
segments.Add(sec);
return sec;
}

public Segment GetSegment(int index)
{
if (index >= 0 && index < segments.Count)
{
return (Segment)segments[index];
}
else
{
return null;
}
}

public void RemoveSegment(int segIndex)
{
segments.RemoveAt(segIndex);
}

public void UpSegment(int segIndex)
{
if (segIndex < segments.Count - 1)             {                 Object temp = segments[segIndex + 1];                 segments[segIndex + 1] = segments[segIndex];                 segments[segIndex] = temp;             }         }         public void DownSegment(int segIndex)         {             if (segIndex > 0)
{
Object temp = segments[segIndex - 1];
segments[segIndex - 1] = segments[segIndex];
segments[segIndex] = temp;
}
}

public void TopSegment(int segIndex)
{
if (segIndex < segments.Count - 1)             {                 Object temp = segments[segIndex];                 segments.RemoveAt(segIndex);                 segments.Add(temp);             }         }         public void BottomSegment(int segIndex)         {             if (segIndex > 0)
{
Object temp = segments[segIndex];
segments.RemoveAt(segIndex);
segments.Insert(0, temp);
}
}

public int SegmentCount
{
get
{
return segments.Count;
}
}

public int RectCount
{
get
{
return rects.Count;
}
}

public int Delay { set; get; }

private ArrayList segments;
private ArrayList rects;
}

用户通过拖拽大图的切片到编辑区的视图,FrameEditMediator 负责接受视图事件,并发出值为”AddSegment”的通知,通知触发AddSegmentCommand命令来执行业务逻辑,计算相应坐标之后,找到 FrameProxy执行对Frame的操作,FrameProxy验证了合法性之后,执行操作,发出帧更新通知,因为 FrameEditMediator对该通知感兴趣,它接受到之后,更新视图,一个完整的修改操作就完成了。

下面我们来讲一下pureMVC所带来的问题。首先,它的通知机制基于字符 串,字符串固然灵活,但对命名冲突之类的问题不好控制,这个可以通过严格的命名规则来解决;其次,通知不定向,会带来调试上的困难;再次,性能问题,由于 强制解耦,系统中的对象众多,对象往往通过字符串来索引,所以,在游戏设计这种以帧为逻辑执行单位的应用中,务必要注意性能问题。

不管仍然存在哪些问题,在项目的实际应用中,该框架仍然带来了意料之中的好处,使得应对需求变更及时,后期维护简单。当然,框架只是工具,心中有框架,手中无框架才是真正的高手。希望本文和pureMVC能够给各位同行的工作带来启发。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏