名词解释:
组合:由几个部分或个体结合成整体或者组织成整体。
组合模式定义:将对象以树形结构组织起来,以达成“部分-整体”的层次结构,使得客户端对单个对象和组合对象的使用具有一致性。
组合模式的组成:
1):Component:它为组合中的对象声明接口,也可以为共有接口实现缺省行为。
2):角色Leaf:顾名思义,它是单一构件,不存在任何的子项.实现抽象构件角色声明的接口。
3):角色Composite:复杂对象,一般会包含多个树叶(Leaf)对象.实现抽象构件角色声明的接口;管理树叶(Leaf)对象。
组合模式一般有两实现方式:安全性与透明性模式
透明性:
在Component里面声明所有用来管理子类对象的方法。目的就是为了使客户看来Leaf和Composite没有区别。Leaf是不存在子类的,因此Component声明的一些方法对于Leaf来说是不适用的。这说是它的安全性问题。下面是类图:
安全性:
只在Composite里面声明所有的用来管理子类对象的方法。这样就避免了安全性问题,但是由于Leaf和Composite有不同的接口,所以又失去了透明性。下面是类图:
这两种实现方式各有所长,开发者可以根据自己的实际情况来取舍,
本人推荐透明性,因为它对客户端来说是最简单的,它的安全性问题完全可以用些空处理等方式来避免.
我在这里用一个增送礼物的需求来说明一下组合模式的应用.
说明:此例子不太符合实际,只是为了说明组合模式的应用生搬的一个例子。在实际中并不会出现这样的情况:
把第一名得到的五个奖品分五次发送。
需求:网站需要推行一个有奖活动,中奖的用户可以根据名次来得到相应的奖品.例如,奖项分为五个:
第五名:一个奖品.
第四名:两个奖品
第三名:三个奖品
第二名:四个奖品
第一名:五个奖品
程序要实现的就是根据名次来发送相应的礼品包,同时记录些相关信息,例如礼品包中的礼物信息以后中奖人信息等等.
一个奖品的发送是最简单的,因为信息单一,一个奖品一个用户,录入数据库就OK了,名次越前程序要记录的东西就越多,当在处理第一名时,要判断是单个奖品还是多个奖品(
因为单个奖品和礼品包在业务处理上会有不同).这样在客户端调用的时候往往会产生大量的代码.而且最可怕的是这种代码依赖于奖品发送的具体业务处理类。能不能让客户调用的时候不去费心的判断奖品的类型,而是直接调用一个统一的方法,例如sendGift(),这时就要用到组合模式了。
例子的类结构图
礼物与中奖人信息我用一个实体类来记录 giftAndUserInfo,它包含礼物名称及中奖人用户名.

Code
public class giftAndUserInfo

{

/**//// <summary>
/// 礼物名称
/// </summary>
public string giftName

{ get; set; }

/**//// <summary>
/// 中奖用户名称
/// </summary>
public string userName

{ get; set; }
}
抽象类sendGiftComponent,它定义了发送礼物的方法以及管理子类的方法Add,Romove.

Code
public abstract class sendGiftComponent

{
//礼物和用户实体对象
protected giftAndUserInfo giftAndUser;
public sendGiftComponent(giftAndUserInfo _giftAndUser)

{
this.giftAndUser = _giftAndUser;
}
public sendGiftComponent()

{

}

/**//// <summary>
/// 发送礼物
/// </summary>
/// <returns>是否成功</returns>
public abstract bool sendGift();

/**//// <summary>
/// 添加子构件
/// </summary>
/// <param name="_sendGiftComponent"></param>
public abstract void Add(sendGiftComponent _sendGiftComponent);

/**//// <summary>
/// 删除子构件
/// </summary>
/// <param name="_sendGiftComponent"></param>
public abstract void Remove(sendGiftComponent _sendGiftComponent);
}
发送单个礼物的Leaf,这个类实现单个礼物的发送

Code
public class Bll_sendGiftLeaf : sendGiftComponent

{
public Bll_sendGiftLeaf(giftAndUserInfo gift)
: base(gift)

{
}

/**//// <summary>
/// 将一个礼物发送给中奖用户
/// </summary>
/// <returns>发送礼物是否成功</returns>
public override bool sendGift()

{
Console.WriteLine(giftAndUser.giftName + "已经成功发送给了用户" + giftAndUser.userName);
return true ;
}

/**//// <summary>
/// 单个礼物的Add方法抛出异常即可
/// </summary>
/// <param name="_sendGiftComponent"></param>
public override void Add(sendGiftComponent _sendGiftComponent)

{
throw new NotImplementedException();
}

/**//// <summary>
/// 单个礼物的Remove方法抛出异常即可
/// </summary>
/// <param name="_sendGiftComponent"></param>
public override void Remove(sendGiftComponent _sendGiftComponent)

{
throw new NotImplementedException();
}

}
发送礼物包的Composite,它负责把多个礼物打包成一个整体,然后一个一个发送出去.

