万字解析设计模式之策略模式、命令模式

本文详细介绍了策略模式和命令模式。策略模式定义算法家族并封装,可互相替换,介绍了其结构、实现、优缺点和使用场景,还给出实验任务。命令模式将请求封装成对象,实现请求者和接收者松耦合,同样介绍了相关内容及源码分析,也有对应实验。
该文章已生成可运行项目,

一、策略模式

1.1概述

先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车、可以坐汽车、可以坐火车、可以坐飞机。

 策略模式(Strategy Pattern)是一个行为型设计模式,它定义了一组算法家族,分别封装起来,让它们之间可以互相替换,且算法的变化不会影响使用算法的客户。通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

1.2结构

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。定义了所有支持的算法的共同接口,可以是抽象类、接口或抽象类和接口的组合。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境(Context)类:持有一个策略类的引用,将具体的算法委托给这些策略对象来处理,最终给客户端调用。

1.3实现

【例】促销活动

一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、圣诞节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:

 抽象策略(Strategy)类

package com.yanyu.Strategyer;


public interface Strategy {
    void show();
}

具体策略角色(Concrete Strategy)

package com.yanyu.Strategyer;

//为春节准备的促销活动A
public class StrategyA implements Strategy {

    public void show() {
        System.out.println("买一送一");
    }
}
package com.yanyu.Strategyer;

//为中秋准备的促销活动B
public class StrategyB implements Strategy {
    public void show() {
        System.out.println("满200元减50元");
    }
}
package com.yanyu.Strategyer;

//为圣诞准备的促销活动C
public class StrategyC implements Strategy {
    public void show() {
        System.out.println("满1000元加一元换购任意200元以下商品");
    }
}

环境(Context)类

package com.yanyu.Strategyer;

public class SalesMan {
    //持有抽象策略角色的引用
    private Strategy strategy;

    public SalesMan(Strategy strategy) {
        this.strategy = strategy;
    }

    public Strategy getStrategy() {
        return strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    //向客户展示促销活动
    public void salesManShow(){
        strategy.show();
    }
}

客户端类

package com.yanyu.Strategyer;

public class Client {
    public static void main(String[] args) {
        //春节来了, 使用春节促销活动
        SalesMan salesMan = new SalesMan(new StrategyA());
       // 展示促销活动
        salesMan.salesManShow();
        System.out.println("=============");
       // 中秋节到了, 使用中秋节的促销活动
        salesMan.setStrategy (new StrategyB());
        // 展示促销活动
        salesMan.salesManShow();
    }
}

1.4优缺点

1,优点:

  • 策略类之间可以自由切换

    由于策略类都实现同一个接口,所以使它们之间可以自由切换。

  • 易于扩展

    增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“

  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

2,缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量

1.5使用场景

  • 当你想使用对象中各种不同的算法变体, 并希望能在运行时切换算法时, 可使用策略模式;

  • 当你有许多仅在执行某些行为时略有不同的相似类时, 可使用策略模式;

  • 如果算法在上下文的逻辑中不是特别重要, 使用该模式能将类的业务逻辑与其算法实现细节隔离开来;

