基于组件和消息的架构样式 C2






4.93/5 (13投票s)
C2 架构模式的描述及示例
引言
首先,这是我的第一篇文章,我感到很困惑。我写这篇文章是希望为那些希望拓宽架构风格视野的初学者提供一些思路。这种风格最初由加州大学欧文分校软件研究所的一个团队提出。他们也有一个基于 Java 的实现框架。我想把它提醒给老程序员,并指导初学者。我尝试用 C# 来实现它,它们并不完全相同,但似乎足以进行一些演示。
更多详情,请参阅
背景
C2 是一种异步、事件驱动的架构风格,它通过有限的可见性来促进重用、动态性和灵活性。组件和连接器有定义的顶部和底部,导致它们被排列成层。组件知道它们上面的元素,但不知道它们下面的元素。因此,它们可以发送请求,这些请求会沿着架构向上移动,并期望得到上方某些组件的满足。组件也可以发出通知,这些消息会沿着架构向下传递,而不期望是否会被处理。
如我所说,这种架构中的组件具有有限的可见性或“底层独立性”;组件以分层的方式组装,组件完全不知道“下方”存在的组件。这种独立性为促进跨架构组件的可互换性和重用提供了明显的可能性。
组件通过消息传递向“上方”的组件请求服务,并且不知道“下方”的组件(在这种架构中,消息向上传递,意味着接口层被认为是更靠近“底部”,而“应用程序”层被认为是更靠近顶部。请求流向上,消息或通知流向下)。C2 风格的特点是将组件与称为连接器的通信转发器关联起来。
优点
根据软件研究所的说法,这种架构风格的好处包括:
关注点分离,即将架构设计与其实现分离的概念,将软件组织和分解成更易于管理和理解的块;开放式架构,鼓励模块化策略,其中模块设计与实现这些设计的机制有清晰的分离。
可伸缩性,理解操作约束并支持多层次的组件接口粒度。
可扩展性,替换共享相同接口的组件,限制组件的相互依赖。
灵活性,在执行前或执行期间,通过添加新组件或重新配置现有组件来修改系统架构。
可靠性,利用经过精心设计、实现和验证的现有组件;成本降低,通过组件重用和架构指导来减少开发工作量。
可理解性,通过使用高级模型来提高复杂系统的可理解性。
C2 非常适合在分布式系统中使用。由于不假定共享内存或地址空间,因此可以在高度分布式、异构的环境中组合这些组件。组件通过连接器进行交互,组件本身不必位于同一台物理机器或网络上。服务可以通过复杂的中间件链和多个分布式服务器来使用,从而分布式处理。C2 风格通过关注连接器的结构化处理来实现层独立性,从而支持分布式、动态应用程序的开发。
Using the Code
C2 的 Java 类框架使用 Port 对象集合来促进 Component 和 Connector 实例之间的通信,另一种选择是使用基于事件的隐式调用模型。使用 C#,可以通过使用多播委托来维护架构事件模型的完整性。组件然后可以通过订阅事件来注册其方法。
我决定按照他们的方式实现,对我来说似乎更容易,也许以后我会改变它。
Signal
类是其他类型消息的基类。
每条消息都有名称、类型和参数。在 C2 中,类型是 Request (请求) 或 Notification (通知)。参数是命名的,可以是任何类型。
消息结构的基类是 Signal
类;
public class Signal
{
...
protected string NameOfMessage;
protected Hashtable @params;
protected string type;
//And methods of it is like that;
public virtual void AddParameter(string name, object value)
{
@params[name] = value;
}
public virtual object GetParameter(string name)
{
return @params[name];
}
public virtual void RemoveParameter(string name)
{
@params.Remove(name);
}
public virtual bool HasParameter(string name)
{
return @params.ContainsKey(name);
}
}
我们有两个从 Signal 类派生的消息类;
Notification
;沿着架构向下传递的消息
public sealed class Notification : Signal
{
public Notification()
: base(TypeNotification)
{
}
public Notification(string name)
: base(TypeNotification, name)
{
}
}
Request
;沿着架构向上传递的消息
public sealed class Request : Signal
{
public Request()
: base(TypeRequest)
{
}
public Request(string name)
: base(TypeRequest, name)
{
}
/// <summary>
/// Create a reply notification to this Request. The name of the notification
/// message is the same as this Request.
/// </summary>
public Notification Reply()
{
return Reply(MessageName());
}
/// <summary>
/// Create a reply notification to this Request with the given name. </summary>
public Notification Reply(string name)
{
var n = new Notification(name);
object token = GetParameter("REPLY_TOKEN");
if (null != token)
{
// if this request has a token, copy the token to its reply
n.AddParameter("REPLY_TOKEN", token);
}
return n;
}
}
其他类是 Brick
和 Bus
,我派生了一个名为 BrickGUI
的类来支持带有图形用户界面的应用程序。Brick
类是组件的基类型,Bus
类代表连接器。所有这些类都实现了 IBrick
接口。
public interface IBrick
{
void Send(Signal message);
void Handle(Signal message);
void AddTop(IBrick component);
void Weld(IBrick component);
}
Brick
类的实现
public abstract class Brick : IBrick
{
readonly List<IBrick> _bottomBusList;
readonly List<IBrick> _topBusList;
protected Brick()
{
_bottomBusList = new List<IBrick>();
_topBusList = new List<IBrick>();
}
protected Brick(string name)
: this()
{
BrickName = name;
}
public string BrickName { get; set; }
public void Weld(IBrick bus)
{
if (bus is Bus)
{
bus.AddTop(this);
_bottomBusList.Add(bus);
}
}
public void AddTop(IBrick bus)
{
if (bus is Bus)
{
_topBusList.Add(bus);
}
}
public void Send(Signal message)
{
new Thread((() =>
{
if (message is Request)
{
_topBusList.ForEach(p => p.Handle(message));
}
else if (message is Notification)
{
_bottomBusList.ForEach(p => p.Handle(message));
}
})).Start();
}
public void Handle(Signal message)
{
DoEvents(message);
Send(message);
}
public abstract void DoEvents(Signal message);
}
以及 Bus
类
public class Bus : IBrick
{
private readonly List<IBrick> _bottomLinks;
private readonly List<IBrick> _topLinks;
public Bus()
{
_bottomLinks = new List<IBrick>();
_topLinks = new List<IBrick>();
}
public Bus(string name) : this()
{
Name = name;
}
public string Name { get; set; }
public void Weld(IBrick component)
{
_bottomLinks.Add(component);
component.AddTop(this);
}
public void AddTop(IBrick component)
{
_topLinks.Add(component);
}
public void Send(Signal message)
{
new Thread((() =>
{
if (message is Request)
{
_topLinks.ForEach(p => p.Handle(message));
}
else if (message is Notification)
{
_bottomLinks.ForEach(p => p.Handle(message));
}
})).Start();
}
public void Handle(Signal message)
{
Send(message);
}
}
现在让我们看看它的实际运行情况。
简单的应用程序
这是 C2 架构的一个简单应用,其中有一个时钟、两个设备、一个监视器和两个总线。我们的目标是在时钟的帮助下同步两个设备,并监视 Device 2 的值。
让我们先从 Clock
开始
public class Clock : Brick
{
public Clock()
{
Timer tmr = new Timer {Interval = 1000};
tmr.Tick += tmr_Tick;
tmr.Start();
}
void tmr_Tick(object sender, EventArgs e)
{
Signal sig = new Notification("clock");
this.Send(sig);
}
protected Clock(string name)
: base(name)
{
}
public override void DoEvents(Signal message)
{
}
}
我们可以将 DoEvents
方法留空,因为 Clock
不期望任何消息。Clock
的构造函数中创建了一个简单的计时器,它每秒推送一次带有“clock
”标签的 Notification 消息。然后 Bus1
接收此消息并将其推送到 Device1
和 Device2
。让我们看看它们的实现。
public partial class Device1 : BrickForm
{
public Device1()
{
InitializeComponent();
}
public Device1(string name)
: base(name)
{
InitializeComponent();
Text = name;
}
public override void DoEvents(Signal message)
{
if (message is Notification && message.MessageName().Equals("clock"))
{
if (dateTimePicker1.InvokeRequired)
{
dateTimePicker1.BeginInvoke(new MethodInvoker(() =>
{
dateTimePicker1.Value = dateTimePicker1.Value.AddSeconds(1);
}));
}
else
{
dateTimePicker1.Value = dateTimePicker1.Value.AddSeconds(1);
}
}
}
}
Device1
简单地检查传入的信号,如果消息是时钟通知,则增加其值。
public partial class Device2 : BrickForm
{
public Device2()
{
InitializeComponent();
}
public Device2(string name)
: base(name)
{
InitializeComponent();
Text = name;
}
public override void DoEvents(Signal message)
{
if (message is Notification && message.MessageName().Equals("clock"))
{
if (progressBar1.InvokeRequired)
{
progressBar1.BeginInvoke(new MethodInvoker(() =>
{
progressBar1.Value++;
}));
}
else
{
progressBar1.Value++;
}
Signal n = new Notification("device2state");
n.AddParameter("percentage", progressBar1.Value);
Send(n);
}
}
}
Device2
通过 clock
信号增加其进度百分比,然后通过 Notification
消息推送此值。
Monitor
将捕获的值打印到屏幕上。
public partial class FormMonitor : BrickForm
{
public override void DoEvents(Signal message)
{
if (message is Notification &&
message.MessageName().Equals("device2state") &&
message.HasParameter("percentage"))
{
if (richTextBox1.InvokeRequired)
{
richTextBox1.BeginInvoke(new MethodInvoker(() =>
richTextBox1.AppendText("Device 2 state : "+
message.GetParameter("percentage").ToString() +"% \r\n")));
}
else
{
richTextBox1.AppendText(message.GetParameter("percentage").ToString());
}
}
}
}
最后一步是将它们绑定在一起。
private void CreateComponents()
{
Bus bus1 = new Bus("bus1");
Bus bus2 = new Bus("bus2");
var clock = new Clock();
BrickForm device1 = new Device1("device 1")
{
Location = new Point(200, 200)
};
BrickForm device2 = new Device2("device 2")
{
Location = new Point(300, 200)
};
BrickForm monitor = new FormMonitor("monitor")
{
Location = new Point(100, 200)
};
clock.Weld(bus1);
bus1.Weld(device1);
bus1.Weld(device2);
device2.Weld(bus2);
bus2.Weld(monitor);
monitor.Show();
device1.Show();
device2.Show();
}
结果将是这样的,组件知道它们上面的元素。关闭监视器不会影响设备或时钟的工作流程。
稍微复杂一些的示例
我制作了这个示例来可视化幕后发生的事情。
在此场景中,FormMain
类发送一个带有目标名称和 text 参数的 Request
消息(记住,底部知道顶部),消息被分发到尽可能远的地方。如果消息成功传递,目标会发送一个通知消息,主界面会打印传入的通知。
我只需要创建两个组件;一个 FormMain
类和一个 Banner
类。之后,我将创建 5 个 Banner
实例。
public partial class FormMain : BrickForm
{
...
public override void DoEvents(Signal message)
{
if (message.HasParameter("dest") && message.HasParameter("text"))
{
AddToLog(String.Format("{0} is set to \"{1}\"",
message.GetParameter("dest"), message.GetParameter("text")));
}
}
public void AddToLog(string text)
{
lock (richTextBox1)
{
richTextBox1.Clear();
richTextBox1.AppendText(text);
richTextBox1.AppendText(Environment.NewLine);
}
}
private void buttonSendMessage_Click(object sender, EventArgs e)
{
Application.OpenForms.OfType<IBrick>().OfType<Form>().ToList().ForEach
(p => p.BackColor = SystemColors.Control);
Signal sig = new Request(textBoxTarget.Text);
sig.AddParameter("text",textBoxMessage.Text);
this.Send(sig);
}
}
public partial class Banner : BrickForm
{
#region ctor
public Banner()
{
InitializeComponent();
}
public Banner(string name)
: base(name)
{
InitializeComponent();
Text = name;
}
public override void DoEvents(Signal message)
{
if (message is Request)
{
this.BackColor = Color.Yellow;
if (message.MessageName() == this.BrickName)
{
if (message.HasParameter("text"))
textBox1.Text = message.GetParameter("text").ToString();
this.BackColor = Color.Green;
Thread.Sleep(1000);
Signal n = (message as Request).Reply("main");
n.AddParameter("dest", this.BrickName);
n.AddParameter("text", message.GetParameter("text"));
this.Send(n);
}
else
{
Thread.Sleep(1000);
this.BackColor = SystemColors.Control;
}
}
else
{
this.BackColor = Color.Yellow;
Thread.Sleep(1000);
this.BackColor = SystemColors.Control;
}
}
}
我添加了一些 Thread.Sleep
和一些颜色来可视化通信过程中发生的事情。
最后一步,创建和焊接组件。
private void CreateForms()
{
BrickForm t1 = new Banner("t1")
{
Location = new Point(200, 200)
};
BrickForm t2 = new Banner("t2")
{
Location = new Point(400, 200)
};
BrickForm t3 = new Banner("t3")
{
Location = new Point(600, 150)
};
BrickForm t4 = new Banner("t4")
{
Location = new Point(600, 250)
};
BrickForm t5 = new Banner("t5")
{
Location = new Point(800, 200)
};
BrickForm main = new FormMain();
var bus1 = new Bus("Bus1");
var bus2 = new Bus("Bus2");
var bus3 = new Bus("Bus3");
var bus4 = new Bus("Bus4");
t5.Weld(bus4);
bus4.Weld(t3);
bus4.Weld(t4);
t3.Weld(bus3);
t4.Weld(bus3);
bus3.Weld(t2);
t2.Weld(bus2);
bus2.Weld(t1);
t1.Weld(bus1);
bus1.Weld(main);
main.Show();
t1.Show();
t2.Show();
t3.Show();
t4.Show();
t5.Show();
}
请求正在传播。
目标已找到。
关注点
您可以查阅这篇 文章 以获取更深入的思考。
结论
关注点分离是架构的一个重要优势。即将架构设计与其实现分离的概念,将软件组织和分解成更易于管理和理解的块。但请记住,每个组件都意味着程序额外的线程,并降低了系统的整体性能。C2 非常适合在分布式系统中使用,因为它不假定共享内存或地址空间,这使得在高度分布式、异构的环境中可以组合这些组件。
历史
- 2014.05.10 - 初始版本