您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
设计模式(五)
 
   次浏览      
2014-3-5
 
编辑推荐:
本文主要介绍设计模式中的命令模式,希望对您有所帮助。
本文来源于网络,由火龙果软件Linda编辑,推荐。

设计模式之命令模式(十三)

一、引出模式

在前面的模式中,我们已经组装好了一台电脑,现在要做的是开机。是的,开机!对用户们来说开机只不过是按个电源按钮,跟喝水一样简单!但对于我们搞技术的,就不一样了,可这其中又发生了什么不为人知的事呢?自己百度去!

我们先简单将以下流程,不做深入讲解。

首先加载电源,然后设备自检,接下来装在操作系统,最后电脑就启动了。可是谁来完成这些过程?如何完成的呢?

总不能让用户做这些吧,其实真正完成这些功能的是主板。那客户和主板又是怎么联系的呢?现实中,使用连接线将按钮连接到主板上,这样当用户按下按钮时,就相当与发命令给主板,让主板去完成后续工作。

想想,在这里有没有什么问题?

我们把这种情形放到软件开发中看看。客户端只是想要发出命令,不关心命令的执行者是谁,也不关心执行者是怎么完成的,有时同一个请求可能需要执行不同的操作,那怎么办?

二、认识模式

1.模式定义

将请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求进行排队或记录请求日志,以及支持可撤销的操作。

2.解决思路

我们来试着用命令模式解决上述开机的过程。

当客户按下按钮时,按钮本身并不知道如何处理,于是我们通过连接线,将按钮和主板连接起来,让主板去完成真正启动机器的功能。

在这里,我们通过引入按钮和连接线,来让发出命令的客户和命令的真正实现者——主板完全解耦,客户操作的始终是按钮,按钮后面的事客户就不管了,因为客户只知道只要按下按钮就能开机了,中间做了什么客户是不关心的,客户只求结果,不问过程。

在命令模式中,会定义一个命令的接口,用来约束所有的命令对象,然后提供具体的命令实现,每个命令实现对象是对客户端某个请求的封装,对应于机箱上的按钮,一个机箱可以有很多按钮,也就相当于有很多个具体的命令实现对象。

在命令模式中,命令对象是不知道如何处理命令的,他会转调命令接受者对象来真正执行命令。就像刚才例子中,按钮是不知道如何处理的,按钮是吧这个请求转发给主板,主板来执行,这个主板就相当于命令模式汇总的接受者。

在命令模式中,命令对象和接收者的关系并不是与生俱来的,需要有一个装配者对两者进行关联,命令模式中的Client对象可以实现这样的功能,在电脑中,有了按钮,有了主板,那还需要有根连接线将按钮和主板连接起来才行,这根连接线就充当着Client对象的角色。

命令模式中,还会提供一个Invoker对象来持有命令对象。比如,机箱上会有多个按钮,这个机箱就相当于Invoker对象,这样我们客户就可以通过Invoker也就是机箱来按下按钮来执行相应的命令。

3.模式结构原型

Command:定义命令的接口,声明执行的方法。

ConreteCommand:命令接口实现对象,是“虚”的实现;通常会持有接受者,并调用接受者的功能来完成命令要执行的操作。

Receiver:接受者,真正执行命令的对象。任何类都能成为接受者,只要它能够实现命令要求实现的相应的功能。

Invoker:要求命令对象执行请求,通常会持有命苦对象,可以持有很多命令对象。这个是客户端真正触发命令并要求执行相应操作的定法,也就是说这才是使用命令对象的入口。

Client:创建具体的命令对象,并且设置命令对象的接受者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接受者,可以将它称为装配者,真正使用命令的客户端是从Invoker来触发执行的。

4.模式原型示例代码

class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.Run();
Console.ReadKey();
}
}

/// <summary>
/// 示意,负责创建命令对象,并设定它的接受者
/// </summary>
public class Client
{
public void Run()
{
Receiver receiver = new Receiver();
//创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
//创建Invoker,把命令对象设置进去
Invoker invoker = new Invoker(command);
invoker.RunCommand();
}
}

/// <summary>
/// 命令接口,声明执行的操作
/// </summary>
public interface Command
{
/// <summary>
/// 执行命令对应的操作
/// </summary>
void Execute();
}