  • 当类中使用了复杂条件运算符以在同一算法的不同变体中切换时, 可使用该模式。

  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为

二、 命令模式

2.1概述

日常生活中,我们出去吃饭都会遇到下面的场景。

命令模式是一种行为设计模式,将请求或操作封装成对象,从而使请求者和接收者松耦合,使发出请求的责任和执行请求的责任分割开。在命令模式中,命令就像是一个对象,可以被存储、重复、撤销、恢复等操作,因此它可以通过队列、日志等方式来管理和操作。

为什么需要命令模式

假如你正在开发一款新的文字编辑器, 当前的任务是创建一个包含多个按钮的工具栏, 并让每个按钮对应编辑器的不同操作。 你创建了一个非常简洁的 按钮类, 它不仅可用于生成工具栏上的按钮, 还可用于生成各种对话框的通用按钮。

最简单的解决方案是在使用按钮的每个地方都创建大量的子类。 这些子类中包含按钮点击后必须执行的代码。

zeren

你很快就意识到这种方式有严重缺陷。 首先, 你创建了大量的子类, 当每次修改基类 按钮时, 你都有可能需要修改所有子类的代码。 简单来说, GUI 代码以一种拙劣的方式依赖于业务逻辑中的不稳定代码。 还有一个部分最难办。 复制/粘贴文字等操作可能会在多个地方被调用。 例如用户可以点击工具栏上小小的 “复制” 按钮, 或者通过上下文菜单复制一些内容, 又或者直接使用键盘上的 Ctrl+C 。

最简单的解决方案是在使用按钮的每个地方都创建大量的子类。 这些子类中包含按钮点击后必须执行的代码。

zeren

你很快就意识到这种方式有严重缺陷。 首先, 你创建了大量的子类, 当每次修改基类 按钮时, 你都有可能需要修改所有子类的代码。 简单来说, GUI 代码以一种拙劣的方式依赖于业务逻辑中的不稳定代码。 还有一个部分最难办。 复制/粘贴文字等操作可能会在多个地方被调用。 例如用户可以点击工具栏上小小的 “复制” 按钮, 或者通过上下文菜单复制一些内容, 又或者直接使用键盘上的 Ctrl+C 。

用命令模式解决问题

软件设计通常会将关注点进行分离。 最常见的例子: 一层负责用户图像界面; 另一层负责业务逻辑。 GUI 层负责在屏幕上渲染美观的图形, 捕获所有输入并显示用户和程序工作的结果。 当需要完成一些重要内容时 (比如计算月球轨道或撰写年度报告), GUI 层则会将工作委派给业务逻辑底层。

这在代码中看上去就像这样: 一个 GUI 对象传递一些参数来调用一个业务逻辑对象。 这个过程通常被描述为一个对象发送请求给另一个对象。

 2.2结构

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 负责执行具体的命令操作。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 负责调用命令并执行。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

2.3实现

将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当。

服务员: 就是调用者角色,由她来发起命令。

资深大厨: 就是接收者角色,真正命令执行的对象。

订单: 命令中包含订单。

类图如下:

抽象命令类

package com.yanyu.Commander;

public interface Command {
    void execute();//只需要定义一个统一的执行方法
}

具体命令(Concrete Command)角色

package com.yanyu.Commander;

import java.util.Set;

public class OrderCommand implements Command {

    //持有接受者对象
    private SeniorChef receiver; // 命令模式中的接收者对象
    private Order order; // 命令的参数

    public OrderCommand(SeniorChef receiver, Order order){
        this.receiver = receiver; // 初始化接收者对象
        this.order = order; // 初始化命令的参数
    }

    public void execute()  {
        System.out.println(order.getDiningTable() + "桌的订单:"); // 输出订单信息
        Set<String> keys = order.getFoodDic().keySet(); // 获取订单中食物的键集合
        for (String key : keys) {
            receiver.makeFood(order.getFoodDic().get(key),key); // 调用接收者对象的方法执行制作食物的操作
        }

        try {
            Thread.sleep(100); // 停顿一下 模拟做饭的过程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(order.getDiningTable() + "桌的饭弄好了"); // 输出制作完成的信息
    }
}

接收者(Receiver)角色

package com.yanyu.Commander;

// 资深大厨类 是命令的Receiver
public class SeniorChef {
    public void makeFood(int num,String foodName) {
        System.out.println(num + "份" + foodName);
    }
}

调用者

package com.yanyu.Commander;

import java.util.ArrayList;

public class Waitor {

    private ArrayList<Command> commands;//可以持有很多的命令对象

    public Waitor() {
        commands = new ArrayList();
    }

    public void setCommand(Command cmd){
        commands.add(cmd);
    }

    // 发出命令 喊 订单来了,厨师开始执行
    public void orderUp() {
        System.out.println("美女服务员:叮咚,大厨,新订单来了.......");
        for (int i = 0; i < commands.size(); i++) {
            Command cmd = commands.get(i);
            if (cmd != null) {
                cmd.execute();
            }
        }
    }
}

命令的请求

package com.yanyu.Commander;

import java.util.HashMap;
import java.util.Map;

public class Order {
    // 餐桌号码
    private int diningTable;

