封装的不同层次
在面向对象的设计中,封装是为了达到以下两个目的:
1. 保护类的属性不受到非法的操作;
2. 不同实现的透明化。
在以下的几个例子中,我们会看到封装的具体应用:
class Account
{
private double balance; //账户余额
public void deposit(double amount)
{
Balance += amount;
}
public void withdraw(double amount)
{
if (amount < balance)
{
balance -= amount;
}
}
};
![]()
在上面的例子中,如果把balance声明为公有的属性,则使用这个类的程序员完全可能因为使用上的错误,造成程序运行的错误(比如不经过余额的检查,直接用balance -= amount的作法)。
在下面的例子中,属性age的封装,是为了实现年龄增长对于使用者的透明化:
class Person
{
private int age;
public void addDays(int numberOfDays)
{
//增加天数,
boolean nextYear = checkIfNextYear(); //检查增加后是否为下一年
if (nextYear)
{
age++; //不希望手工增长年龄,需要在程序中达到目的。
}
}
}
![]()
在Java中,还有第三种情况会要求封装:Java中的赋值操作只有对于32位及以下长度的整型变量是单元操作,而对于浮点数以及长整数,这个操作不是单元操作,也就是说,在多线程的环境中,有可能引起错误。在这种情况下,可以通过封装,把赋值操作封装在一个同步的方法中,以防止程序的使用者因为没有注意到多线程问题而出错。
这些,仅仅是在类层次的封装。在更高的层次,同样存在着封装的问题,在这个层次的封装,我们称之为组件。
下面我们来看一个没有组件的界面,使用语言为C#:

图1:实例界面
Windows 窗体设计器生成的代码#region Windows 窗体设计器生成的代码
/**//// <summary>
/// 设计器支持所需的方法 - 不要使用代码编辑器修改
/// 此方法的内容。
/// </summary>
private void InitializeComponent()
...{
this.mainMenu = new System.Windows.Forms.MainMenu();
this.fileMenu = new System.Windows.Forms.MenuItem();
this.newItem = new System.Windows.Forms.MenuItem();
this.openItem = new System.Windows.Forms.MenuItem();
this.aboutMenu = new System.Windows.Forms.MenuItem();
this.aboutItem = new System.Windows.Forms.MenuItem();
this.viewToolBar = new System.Windows.Forms.ToolBar();
this.newButton = new System.Windows.Forms.ToolBarButton();
this.openButton = new System.Windows.Forms.ToolBarButton();
this.aboutToolBar = new System.Windows.Forms.ToolBar();
this.aboutButton = new System.Windows.Forms.ToolBarButton();
this.SuspendLayout();
//
// mainMenu
//
this.mainMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] ...{
this.fileMenu,
this.aboutMenu});
//
// fileMenu
//
this.fileMenu.Index = 0;
this.fileMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] ...{
this.newItem,
this.openItem});
this.fileMenu.Text = "File";
//
// newItem
//
this.newItem.Index = 0;
this.newItem.Text = "New";
//
// openItem
//
this.openItem.Index = 1;
this.openItem.Text = "Open";
//
// aboutMenu
//
this.aboutMenu.Index = 1;
this.aboutMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] ...{
this.aboutItem});
this.aboutMenu.Text = "About";
//
// aboutItem
//
this.aboutItem.Index = 0;
this.aboutItem.Text = "About...";
//
// viewToolBar
//
this.viewToolBar.Buttons.AddRange(new System.Windows.Forms.ToolBarButton[] ...{
this.newButton,
this.openButton});
this.viewToolBar.DropDownArrows = true;
this.viewToolBar.Location = new System.Drawing.Point(0, 0);
this.viewToolBar.Name = "viewToolBar";
this.viewToolBar.ShowToolTips = true;
this.viewToolBar.Size = new System.Drawing.Size(376, 41);
this.viewToolBar.TabIndex = 0;
this.viewToolBar.ButtonClick += new System.Windows.Forms.ToolBarButtonClickEventHandler(this.viewToolBar_ButtonClick);
//
// newButton
//
this.newButton.Text = "New";
//
// openButton
//
this.openButton.Text = "Open";
//
// aboutToolBar
//
this.aboutToolBar.Buttons.AddRange(new System.Windows.Forms.ToolBarButton[] ...{
this.aboutButton});
this.aboutToolBar.DropDownArrows = true;
this.aboutToolBar.Location = new System.Drawing.Point(0, 41);
this.aboutToolBar.Name = "aboutToolBar";
this.aboutToolBar.ShowToolTips = true;
this.aboutToolBar.Size = new System.Drawing.Size(376, 41);
this.aboutToolBar.TabIndex = 1;
this.aboutToolBar.ButtonClick += new System.Windows.Forms.ToolBarButtonClickEventHandler(this.aboutToolBar_ButtonClick);
//
// aboutButton
//
this.aboutButton.Text = "About";
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);
this.ClientSize = new System.Drawing.Size(376, 266);
this.Controls.Add(this.aboutToolBar);
this.Controls.Add(this.viewToolBar);
this.Menu = this.mainMenu;
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
this.ResumeLayout(false);
}
#endregion
![]()
这样的结果是:界面代码很多,各种消息处理机制混杂在界面代码中,原本的界面代码目标不清晰。
现在如果采用封装的方式,加上Mediator的设计模式重新加以设计,如下所示:

