装饰器模式提供了一种在无需创建新的派生类的情况下修改个体对象行为的方式。
假设我们需要给工具栏中的按钮添加一个特殊的边框,如果我们创建一个新的派生出来的按钮类,那么就意味着这一新类的所有按钮都会有相同的特殊的边框,这可能并不是我们最初想要的结果。替代的做法是,我们可以创建一个装饰器类来修饰按钮,然后从这一主修饰器类中派生出任意特定的装饰器,每个装饰器完成某种特定类型的装饰。这是另一种对象包含比对象继承更受青睐的情况,装饰器是一个图像对象,但是包含了其所装饰的对象,他可以拦截一些图形方法的调用,执行一些附加的计算,然后再把调用传递给其所装饰的底层对象。
C#的空间中没有类似的按钮行为,不过我们可以通过装饰一个面板,并使用面板来包含按钮的方式来获得这样的行为。
设计模式一书中建议装饰器应该派生于某个一般性的可视化空间类,然后实际按钮的每个消息都由装饰器类来转发。装饰器这样的类应该作为抽象类,并且应该从该抽象类中派生出所有的做实际工作的装饰器。
这样我们这个装饰器的抽象就可以定义为:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace CoolDec
{
/// <summary>
/// Summary description for Decorator.
/// </summary>
public interface Decorator
{
void mouseMove(object sender, MouseEventArgs e);
void mouseEnter(object sender, EventArgs e);
void mouseLeave(object sender, EventArgs e);
void paint(object sender, PaintEventArgs e);
}
}
要实现的对按钮的装饰效果如图:当鼠标放上按钮以后显示一个特殊的边框
具体的装饰器类的定义:
using System;
using System.Windows.Forms;
using System.Drawing ;
namespace CoolDec
{
/// <summary>
/// Summary description for CoolDecorator.
/// </summary>
public class CoolDecorator :Panel, Decorator
{
protected Control contl;
protected Pen bPen, wPen, gPen;
private bool mouse_over;
protected float x1, y1, x2, y2;
//----------------------------------
public CoolDecorator(Control c) {
contl = c; //copy in control
//add button to controls contained in panel
this.Controls.AddRange(new Control[] {contl});
locate(c.Location );
this.Name = "deco"+contl.Name ;
this.Size = contl.Size;
x1 = c.Location.X - 1;
y1 = c.Location.Y - 1;
x2 = c.Size.Width;
y2 = c.Size.Height;
//create the overwrite pens
gPen = new Pen(c.BackColor, 2); //gray pen overwrites borders
bPen = new Pen(Color.Black , 1);
wPen = new Pen(Color.White, 1);
//mouse over, enter handler
EventHandler evh = new EventHandler(mouseEnter);
c.MouseHover += evh;
c.MouseEnter+= evh;
//mouse move handler
c.MouseMove += new MouseEventHandler(mouseMove);
c.MouseLeave += new EventHandler(mouseLeave);
//paint handler catches button's paint
c.Paint += new PaintEventHandler( paint);
}
//------------------------------
public void locate(Point p) {
//moves panel to button position
this.Location = p;
contl.Location =new Point(0,0);
x1 = p.X ;
y1 = p.Y ;
}
//------------------------------
public virtual void locate(Control c) {
this.Location = c.Location;
c.Location =new Point (0,0);
x1 = c.Location.X - 1;
y1 = c.Location.Y - 1;
x2 = c.Size.Width;
y2 = c.Size.Height;
}
//------------------------------
public void mouseMove(object sender, MouseEventArgs e){
mouse_over = true;
}
//------------------------------
public void mouseEnter(object sender, EventArgs e){
mouse_over = true;
this.Refresh ();
}
//------------------------------
public void mouseLeave(object sender, EventArgs e){
mouse_over = false;
this.Refresh ();
}
//------------------------------
public virtual void paint(object sender, PaintEventArgs e){
//draw over button to change its outline
Graphics g = e.Graphics;
const int d = 1;
//draw over everything in gray first
g.DrawRectangle(gPen, 0, 0, x2 - 1, y2 - 1);
//draw black and white boundaries
//if the mouse is over
if( mouse_over) {
g.DrawLine(bPen, 0, 0, x2 - d, 0);
g.DrawLine(bPen, 0, 0, 0, y2 - 1);
g.DrawLine(wPen, 0, y2 - d, x2 - d, y2 - d);
g.DrawLine(wPen, x2 - d, 0, x2 - d, y2 - d);
}
}
}
}
这里添加了一些鼠标事件和重绘事件。将事件处理系统与方法联系起来。我们捕捉的时间是发生在被包含的按钮上的事件,而不是发生在按钮周边的面板上的事件。因此我们往其之上添加处理程序的空间是按钮本身。
我们把按钮添加到新面板的Controls数组中,接着把面板添加到窗口的controls数组中:
//------------------------------
private void init() {
//create a cool decorator
cdec = new CoolDecorator (btButtonA);
cqdec= new CoolDecorator(btButtonB);
//add outside decorator to the layout
//and remove the button from the layout
this.Controls.AddRange(new System.Windows.Forms.Control[] {cdec, cqdec});
this.Controls.Remove (btButtonA);
this.Controls.Remove (btButtonB);
//this.Controls.AddRange(new System.Windows.Forms.Control[] {cdec});
}
多个装饰器的应用。如果我们需要另外一种装饰,比如在按钮上画一条红色斜线,我们仅需要把CoolDecorator包含在另一个产生更多的装饰效果的装饰器面板内部。唯一真正需要改动的是,我们不但需要被包含到另一个面板中的那个面板的实例,而且需要中心的那个被装饰的对象的实例,因为我们必须把我们的绘制例程添加到这一中心对象的绘图方法上。
我们需要为我们这个装饰器创建一个构造函数,该装饰器把其围起来的面板和按钮都当成空间对待。
效果图如图:
using System;
using System.Windows.Forms;
using System.Drawing ;
namespace SlashDecorator
{
/// <summary>
/// Summary description for CoolDecorator.
/// </summary>
public class CoolDecorator :Panel, Decorator
{
protected Control contl;
protected Pen bPen, wPen, gPen;
private bool mouse_over;
protected float x1, y1, x2, y2;
//----------------------------------
public CoolDecorator(Control c, Control baseC) {
//the first control is the one layed out
//the base control is the one whose paint method we extend
//this allows for nesting of decorators
contl = c;
this.Controls.AddRange(new Control[] {contl});
c.Location =new Point(0,0);
this.Name = "deco"+contl.Name ;
this.Size = contl.Size;
x1 = c.Location.X - 1;
y1 = c.Location.Y - 1;
x2 = c.Size.Width;
y2 = c.Size.Height;
//create the overwrite pens
gPen = new Pen(c.BackColor, 2);
bPen = new Pen(Color.Black , 1);
wPen = new Pen(Color.White, 1);
//mouse over, enter handler
EventHandler evh = new EventHandler(mouseEnter);
c.MouseHover += evh;
c.MouseEnter+= evh;
c.MouseHover += evh;
//mouse move handler
c.MouseMove += new MouseEventHandler(mouseMove);
c.MouseLeave += new EventHandler(mouseLeave);
//paint handler catches button's paint
baseC.Paint += new PaintEventHandler( paint);
}
public void locate(Point p) {
this.Location = p;
contl.Location =new Point(0,0);
x1 = p.X ;
y1 = p.Y ;
}
public virtual void locate(Control c) {
this.Location = c.Location;
c.Location =new Point (0,0);
x1 = c.Location.X - 1;
y1 = c.Location.Y - 1;
x2 = c.Size.Width;
y2 = c.Size.Height;
}
public void mouseMove(object sender, MouseEventArgs e){
mouse_over = true;
}
public void mouseEnter(object sender, EventArgs e){
mouse_over = true;
this.Refresh ();
}
public void mouseLeave(object sender, EventArgs e){
mouse_over = false;
this.Refresh ();
}
public virtual void paint(object sender, PaintEventArgs e){
//draw over button to change its outline
Graphics g = e.Graphics;
const int d = 1;
//draw over everything in gray first
g.DrawRectangle(gPen, 0, 0, x2 - 1, y2 - 1);
//draw black and white boundaries
//if the mouse is over
if( mouse_over) {
g.DrawLine(bPen, 0, 0, x2 - d, 0);
g.DrawLine(bPen, 0, 0, 0, y2 - 1);
g.DrawLine(wPen, 0, y2 - d, x2 - d, y2 - d);
g.DrawLine(wPen, x2 - d, 0, x2 - d, y2 - d);
}
}
}
}
using System;
using System.Windows.Forms;
using System.Drawing ;
namespace SlashDecorator
{
/// <summary>
/// Summary description for SlashDeco.
/// </summary>
public class SlashDeco:CoolDecorator {
private Pen rPen;
//----------------
public SlashDeco(Control c, Control bc):base(c, bc) {
rPen = new Pen(Color.Red , 2);
}
//----------------
public override void paint(object sender, PaintEventArgs e){
Graphics g = e.Graphics ;
x1=0; y1=0;
x2=this.Size.Width ;
y2=this.Size.Height ;
g.DrawLine (rPen, x1, y1, x2, y2);
}
}
}
这样类的结构关系图如图所示:
当然,装饰器并不局限于增强可视类对象,也可以以类似的方式来添加或者修改任何对象的方法。非可视对象更易于修改,因为要拦截并作转发的方法可能会少一些。无论合适只要是把一个类的实例放入到另一个类中,并且让外部的类在该类智商进行操作的情况,基本上都算是装饰内部的类。
适配器也似乎实在“装饰”一个现有的类,不过他们的功能是把一个或者多个类的接口改造成更合适用于某个特定程序的接口;装饰器往类的某些特定实例而不是全部实例中添加方法;也可以认定由单一对象组成的组合本质上就是一个装饰器,不过要说明的是,其意图是不一样的。
装饰器模式提供了一种比使用继承更加灵活的给类添加职责的方式,因为该模式只是给那些指定的类实例添加这些职责,该模式还允许无需在继承层次结构智商在创建子类的情况下自定义类。
装饰器模式的两个不足:
1. 装饰器和其所封装的组件并不相同,因此对对象类型的检查会失败。
2. 装饰器可能会导致系统有许多较小的对象,这些对象对于代码的维护带来很大的不便。