    // 用来存储餐名并记录份数
    private Map<String, Integer> foodDic = new HashMap<String, Integer>();

    public int getDiningTable() {
        return diningTable; // 获取餐桌号码
    }

    public void setDiningTable(int diningTable) {
        this.diningTable = diningTable; // 设置餐桌号码
    }

    public Map<String, Integer> getFoodDic() {
        return foodDic; // 获取食物名称和数量的映射
    }

    public void setFoodDic(String name, int num) {
        foodDic.put(name,num); // 添加食物名称和数量的映射
    }
}

客户端类

package com.yanyu.Commander;

public class Client {
    public static void main(String[] args) {
        //创建2个order
        Order order1 = new Order();
        order1.setDiningTable(1);
        order1.getFoodDic().put("西红柿鸡蛋面",1);
        order1.getFoodDic().put("小杯可乐",2);

        Order order2 = new Order();
        order2.setDiningTable(3);
        order2.getFoodDic().put("尖椒肉丝盖饭",1);
        order2.getFoodDic().put("小杯雪碧",1);

        //创建接收者
        SeniorChef receiver=new SeniorChef();
        //将订单和接收者封装成命令对象
        OrderCommand cmd1 = new OrderCommand(receiver, order1);
        OrderCommand cmd2 = new OrderCommand(receiver, order2);
        //创建调用者 waitor
        Waitor invoker = new Waitor();
        invoker.setCommand(cmd1);
        invoker.setCommand(cmd2);

        //将订单带到柜台 并向厨师喊 订单来了
        invoker.orderUp();
    }
}

2.4 优缺点

1,优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  • 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

2,缺点:

  • 使用命令模式可能会导致某些系统有过多的具体命令类。
  • 系统结构更加复杂。

2.5使用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

2.6源码分析

在JDK中,命令模式的经典实现是Runnable接口和java.util.concurrent包中的Executor框架。Runnable接口定义了一个执行动作的协议,而Executor框架则提供了一些实现Runnable接口的类(如ThreadPoolExecutor),以及执行Runnable对象的方法。

以下是一个简单的示例,我们将使用Runnable接口和Executor框架来实现命令模式:

public class CommandPatternJDKExample {

    public static void main(String[] args) {
        // 创建一个命令对象
        Runnable command = new ConcreteCommand();

        // 创建一个执行器对象
        Executor executor = Executors.newSingleThreadExecutor();

        // 执行命令
        executor.execute(command);
    }
}

// 具体命令类
class ConcreteCommand implements Runnable {

    @Override
    public void run() {
        System.out.println("命令被执行了");
    }
}

在上面的示例中,我们创建了一个ConcreteCommand类,它实现了Runnable接口,并且在run()方法中执行了一个简单的命令。然后,我们使用Executors类创建了一个执行器对象,并调用了它的execute()方法来执行这个命令。

三、策略模式实验

任务描述

在多个裁判负责打分的单人跳水比赛中,每位裁判给选手一个得分,选手的最后得分是根据全体裁判的得分计算出来的。现有5名裁判和7人裁判两种评分计算方案,以后可能会有更多计算方案。

  • 5名裁判员评分规则:5名裁判员打出分数以后,先删去最高和最低的无效分,余下3名裁判员的分数之和乘以运动员所跳动作的难度系数,便得出该动作的实得分;
  • 7名裁判员评分规则:方法与5名裁判员评分方法相同,但7人裁判员算出的得分最后还应除以5,再乘以3。

本关任务:请设计一种能兼容多种评分策略的架构,且用户可以自主选择其中一种策略作为比赛的评分方案。

实现方式

  1. 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符);

  2. 声明该算法所有变体的通用策略接口;

  3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口;

  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据;

  5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。

编程要求

根据提示,在右侧编辑器 Begin-End 内补充代码:

