C#设计模式之10——装饰器模式

本文介绍了C#中的装饰器模式,通过实例展示了如何使用装饰器模式为按钮添加特殊边框,强调了装饰器模式相比继承的灵活性,以及其在不改变对象类型的情况下动态增加对象功能的能力。同时,文章提到了装饰器模式的不足,如对象类型检查失败和可能导致大量小对象的产生,对代码维护带来的挑战。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

装饰器模式提供了一种在无需创建新的派生类的情况下修改个体对象行为的方式。

假设我们需要给工具栏中的按钮添加一个特殊的边框,如果我们创建一个新的派生出来的按钮类,那么就意味着这一新类的所有按钮都会有相同的特殊的边框,这可能并不是我们最初想要的结果。替代的做法是,我们可以创建一个装饰器类来修饰按钮,然后从这一主修饰器类中派生出任意特定的装饰器,每个装饰器完成某种特定类型的装饰。这是另一种对象包含比对象继承更受青睐的情况,装饰器是一个图像对象,但是包含了其所装饰的对象,他可以拦截一些图形方法的调用,执行一些附加的计算,然后再把调用传递给其所装饰的底层对象。

 

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. 装饰器可能会导致系统有许多较小的对象,这些对象对于代码的维护带来很大的不便。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值