Head First设计模式——组合模式

  最近比较忙,有段时间没有更新设计模式的进度了。今天继续学习组合设计模式。

  组合模式的例子我们继续延续上篇《Head First设计模式——迭代器模式》的菜单例子,首先声明下迭代器和组合模式没有什么关系,他们是两个不同模式。只是我们在这个菜单例子的组合模式内部会用到迭代器。

迭代器模式中说到两个餐馆合并然后使用迭代器进行统一处理菜单的打印,但是现在有一个新的需求是原来大菜单中我们希望加入子菜单,比如饭后甜点。那么这个时候对于需求模型来说就是类似下面这样

 

菜单拥有菜单项,菜单项中可能还拥有子菜单,我们现在要打印菜单。也就是处理每个菜单和菜单项,如何将他们合理的组织起来并统一处理?要解决这个问题,组合模式来实现这一需求。

定义组合模式

组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

这个模式能够创建一个树形结构,如果我们有了一个树形结构的菜单、子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种“组合”。因为它既可以包含其他菜单,也可以包含菜单项。个别对象只是菜单项并未持有其他对象。

利用组合设计菜单

我们需要创建一个组件接口来作为菜单和菜单项的共同接口,让我们能够用统一的做法来处理菜单和菜单项,换句话说,我们可以针对菜单或菜单项调用相同的方法。

我们画出菜单设计的类图:

 

MenuComponent:提供接口,让菜单项和菜单共同使用。我们可能会对方法提供一些默认实现,所以我们可以使用抽象类。

MenuItem:继承自MenuComponent,覆盖了它有意义的方法(add,remove不用管)。

Menu:继承自MenuComponent,覆盖对它有意义的方法。

实现组合模式

 实现菜单组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public abstract class MenuComponent
{
    public virtual void Add(MenuComponent menuComponent) {
        throw new NotSupportedException();
    }
    public virtual void Remove(MenuComponent menuComponent)
    {
        throw new NotSupportedException();
    }
    public virtual MenuComponent GetChild(int i)
    {
        throw new NotSupportedException();
    }
    public virtual void GetName()
    {
        throw new NotSupportedException();
    }
    public virtual string GetDescription()
    {
        throw new NotSupportedException();
    }
    public virtual double GetPrice()
    {
        throw new NotSupportedException();
    }
    public virtual bool IsVegetarian()
    {
        throw new NotSupportedException();
    }
    public virtual void Print()
    {
        throw new NotSupportedException();
    }
}

  实现菜单项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class MenuItme : MenuComponent
{
    string name;
    string decription;
    bool vegetarian;
    double price;
 
    public MenuItme(string name, string decription, bool vegetarian, double price)
    {
        this.name = name;
        this.decription = decription;
        this.vegetarian = vegetarian;
        this.price = price;
    }
 
    public override string GetName()
    {
        return name;
    }
    public override string GetDescription()
    {
        return decription;
    }
    public override double GetPrice()
    {
        return price;
    }
    public  override bool IsVegetarian()
    {
        return vegetarian;
    }
    public override void Print()
    {
        Console.Write(" " + GetName());
        if (IsVegetarian())
        {
            Console.Write("V" + GetName());
        }
        Console.WriteLine("," + GetPrice());
        Console.WriteLine("  --" + GetPrice());
    }
}

  实现组合菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public  class Menu:MenuComponent
{
    List<MenuComponent> menuComponents = new List<MenuComponent>();
    string name;
    string description;
    public Menu(string name, string description)
    {
        this.name = name;
        this.description = description;
    }
    public override void Add(MenuComponent menuComponent)
    {
        menuComponents.Add(menuComponent);
    }
    public override void Remove(MenuComponent menuComponent)
    {
        menuComponents.Remove(menuComponent);
    }
    public override MenuComponent GetChild(int i)
    {
        return menuComponents[i];
    }
    public override string GetName()
    {
        return name;
    }
    public override string GetDescription()
    {
        return description;
    }
 
    public override void Print()
    {
        Console.Write("\n" + GetName());
        Console.WriteLine("," + GetDescription());
        Console.WriteLine("---------------------");
        foreach (var item in menuComponents)
        {
            item.Print();
        }
    }
}

  这里菜单打印直接用foreach 循环打印菜单组件,如果遇到另外一个菜单对象则进入子菜单打印。此处就是使用迭代器模式,只不过我偷了个懒直接用了foreach,因为list C#已经实现了迭代器,使用foreach语法即可。《C# Foreach循环本质与枚举器》

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
MenuComponent breakfastMenu = new Menu("早餐菜单", "早餐供应");
MenuComponent dinnerMenu = new Menu("晚餐菜单", "晚餐供应");
MenuComponent dessertMenu = new Menu("甜点菜单", "晚餐甜点");
 
MenuComponent allMenus = new Menu("ALL MENUS", "all menus combaind");
//加入菜单
allMenus.Add(breakfastMenu);
allMenus.Add(dinnerMenu);
//加入菜单项
breakfastMenu.Add(new MenuItme("包子", "鲜肉酱肉", false, 2));
dinnerMenu.Add(new MenuItme("牛肉拉面", "拉面配牛肉", false, 15));
dinnerMenu.Add(dessertMenu);
dessertMenu.Add(new MenuItme("梦龙卷", "切件", false, 16));
 
allMenus.Print();

  

posted @   XSpringSun  阅读(676)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示