  • PlayerScore:上下文环境类;
  • Codepoints:抽象策略;
  • Codepoints5:5人裁判评分策略类;
  • Codepoints7:7人裁判评分策略类;
  • Client:客户端类。

测试说明

平台会对你编写的代码进行测试,第一行给出一个正整数 n(表示裁判人数)和一个实数 k(表示跳水难度系数)。第二行给出 n 个实数(评分成绩),中间以空格进行分隔。输出最后结果,格式为 The final score is XX.XX,保留小数点后两位。

测试输入: 5 2.0 5 5.5 5 5 4.5 预期输出: The final score is 30.00

测试输入: 7 2.0 5 5.5 5 5 4.5 5 5 预期输出: The final score is 30.00

抽象策略(Strategy)类

package step1;

/********** Begin *********/
public interface Codepoints{
    double computerScore(double []a,double difficulty);

}
/********** End *********/

 具体策略(Concrete Strategy)类

package step1;

/********** Begin *********/
import java.util.Arrays;

public class Codepoints5 implements Codepoints{
    
    public double computerScore(double[]a,double difficulty){
        if(a.length<=2)
            return 0;
        double score,sum=0;
        Arrays.sort(a);//排序数组
        for (int i=1;i<a.length-1;i++){
            sum=sum+a[i];
        }
        score=sum*difficulty;
        return score;
    }
}

/********** End *********/
package step1;

/********** Begin *********/
import java.util.Arrays;

public class Codepoints7 implements Codepoints{
    
    public double computerScore(double[]a,double difficulty){
        if(a.length<=2)
            return 0;
        double score,sum=0;
        Arrays.sort(a);//排序数组
        for (int i=1;i<a.length-1;i++){
            sum=sum+a[i];
        }
        score=sum/5.0*3*difficulty;
        return score;
    }
}

/********** End *********/

环境(Context)类

package step1;

/********** Begin *********/

public class PlayerScore{
    private Codepoints codepoints;
    public void setStrategy(Codepoints codepoints){
    this.codepoints=codepoints;
    }
    public double Calculate(double []a,double difficulty){
        return codepoints.computerScore(a,difficulty);
    }
}
/********** End *********/

客户端类


package step1;

import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        /********** Begin *********/
        PlayerScore player=new PlayerScore ();
        Scanner scanner = new Scanner(System.in);
        int n =scanner.nextInt();// 裁判人数
        double diff=scanner.nextDouble();//动作的难度系数
        double[] array=new double [n];// 裁判的评分
        for (int i=0 ; i<n ; i++){
            array[i] = scanner.nextDouble();
        }
        if (n==5){
            player.setStrategy(new Codepoints5());
        }
        else if (n==7) {
            player.setStrategy(new Codepoints7());
        }
       
        System.out.println("The final score is "+ String.format("%.2f", player.Calculate(array,diff)));
        /********** End *********/
    }
}

四、命令模式实验

任务描述

zeren

在植物大战僵尸的游戏设计中,植物战士是兵营生产的,玩家通过点击界面上“图片按钮”向兵营发出生产命令,兵营接受命令后每生产一个植物战士需要冷却一定的时间才能继续生产。

本关任务:本模拟程序中只需要实现一种植物战士的生产,请你用命令模式实现。

实现方式

  1. 声明仅有一个执行方法的命令接口;

  2. 抽取请求并使之成为实现命令接口的具体命令类。 每个类都必须有一组成员变量来保存请求参数和对于实际接收者对象的引用。 所有这些变量的数值都必须通过命令构造函数进行初始化;

  3. 找到担任发送者职责的类。 在这些类中添加保存命令的成员变量。 发送者只能通过命令接口与其命令进行交互。 发送者自身通常并不创建命令对象, 而是通过客户端代码获取;

  4. 修改发送者使其执行命令, 而非直接将请求发送给接收者;

  5. 客户端必须按照以下顺序来初始化对象:

    •  创建接收者;
    •  创建命令, 如有需要可将其关联至接收者;
    •  创建发送者并将其与特定命令关联。

编程要求

根据提示,在右侧编辑器 Begin-End 内补充 “CampInvokers.java”和 “ProduceCommand.java” 文件代码,其它地方不要修改。

测试说明

平台会对你编写的代码进行测试,请输入发出生产命令的个数和兵营冷却时间(毫秒)。 输入结束后,为了模拟界面操作,系统会在 1000 毫秒内持续监控兵营接到的命令。 测试输入:6 200 预期输出: 豌豆射手生产出来了 豌豆射手生产出来了 豌豆射手生产出来了 豌豆射手生产出来了 豌豆射手生产出来了