Code
public class Bll_sendGiftComposite : sendGiftComponent

{

/**//// <summary>
/// 礼物包的组合对象
/// </summary>
protected List<sendGiftComponent> _sendGiftComponent = new List<sendGiftComponent>();
//public Bll_sendGiftComposite(giftAndUserInfo gift)
// : base(gift)
//{
//}
public Bll_sendGiftComposite()

{

}

/**//// <summary>
/// 将一个礼物包(包含多个礼物)发送给中奖用户
/// </summary>
/// <returns>发送礼物是否成功</returns>
public override bool sendGift()

{
foreach (sendGiftComponent gift in _sendGiftComponent)

{
gift.sendGift();
}
return true ;
}

/**//// <summary>
/// 给实物包中增加礼物
/// </summary>
/// <param name="_sendGiftComponent"></param>
public override void Add(sendGiftComponent _sendGift)

{
_sendGiftComponent.Add(_sendGift);
}

/**//// <summary>
/// 给实物包中删除礼物
/// </summary>
/// <param name="_sendGiftComponent"></param>
public override void Remove(sendGiftComponent _sendGift)

{
_sendGiftComponent.Remove (_sendGift);
}
}
客户端调用代码:
本例只为说明问题,只是将要发送的礼品包里面的全部礼品名称以及中奖人信息输出.例子是调用的是发送一个第二名礼品包.

Code
static void Main(string[] args)

{
//礼物和用户信息实体

构造一个大礼包#region 构造一个大礼包
Bll_sendGiftComposite _Bll_sendGiftComposite = new Bll_sendGiftComposite();
//加入礼物一
giftAndUserInfo _giftAndUser1 = new giftAndUserInfo();
_giftAndUser1.giftName = "一张机票";
_giftAndUser1.userName = "张三";
Bll_sendGiftLeaf _Bll_sendGiftLeaf_1 = new Bll_sendGiftLeaf(_giftAndUser1 );
_Bll_sendGiftComposite.Add(_Bll_sendGiftLeaf_1);

//加入礼物二
giftAndUserInfo _giftAndUser2 = new giftAndUserInfo();
_giftAndUser2.giftName = "500RMB";
_giftAndUser2.userName = "张三";
Bll_sendGiftLeaf _Bll_sendGiftLeaf_2 = new Bll_sendGiftLeaf(_giftAndUser2);
_Bll_sendGiftComposite.Add(_Bll_sendGiftLeaf_2);
//加入礼物三
giftAndUserInfo _giftAndUser3 = new giftAndUserInfo();
_giftAndUser3.giftName = "一个MP3";
_giftAndUser3.userName = "张三";
Bll_sendGiftLeaf _Bll_sendGiftLeaf_3 = new Bll_sendGiftLeaf(_giftAndUser3);
_Bll_sendGiftComposite.Add(_Bll_sendGiftLeaf_3);
//加入礼物四
giftAndUserInfo _giftAndUser4 = new giftAndUserInfo();
_giftAndUser4.giftName = "一辆自行车";
_giftAndUser4.userName = "张三";
Bll_sendGiftLeaf _Bll_sendGiftLeaf_4 = new Bll_sendGiftLeaf(_giftAndUser4);
_Bll_sendGiftComposite.Add(_Bll_sendGiftLeaf_4);
#endregion
//发送大礼包
sendGiftComponent _send = _Bll_sendGiftComposite;
_send.sendGift();
Console.WriteLine("方法调用结束");
Console.ReadKey();

}
输出结果:

优点:
1:以上的代码中,客户端不关心要处理的是单个礼物还是礼品包,统一对待,使客户端调用简单化.
2:如果礼品包中要增加或者是删除一个礼物,都无需更改代码结构,这符合"开闭原则".
总结:
无论模式如何强大,用与不用取决于开发人员,如果你有足够的理由不采用那么你可以不屑此模式。
注:
本文引用:
http://blog.csdn.net/ai92/archive/2005/02/23/298336.aspx
http://terrylee.cnblogs.com/archive/2006/03/11/347919.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)