/// <summary>
/// 具体的命令实现对象
/// </summary>
public class ConcreteCommand : Command
{
/// <summary>
/// 持有相应的接受者对象
/// </summary>
private Receiver receiver = null;

/// <summary>
/// 示意,命令对象可以有自己的状态
/// </summary>
private string state = null;

/// <summary>
/// 构造方法,传入相应的接受者对象
/// </summary>
/// <param name="receiver">相应的接受者对象</param>
public ConcreteCommand(Receiver receiver)
{
this.receiver = receiver;
}

public void Execute()
{
//通常会转调接受者对象的相应方法,让接受者来真正执行功能
receiver.Action();
}
}

/// <summary>
/// 接收者对象
/// </summary>
public class Receiver
{
/// <summary>
/// 示意方法,真正执行命令相应的操作
/// </summary>
public void Action()
{
Console.WriteLine("命令执行了");
}
}

/// <summary>
/// 调用者(机箱)
/// </summary>
public class Invoker
{
/// <summary>
/// 持有命令对象
/// </summary>
private Command command;

/// <summary>
/// 设置调用者持有的命令对象
/// </summary>
/// <param name="command">命令对象</param>
public Invoker(Command command)
{
this.command = command;
}

/// <summary>
/// 示意方法,要求命令执行请求
/// </summary>
public void RunCommand()
{
command.Execute();
}

5.电脑开机示例示例代码

class Program
{
static void Main(string[] args)
{
//把命令和真正的实现组合起来,相当于在组装机器,
MainBoardApi mainBoardApi=new GigaMainBoard();

//把机箱上按钮的连接线插接到主板上。
Command command=new OpenCommand(mainBoardApi);

//真正的客户端测试

//为机箱上的按钮设置对应的命令,让按钮知道该干什么
Box box=new Box();
box.SetOpenCommand(command);

//然后模拟按下机箱上的按钮
box.OpenButtonPressed();

Console.Read();
}
}

/// <summary>
/// 命令接口,声明执行的操作
/// </summary>
public interface Command
{
/// <summary>
/// 执行命令对应的操作
/// </summary>
void Execute();
}

/// <summary>
/// 持有开机命令的真正实现,通过调用接收者的方法来实现命令
/// </summary>
public class OpenCommand : Command
{
/// <summary>
/// 持有真正实现命令的接收者——主板对象
/// </summary>
private MainBoardApi mainBoard = null;

/// <summary>
/// 构造方法,传入主板对象
/// </summary>
/// <param name="mainBoard">主板对象</param>
public OpenCommand(MainBoardApi mainBoard)
{
this.mainBoard = mainBoard;
}

public void Execute()
{
//对于命令对象,根本不知道如何开机,会转调主板对象
//让主板去完成开机的功能
this.mainBoard.Open();
}
}

/// <summary>
/// 主板的接口
/// </summary>
public interface MainBoardApi
{

/// <summary>
/// 主板具有能开机的功能
/// </summary>
void Open();
}

/// <summary>
/// 技嘉主板类,开机命令的真正实现者,在Command模式中充当Receiver
/// </summary>
public class GigaMainBoard : MainBoardApi
{

/// <summary>
/// 真正的开机命令的实现
/// </summary>
public void Open()
{
Console.WriteLine("技嘉主板现在正在开机,请等候");
Console.WriteLine("接通电源......");
Console.WriteLine("设备检查......");
Console.WriteLine("装载系统......");
Console.WriteLine("机器正常运转起来......");
Console.WriteLine("机器已经正常打开,请等候");
}
}

/// <summary>
/// 微星主板类,开机命令的真正实现者,在Command模式中充当Receiver
/// </summary>
public class MsiMainBoard : MainBoardApi
{
/// <summary>
/// 真正的开机命令的实现
/// </summary>
public void Open()
{
Console.WriteLine("微星主板现在正在开机,请等候");
Console.WriteLine("接通电源......");
Console.WriteLine("设备检查......");
Console.WriteLine("装载系统......");
Console.WriteLine("机器正常运转起来......");
Console.WriteLine("机器已经正常打开,请等候");
}
}


/// <summary>
/// 机箱对象,本身有按钮,持有按钮对应的命令对象
/// </summary>
public class Box
{
/// <summary>
/// 开机命令对象
/// </summary>
private Command openCommand;

/// <summary>
/// 设置开机命令对象
/// </summary>
/// <param name="command">开机命令对象</param>
public void SetOpenCommand(Command command)
{
this.openCommand = command;
}

/// <summary>
/// 提供给客户使用,接受并相应用户请求,相当于按钮被按下触发的方法
/// </summary>
public void OpenButtonPressed()
{
//按下按钮,执行命令
openCommand.Execute();
}
}

三、理解模式

1.命令模式的关键

命令模式的关键之处就是把请求封装称为对象,也就是命令对象,并定义统一的执行操作的接口,这个命令对象被存储、转发、记录、处理、撤销等,整个命令模式都是围绕这个对象进行的。

2.命令模式的组装和调用

命令模式中经常会有一个命令的装配者,用它来维护命令的“虚”实现和真实实现之间的关系。如果是超级智能的命令,也就是说命令对自己完全实现好了,不需要接受者,那就是命令模式的退化,不需要接受者,自然也不需要装配者。

实际开发中,Client和Invoker是可以融合在一起的,有客户在使用命令模式的时候,先进行命令对象和接受者的组装,组装完成后,就可以调用命令执行请求。

3.命令的接受者

接受者是可以是任意的类,只要这个对象知道如何真正的执行命令,执行时是从Command的实现类里面转调过来的。

一个接受者对象可以处理多个命令对象,接受者和命令之间没有约定的对象关系。

4.智能命令

在标准的命令模式中,命令的实现类是没有真正实现命令要求的功能的,真正执行命令的是接受者。

如果命令的实现对象比较智能,自己就能实现命令要求的功能,就不需要调用接受者,这种情况称为智能命令。

5.发起请求的对象和真正处理的对象是解耦的

请求有谁来处理?如何处理?发起请求的对象是不知道的,也就是发情请求的对象和真正实现的对象是解耦的。

6.参数化配置

所谓的命令模式的参数化配置,指的是:可以用不同的命令对象,去参数化配置客户端的请求。

如前面的表述,按下按钮你是不知道是开机,关机还是重启的,那就要看参数化配置是哪一个具体的按钮对象。

示例代码:

class Program
{
static void Main(string[] args)
{
//把命令和真正的实现组合起来,相当于在组装机器,
MainBoardApi mainBoardApi = new GigaMainBoard();

//创建开机命令
Command command = new OpenCommand(mainBoardApi);

//创建重启机器的命令
ResetCommand resetCommand = new ResetCommand(mainBoardApi);

//真正的客户端测试

//为机箱上的按钮设置对应的命令,让按钮知道该干什么
Box box = new Box();

//先正确配置,就是开机按钮对开机命令,重启按钮对重启命令
box.SetOpenCommand(command);
box.SetResetCommand(resetCommand);

//然后模拟按下机箱上的按钮
Console.WriteLine("正确配置下------------------------->");
Console.WriteLine(">>>按下开机按钮:>>>");

box.OpenButtonPressed();
Console.WriteLine(">>>按下重启按钮:>>>");
box.ResetButtonPressed();
Console.Read();
}
}

/// <summary>
/// 命令接口,声明执行的操作
/// </summary>
public interface Command
{
/// <summary>
/// 执行命令对应的操作
/// </summary>
void Execute();
}

/// <summary>
/// 持有开机命令的真正实现,通过调用接收者的方法来实现命令
/// </summary>
public class OpenCommand : Command
{
/// <summary>
/// 持有真正实现命令的接收者——主板对象
/// </summary>
private MainBoardApi mainBoard = null;

/// <summary>
/// 构造方法,传入主板对象
/// </summary>
/// <param name="mainBoard">主板对象</param>
public OpenCommand(MainBoardApi mainBoard)
{
this.mainBoard = mainBoard;
}

public void Execute()
{
//对于命令对象,根本不知道如何开机,会转调主板对象
//让主板去完成开机的功能
this.mainBoard.Open();
}
}

/// <summary>
/// 重启机器命令的实现,实现Command接口,
/// 持有重启机器命令的真正实现,通过调用接收者的方法来实现命令
/// </summary>
public class ResetCommand : Command
{

/// <summary>
/// 持有真正实现命令的接收者——主板对象
/// </summary>
private MainBoardApi mainBoard = null;

/// <summary>
/// 构造方法,传入主板对象
/// </summary>
/// <param name="mainBoard">主板对象</param>
public ResetCommand(MainBoardApi mainBoard)
{
this.mainBoard = mainBoard;
}


public void Execute()
{
//对于命令对象,根本不知道如何重启机器,会转调主板对象
//让主板去完成重启机器的功能
this.mainBoard.Reset();
}
}


/// <summary>
/// 主板的接口
/// </summary>
public interface MainBoardApi
{

/// <summary>
/// 主板具有能开机的功能
/// </summary>
void Open();

/// <summary>
/// 主板具有实现重启的功能
/// </summary>
void Reset();
}

/// <summary>
/// 技嘉主板类,开机命令的真正实现者,在Command模式中充当Receiver
/// </summary>
public class GigaMainBoard : MainBoardApi
{

/// <summary>
/// 真正的开机命令的实现
/// </summary>
public void Open()
{
Console.WriteLine("技嘉主板现在正在开机,请等候");
Console.WriteLine("接通电源......");
Console.WriteLine("设备检查......");
Console.WriteLine("装载系统......");
Console.WriteLine("机器正常运转起来......");
Console.WriteLine("机器已经正常打开,请等候");
}

/// <summary>
/// 真正的重新启动机器命令的实现
/// </summary>
public void Reset()
{
Console.WriteLine("微星主板现在正在重新启动机器,请等候");
Console.WriteLine("机器已经正常打开,请等候");
}
}

/// <summary>
/// 微星主板类,开机命令的真正实现者,在Command模式中充当Receiver
/// </summary>
public class MsiMainBoard : MainBoardApi
{
/// <summary>
/// 真正的开机命令的实现
/// </summary>
public void Open()
{
Console.WriteLine("微星主板现在正在开机,请等候");
Console.WriteLine("接通电源......");
Console.WriteLine("设备检查......");
Console.WriteLine("装载系统......");
Console.WriteLine("机器正常运转起来......");
Console.WriteLine("机器已经正常打开,请等候");
}

/// <summary>
/// 真正的重新启动机器命令的实现
/// </summary>
public void Reset()
{
Console.WriteLine("微星主板现在正在重新启动机器,请等候");
Console.WriteLine("机器已经正常打开,请等候");
}
}


/// <summary>
/// 机箱对象,本身有按钮,持有按钮对应的命令对象
/// </summary>
public class Box
{
/// <summary>
/// 开机命令对象
/// </summary>
private Command openCommand;

/// <summary>
/// 设置开机命令对象
/// </summary>
/// <param name="command">开机命令对象</param>
public void SetOpenCommand(Command command)
{
this.openCommand = command;
}

/// <summary>
/// 提供给客户使用,接受并相应用户请求,相当于按钮被按下触发的方法
/// </summary>
public void OpenButtonPressed()
{
//按下按钮,执行命令
openCommand.Execute();
}

/// <summary>
/// 重启机器命令对象
/// </summary>
private Command resetCommand;

/// <summary>
/// 设置重启机器命令对象
/// </summary>
/// <param name="command"></param>
public void SetResetCommand(Command command)
{
this.resetCommand = command;
}

/// <summary>
/// 提供给客户使用,接受并相应用户请求,相当于重启按钮被按下触发的方法
/// </summary>
public void ResetButtonPressed()
{
//按下按钮,执行命令
resetCommand.Execute();
}
}

7.可撤销的操作

可撤销的操作意思是:放弃该操作,回到未执行操作前的状态。

有两种基本的思路来实现可撤销的操作,一种是补偿式又称反操作式,比如被撤销的操作是+,那撤销的操作就是-。

另一种是存储恢复式,就是把操作前的状态记录下来,然后要撤销操作时直接恢复回去。

在这里我们演示第一种可撤销操作,剩下一种等到备忘录模式时在讲。

做一个计算机功能,只需要实现加减运算,还要让这个计算器支持可撤销的

示例代码:

class Program
{
static void Main(string[] args)
{

//1:组装命令和接收者
//创建接收者
OperationApi operation = new Operation();

//创建命令对象,并组装命令和接收者
AddCommand addCmd = new AddCommand(operation, 5);
SubCommand substractCmd = new SubCommand(operation, 3);

//2:把命令设置到持有者,就是计算器里面
Calculator calculator = new Calculator();
calculator.SetAddCommand(addCmd);
calculator.SetSubCommand(substractCmd);

//3:模拟按下按钮,测试一下
calculator.AddPressed();
Console.WriteLine("一次加法运算后的结果为:" + operation.GetResult());
calculator.SubPressed();
Console.WriteLine("一次减法运算后的结果为:" + operation.GetResult());

//测试撤消
calculator.UndoPressed();
Console.WriteLine("撤销一次后的结果为:" + operation.GetResult());
calculator.UndoPressed();
Console.WriteLine("再撤销一次后的结果为:" + operation.GetResult());

//测试恢复
calculator.RedoPressed();
Console.WriteLine("恢复操作一次后的结果为:" + operation.GetResult());
calculator.RedoPressed();
Console.WriteLine("再恢复操作一次后的结果为:" + operation.GetResult());

Console.Read();
}
}

/// <summary>
/// 命令接口,声明执行的操作,支持可撤销操作
/// </summary>
public interface Command
{
/// <summary>
/// 执行命令对应的操作
/// </summary>
void Execute();

/// <summary>
/// 执行撤销命令对应的操作
/// </summary>
void Undo();
}

/// <summary>
/// 具体的加法命令实现对象
/// </summary>
public class AddCommand : Command
{
/// <summary>
/// 持有具体执行计算的对象
/// </summary>
private OperationApi operationApi = null;

/// <summary>
/// 操作的数据,也就是要加上的数据
/// </summary>
private int num;

/// <summary>
/// 构造方法,传入具体执行计算的对象
/// </summary>
/// <param name="operationApi"></param>
/// <param name="num"></param>
public AddCommand(OperationApi operationApi, int num)
{
this.operationApi = operationApi;
this.num = num;
}

public void Execute()
{
////转调接收者去真正执行功能,这个命令是做加法
operationApi.Add(num);
}

public void Undo()
{
//转调接收者去真正执行功能
//命令本身是做加法,那么撤销的时候就是做减法了
operationApi.Sub(num);
}
}

/// <summary>
/// 具体的减法命令实现对象
/// </summary>
public class SubCommand : Command
{
/// <summary>
/// 持有具体执行计算的对象
/// </summary>
private OperationApi operationApi = null;

/// <summary>
/// 操作的数据,也就是要加上的数据
/// </summary>
private int num;

/// <summary>
/// 构造方法,传入具体执行计算的对象
/// </summary>
/// <param name="operationApi"></param>
/// <param name="num"></param>
public SubCommand(OperationApi operationApi, int num)
{
this.operationApi = operationApi;
this.num = num;
}

public void Execute()
{
//转调接收者去真正执行功能,这个命令是做减法
operationApi.Sub(num);
}

public void Undo()
{
//转调接收者去真正执行功能
//命令本身是做减法,那么撤销的时候就是做加法了
operationApi.Add(num);
}
}

/// <summary>
/// 操作运算的接口
/// </summary>
public interface OperationApi
{
/// <summary>
/// 获取计算完成后的结果
/// </summary>
/// <returns></returns>
int GetResult();

/// <summary>
/// 设置计算开始的初始值
/// </summary>
/// <param name="result"></param>
void SetResult(int result);

/// <summary>
/// 执行加法
/// </summary>
/// <param name="num"></param>
void Add(int num);

/// <summary>
/// 执行减法
/// </summary>
/// <param name="num"></param>
void Sub(int num);
}

/// <summary>
/// 运算类,真正实现加减法运算
/// </summary>
public class Operation : OperationApi
{
/// <summary>
/// 记录运算的结果
/// </summary>
private int result;

public int GetResult()
{
return result;
}

/// <summary>
/// 设置值
/// </summary>
/// <param name="result"></param>
public void SetResult(int result)
{
this.result = result;
}

/// <summary>
/// 实现加法功能
/// </summary>
/// <param name="num"></param>
public void Add(int num)
{
result += num;
}

/// <summary>
/// 实现减法功能
/// </summary>
/// <param name="num"></param>
public void Sub(int num)
{
result -= num;
}
}

/// <summary>
/// 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮
/// </summary>
public class Calculator
{
/// <summary>
/// 命令的操作的历史记录,在撤销时候用
/// </summary>
private List<Command> undoCmds = new List<Command>();

/// <summary>
/// 命令被撤销的历史记录,在恢复时候用
/// </summary>
private List<Command> redoCmds = new List<Command>();

/// <summary>
/// 持有执行加法的命令对象
/// </summary>
private Command addCommand = null;

/// <summary>
/// 持有执行减法的命令对象
/// </summary>
private Command subCommand = null;

/// <summary>
/// 设置执行加法的命令对象
/// </summary>
/// <param name="addCommand"></param>
public void SetAddCommand(Command addCommand)
{
this.addCommand = addCommand;
}

/// <summary>
/// 设置执行减法的命令对象
/// </summary>
/// <param name="subCommand"></param>
public void SetSubCommand(Command subCommand)
{
this.subCommand = subCommand;
}

/// <summary>
/// 加法按钮
/// </summary>
public void AddPressed()
{
this.addCommand.Execute();
//把操作记录到历史记录里面
undoCmds.Add(this.addCommand);
}

/// <summary>
/// 减法按钮
/// </summary>
public void SubPressed()
{
this.subCommand.Execute();
//把操作记录到历史记录里面
undoCmds.Add(this.subCommand);
}

/// <summary>
/// 撤销按钮
/// </summary>
public void UndoPressed()
{
if (this.undoCmds.Count > 0)
{
//取出最后一个命令来撤销
Command cmd = this.undoCmds.Last();
cmd.Undo();
//如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面
this.redoCmds.Add(cmd);
//然后把最后一个命令删除掉,
this.undoCmds.Remove(cmd);
}
else
{
Console.WriteLine("很抱歉,没有可撤销的命令");
}
}

/// <summary>
/// 恢复按钮
/// </summary>
public void RedoPressed()
{
if (this.redoCmds.Count > 0)
{
//取出最后一个命令来重做
Command cmd = this.redoCmds.Last();
cmd.Execute();
//把这个命令记录到可撤销的历史记录里面
this.undoCmds.Add(cmd);
//然后把最后一个命令删除掉
this.redoCmds.Remove(cmd);
}
else
{
Console.WriteLine("很抱歉,没有可恢复的命令");
}
}
}

8.宏命令

宏命令就是包含多个命令的命令,是一个命令的组合。命令命令模式也是能实现的。

9.队列请求

所谓队列请求,就是对命令对象进行排队,组成工作队列,然后一次取出命令对象来执行。

10.日志请求

日志请求,就是将请求的历史记录保存下来,一般是采用永久存储的方式。如果运行请求过程中,系统崩溃了,那么当系统再次运行时,就可以从保存的历史记录中获取日志请求,并重新执行命令。

11.命令模式的优点

更松散的耦合

命令模式使得发起命令的对象——客户端,和命令的执行者对象完全解耦。

更动态的控制

命令模式将请求封装起来,可以动态地对它进行参数化、队列化和日志化等操作,使得系统更加灵活。

更自然的复合命令

命令模式中的命令对象能够很容易的组合成符合命令,如前面的宏命令。

12.何时选用命令模式

如果需要抽象出需要执行的动作,并参数化这些对象,可以使用命令模式。将这些需要执行的动作抽象成为命令,然后实现命令的参数化配置。

如果需要在不同的时刻指定、排列和执行请求,可以选用命令模式。

如果需要支持取消操作,可以选用命令模式,通过管理命令对象,很容易实现命令的恢复和重做功能。

如果需要支持系统崩溃时,重启后能将系统的操作功能重新执行一遍,可以选用命令模式。

在需要事务的系统中,可以选用命令模式。

13.命令模式的本质

命令模式的本质就是“封装请求”。命令模式的关键就是把请求封装称为命令对象,然后就可以对这个对象进行一系列的处理。

设计模式之组合模式(十四)

一、引出模式

在软件开发中,我们经常会遇到树型目录的功能,比如:管理商品的目录

如果让你来实现这个功能,你会怎么做呢?

我们先来分析分析:商品类别树上的节点有三类,根节点、树枝节点和叶子节点,在进一步根节点和树枝节点都是可以包含其他节点的,我们就叫它容器节点。这样,商品类别树就分为了容器节点和叶子节点,我们将它们分别实现成为对象。

代码示例:

class Program
{
static void Main(string[] args)
{
//定义所有的组合对象
Composite root = new Composite("服装");
Composite c1 = new Composite("男装");
Composite c2 = new Composite("女装");
//定义所有的叶子对象
Leaf leaf1 = new Leaf("衬衣");
Leaf leaf2 = new Leaf("夹克");
Leaf leaf3 = new Leaf("裙子");
Leaf leaf4 = new Leaf("套装");
//按照树的结构来组合组合对象和叶子对象
root.AddComposite(c1);
root.AddComposite(c2);

c1.AddLeaf(leaf1);
c1.AddLeaf(leaf2);

c2.AddLeaf(leaf3);
c2.AddLeaf(leaf4);

//调用根对象的输出功能来输出整棵树
root.PrintStruct("");

Console.ReadKey();
}
}

/// <summary>
/// 叶子对象
/// </summary>
public class Leaf
{
/// <summary>
/// 叶子对象的名字
/// </summary>
private string name = null;

/// <summary>
/// 构造方法,传入叶子对象的名字
/// </summary>
/// <param name="name">叶子对象的名字</param>
public Leaf(string name)
{
this.name = name;
}

/// <summary>
/// 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
/// </summary>
/// <param name="preStr">前缀,主要是按照层级拼接的空格,实现向后缩进</param>
public void PrintStruct(string preStr)
{
Console.WriteLine(preStr + "-" + name);
}
}

/// <summary>
/// 组合对象,可以包含其它组合对象或者叶子对象
/// </summary>
public class Composite
{
/// <summary>
/// 用来记录包含的其它组合对象
/// </summary>
private List<Composite> childComposite = new List<Composite>();

/// <summary>
/// 用来记录包含的其它叶子对象
/// </summary>
private List<Leaf> childLeaf = new List<Leaf>();

/// <summary>
/// 组合对象的名字
/// </summary>
private string name = null;

/// <summary>
/// 构造方法,传入组合对象的名字
/// </summary>
/// <param name="name"></param>
public Composite(string name)
{
this.name = name;
}

/// <summary>
/// 向组合对象加入被它包含的其它组合对象
/// </summary>
/// <param name="c"></param>
public void AddComposite(Composite c)
{
this.childComposite.Add(c);
}

/// <summary>
/// 向组合对象加入被它包含的叶子对象
/// </summary>
/// <param name="leaf"></param>
public void AddLeaf(Leaf leaf)
{
this.childLeaf.Add(leaf);
}

/// <summary>
/// 输出组合对象自身的结构
/// </summary>
/// <param name="preStr"></param>
public void PrintStruct(String preStr)
{
//先把自己输出去
Console.WriteLine(preStr + "+" + this.name);
//然后添加一个空格,表示向后缩进一个空格,输出自己包含的叶子对象
preStr += " ";
foreach (Leaf leaf in childLeaf)
{
leaf.PrintStruct(preStr);
}

//输出当前对象的子对象了
foreach (Composite c in childComposite)
{
////递归输出每个子对象
c.PrintStruct(preStr);
}
}
}

功能上已经实现好了,但有何问题呢?

区分了组合对象和叶子对象,并进行有区别的对待,比如在Composite和Client里面,都需要区别对待这两种对象,这就是个问题。

对于这种具有整体与部分关系,并能组合成树型结构的对象结构,如何才能够以一个统一的方式来进行操作呢?

二、认识模式

1.模式定义

将对象组合成为属性结构以表示“整体-部分”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

 

 

   
次浏览       
 
相关文章

用户手册:EA Helper
自然语言自动化生成图
使用iSpace进行多人协作建模
基于模型的软件复用(MBSR)
 
相关文档

AUTOSAR_TR_BSW UML模型建模指南
UML时间图建模(基于EA)
UML 模型框架(基于EA)
UML序列图编写规范
 
相关课程

UML+EA+面向对象分析设计
UML + 嵌入式系统分析设计
业务建模与业务分析
基于UML和EA进行系统分析设计

最新活动计划
QT应用开发 11-21[线上]
C++高级编程 11-27[北京]
LLM大模型应用与项目构建 12-26[特惠]
UML和EA进行系统分析设计 12-20[线上]
数据建模方法与工具 12-3[北京]
SysML建模专家 1-16[北京]
 
 
最新文章
在EA中内嵌文档- Artifact
EA中模型视图
EA中的实体关系图
使用EA进行风险建模
EA中的项目词汇表
EA的模型导出或导入csv文件
自定义表格(Custom Table)在EA中的使用
Gap Analysis Matrix(差距分析矩阵)
更多...   
MBSE工具
MBSE平台
建模工具 EA
模型库-Model Center
需求管理-ReqManager
自动建模-Modeler
多级仿真-Sys Simulator
代码工程-Code Engineer
文档生成器-DocGenerator
更多...   
成功案例
广汽研究院 SysML+EA+软件分析设计
高合汽车研发部门 建模工具EA、WebEA、学习视频
国汽智联 建模工具EA、模型库、WebEA和iSpace
亿咖通 MBSE工程体系与工具链咨询
中航无人机 MBSE工具链
吉利汽车 购买EA工具
华科汽车零部件 购买EA工具
东风岚图汽车 购买EA工具 以及EA定制开发
更多...