测试输入:6 500 预期输出: 豌豆射手生产出来了 豌豆射手生产出来了

抽象命令类

package step1;

public abstract class Commands{
    public abstract void Execute();
}

具体命令

package step1;

public class ProduceCommand extends Commands{
    @Override
    public void Execute() {
        /********** Begin *********/
        //生产植物战士
        new Peashooter();
       
        /********** End *********/
    }
}
package step1;
//所有战士的基类
public interface IBotany {
}
package step1;
//植物战士
public class Peashooter implements IBotany {
    public Peashooter()
    {
        System.out.println("豌豆射手生产出来了");
    }
}

接收者(Receiver)角色

调用者

package step1;

import java.util.ArrayList;
import java.util.List;

// 兵营就是命令的管理者
public class CampInvokers {
    private final List<Commands> commands = new ArrayList<>(); // 命令队列
    private final long TrainTimer; // 训练冷却时间
    private long timer; // 当前时间
    
    // 构造函数,初始化训练冷却时间和当前时间
    public CampInvokers(long trainTime){
        TrainTimer=trainTime;
        timer=0;
    }
    
    // 添加训练命令到命令队列
    public void Train()
    {
        // 将生产命令加入到命令队列
        commands.add(new ProduceCommand());
    }
    
    // 执行命令队列中的命令
    public void ExecuteCommand()
    {
        if (commands.size() <= 0) return;
        if (timer+TrainTimer <= System.currentTimeMillis())
        {
            // 执行队列里最上面的命令
            commands.get(0).Execute(); 
            // 移除最上面的命令
            commands.remove(0); 
            // 重置冷却时间
            timer = System.currentTimeMillis(); 
        }
    }
    
    // 取消训练命令
    public void CancelTrainCommand()
    {
        if (commands.size() > 0)
        {
            commands.remove(commands.size() - 1);
            if (commands.size() == 0)
            {
                timer = System.currentTimeMillis();
            }
        }
    }
}

客户端类 

package step1;

import java.util.Scanner;

// 客户端类,用于演示命令模式
public class Client {
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        
        // 从用户输入获取训练次数和训练冷却时间
        int number = scanner.nextInt();
        long traintime = scanner.nextLong();
        long currtime= System.currentTimeMillis();

        // 创建兵营管理者对象
        CampInvokers camp = new CampInvokers(traintime);
        
        // 循环训练指定次数
        for (int i = 0; i < number; i++) {
            camp.Train();
        }
        
        // 循环执行命令直至时间到达1秒后
        while (true)
        {
            camp.ExecuteCommand();
            if(currtime+1000<System.currentTimeMillis())
                break;
        }
    }
}