图2:Mediator设计模式
对于每一个Plugin来说,都是一个系统的组件,或者说是插件。这个插件负责自己的一套UI,以及对这些UI的消息处理。同时,完成一部分软件的功能。下面是这个Plugin的代码:
/// <summary>
/// Plugin 的摘要说明。
/// </summary>
public abstract class Plugin
{
protected Mediator mediator;
![]()
// 对UI主窗口提供自己的UI界面,使得主窗口可以自由添加
public abstract MenuItem GetMenu();
public abstract ToolBar GetToolBar();
![]()
/// <summary>
/// 给Mediator来对IPlugin通知事件的接口
/// </summary>
/// <param name="evt"></param>
public abstract void NotifyEvent(ControllerEvent evt);
![]()
public void SetMediator(Mediator mediator)
{
this.mediator = mediator;
Setup();
}
![]()
protected abstract void Setup();
}
![]()
但是这样做带来一个问题:如果每一个插件都只对自己负责的话,在UI处理的时候,很多情况下会引起交叉的反应,比如点击一个按钮的时候,需要更新另外一个树形控制的状态。对于这种情况应该如何处理呢?通常的做法,是在每一个UI组件的处理函数中加入对于其他UI组件状态的更新。这样带来的直接副作用,就是所有的UI组件相互之间都是关联的,形成一个网状结构,一旦界面的需求有变化,则整个的维护更新工作将极其繁杂。
如何解决这样的问题?Mediator的作用,就是为了简化这种网状结构,为UI界面的互动提供另外一个功能相同,而结构简单的架构方式。Mediator的结构如下:
图3:Mediator结构图
一个比较常见的用法的结构会是这样的:
图4:常见用法结构图
Mediator所带来的后果是:
1. 减少继承的层级。现在只需要变更Mediator,而不需要变更Colleague。
2. 降低了Colleague之间的耦合,现在Colleague之间不需要知道对方的状态,只是需要通知Mediator自己的状态发生了变化,而Mediator则负责通知其他的Colleague这个新发生的变化。
3. 简化程序的结构,一个网状结构简化为一个星形的结构,这样的结构更容易维护、更新,也更容易为人所理解。
4. 将Colleague之间的交互行为通过Mediator进行了封装和抽象。通过一个独立的Mediator抽象,进一步明晰了Colleague之间的交互过程。
5. 集中了中央控制。Mediator的内部结构会比任何一个Colleague的内部结构都负责,从而使得Mediator的维护工作加剧。
而我们在实际的设计中,采取Mediator和Observer模式的混合,通过Observer的模式来降低Mediator的复杂度,同时获得Mediator模式带来的好处。
在主窗口启动之时,所有的插件都必须向中介注册,同时主窗口获得插件的UI界面,并显示:
当UI插件与用户交互,需要生成事件通知其他UI本身的状态变化或要求其他UI插件协作的时候,系统之间的交互是这样的:

插件通过Mediator的接口函数通知事件的发生(而不是传统的中介的做法:直接调用中介提供的状态更新接口),中介在遍历所有已注册的插件之后,找出所有对该消息感兴趣的插件,并逐一通知所有的这些插件。对于该消息的响应,则完全被封装在该插件内部,而中介不需要再为界面的更新费心。
于是,通过封装,我们把复杂的界面开发以及相互牵扯的消息相应转化成为相对独立的UI模块。而每一个模块的开发基本独立于主程序的开发。通过Java的jar文件或者C#的dll文件封装,我们甚至可以把这些UI模块直接重复使用,从而降低软件开发的风险和成本。