本文章已经生成可运行项目
<think>好的,我现在需要详细解析设计模式中的组合模式。首先,根据用户提供的引用内容,组合模式是一种结构型设计模式,用于处理树形结构的对象集合,让客户端可以统一处理单个对象组合对象。我需要结合这些引用内容,组织一个结构清晰的回答,并生成相关问题。 首先,定义部分。引用[1]提到组合模式将对象组合成树形结构,表示“部分-整体”层次结构,客户端可以统一处理。引用[3]也提到部分-整体模式,用户对单个组合对象有一致的访问。因此,我需要先明确组合模式的定义目的。 接下来,结构分析。根据引用[1][3],组合模式包含组件接口、叶子节点复合节点。需要详细说明每个部分的作用,比如组件接口定义操作,叶子节点实现基本元素,复合节点包含子节点并管理它们。同时,引用[2]提到子节点管理策略,比如使用vector、数组等,这部分需要补充到结构分析中。 然后是设计理念,引用[1]指出透明式安全式的区别。透明式让叶子复合节点有相同接口,但叶子可能不支持某些操作;安全式则分离接口,客户端需要区别对待。需要解释这两种方式的优缺点,可能引用[2]提到的CompositeDecorator的区别,但这里主要聚焦在组合模式本身。 应用场景方面,引用[3]提到文件系统、UI组件、组织结构等例子。需要举出具体实例,如文件夹文件的结构,说明组合模式如何统一处理这些对象。 优缺点部分,优点应该包括简化客户端代码、扩展性好,符合开闭原则。缺点可能涉及设计复杂性性能问题,比如引用[2]提到的管理子节点的不同策略可能影响性能。 实现示例部分,需要提供一个简单的代码例子,比如用C++或Java实现组合模式的结构,展示组件接口、叶子节点复合节点的代码结构,以及如何添加子节点执行操作。 生成相关问题方面,根据解析内容,可能涉及组合模式与装饰器模式的区别、透明式与安全式的比较、实际应用案例、优缺点分析以及如何管理子节点等。 需要确保回答中正确引用来源,比如在定义、结构、设计理念等部分末尾添加引用标识。同时注意使用中文,数学公式用$...$,独立公式用$$...$$,但组合模式解析可能涉及较少数学内容,但如果有表达式的话要注意格式。 检查是否有遗漏的部分,比如组合模式在C++中的具体实现细节,如引用[2]提到的使用STL vector管理子节点,这需要在结构或实现部分提到。同时注意引用[2]中提到的组合模式装饰器模式的结构区别,但用户问题集中在组合模式,可能不需要深入比较,除非在相关问题中提到。 最后,确保回答结构清晰,分点明确,语言通顺,符合用户要求的系统级指令用户可见层的格式。</think>### 组合模式详解 #### 1. **定义与核心思想** 组合模式(Composite Pattern)是一种**结构型设计模式**,用于将对象组织成**树形结构**,以表示“部分-整体”的层次关系。它允许客户端以统一的方式处理单个对象(叶子节点)组合对象(复合节点),从而简化复杂结构的操作[^1][^3]。 #### 2. **模式结构** 组合模式包含以下核心组件: - **组件接口(Component)**:定义所有对象(包括叶子节点复合节点)的公共接口,例如 `operation()` 方法。 - **叶子节点(Leaf)**:表示树形结构中的基本元素,不包含子节点。 - **复合节点(Composite)**:包含子节点集合(如通过 `std::vector` 实现[^2]),并实现组件接口的方法(如递归调用子节点的 `operation()`)。 - **客户端(Client)**:通过组件接口与整个结构交互,无需区分具体类型。 #### 3. **设计理念** - **透明式设计**:组件接口中直接包含子节点管理方法(如 `add()`、`remove()`)。叶子节点需实现这些方法,但可能抛出异常(如不支持添加子节点)。 - **安全式设计**:仅在复合节点中定义子节点管理方法,叶子节点不实现这些方法。客户端需区分对象类型,但接口更安全。 #### 4. **应用场景** - **文件系统**:文件夹(复合节点)与文件(叶子节点)的统一操作。 - **UI界面**:窗口(复合节点)包含按钮(叶子节点)、面板(子复合节点)等。 - **组织结构管理**:部门(复合节点)与员工(叶子节点)的层级关系。 #### 5. **代码示例(C++透明式设计)** ```cpp #include <vector> #include <iostream> // 组件接口 class Component { public: virtual void operation() = 0; virtual void add(Component* c) {} // 透明式设计 virtual void remove(Component* c) {} virtual ~Component() = default; }; // 叶子节点 class Leaf : public Component { public: void operation() override { std::cout << "Leaf operation\n"; } }; // 复合节点 class Composite : public Component { private: std::vector<Component*> children; // 使用STL容器管理子节点 public: void operation() override { for (auto child : children) { child->operation(); // 递归调用子节点操作 } } void add(Component* c) override { children.push_back(c); } void remove(Component* c) override { // 省略具体实现 } }; ``` #### 6. **优缺点分析** - **优点**: - **简化客户端代码**:统一处理单个对象与组合对象。 - **扩展性强**:新增组件类型无需修改现有代码,符合开闭原则。 - **缺点**: - **接口冗余**:透明式设计中叶子节点需实现无关方法。 - **性能开销**:递归遍历可能影响效率,尤其在深层次结构中。 #### 7. **与其他模式的关系** - **与装饰器模式**:两者结构类似,但组合模式关注“部分-整体”结构,装饰器模式侧重动态添加职责[^2]。 - **与迭代器模式**:组合模式常与迭代器模式结合,遍历树形结构。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

烟雨平生9527

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值