面向对象设计模式:行为型模式之状态模式

news2024/11/27 11:22:08

文章目录

      • 一、引入
      • 二、状态模式
        • 2.1 Intent 意图
        • 2.2 Applicability 适用性
        • 2.3 类图
        • 2.4 Collaborations 合作
        • 2.5 Implementation 实现
        • 2.5 状态模式与策略模式的对比
        • 2.5 状态模式实例:糖果机
        • 2.6 状态模式实例:自行车升降档

一、引入

  • State Diagram 状态图:
    A state diagram is a type of diagram used in computer science and related fields to describe the behavior of systems.
    状态图是在计算机科学和相关领域中用于描述系统行为的一种图.

在这里插入图片描述
Parts of UML

  • 糖果机问题

在这里插入图片描述

  1. 状态汇总:No Quarter(无 25 美分)、Has Quarter(有 25 美分)、Gumball Sold(糖果售出)、Out Of Gumballs(糖果售罄)attribute value
  2. 行为汇总:insert quarter(投币)、ejects quarter(吐币)、turns crank(转动曲柄)、dispense(出糖果,For machine)method
package net.dp.state.gumball;

public class GumballMachine {

	final static int SOLD_OUT = 0;
	final static int NO_QUARTER = 1;
	final static int HAS_QUARTER = 2;
	final static int SOLD = 3;

	int state = SOLD_OUT;
	int count = 0;

	public GumballMachine(int count) {
		this.count = count;
		if (count > 0) {
			state = NO_QUARTER;
		}
	}

	public void insertQuarter() {
		if (state == HAS_QUARTER) {
			System.out.println("You can't insert another quarter");
		} else if (state == NO_QUARTER) {
			state = HAS_QUARTER;
			System.out.println("You inserted a quarter");
		} else if (state == SOLD_OUT) {
			System.out.println("You can't insert a quarter, the machine is sold out");
		} else if (state == SOLD) {
			System.out.println("Please wait, we're already giving you a gumball");
		}
	}

	public void ejectQuarter() {
		if (state == HAS_QUARTER) {
			System.out.println("Quarter returned");
			state = NO_QUARTER;
		} else if (state == NO_QUARTER) {
			System.out.println("You haven't inserted a quarter");
		} else if (state == SOLD) {
			System.out.println("Sorry, you already turned the crank");
		} else if (state == SOLD_OUT) {
			System.out
					.println("You can't eject, you haven't inserted a quarter yet");
		}
	}

	public void turnCrank() {
		if (state == SOLD) {
			System.out.println("Turning twice doesn't get you another gumball!");
		} else if (state == NO_QUARTER) {
			System.out.println("You turned but there's no quarter");
		} else if (state == SOLD_OUT) {
			System.out.println("You turned, but there are no gumballs");
		} else if (state == HAS_QUARTER) {
			System.out.println("You turned...");
			state = SOLD;
			dispense();
		}
	}

	public void dispense() {
		if (state == SOLD) {
			System.out.println("A gumball comes rolling out the slot");
			count = count - 1;
			if (count == 0) {
				System.out.println("Oops, out of gumballs!");
				state = SOLD_OUT;
			} else {
				state = NO_QUARTER;
			}
		} else if (state == NO_QUARTER) {
			System.out.println("You need to pay first");
		} else if (state == SOLD_OUT) {
			System.out.println("No gumball dispensed");
		} else if (state == HAS_QUARTER) {
			System.out.println("No gumball dispensed");
		}
	}

	public void refill(int numGumBalls) {
		this.count = numGumBalls;
		state = NO_QUARTER;
	}

	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004\n");
		result.append("Inventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\nMachine is ");
		if (state == SOLD_OUT) {
			result.append("sold out");
		} else if (state == NO_QUARTER) {
			result.append("waiting for quarter");
		} else if (state == HAS_QUARTER) {
			result.append("waiting for turn of crank");
		} else if (state == SOLD) {
			result.append("delivering a gumball");
		}
		result.append("\n");
		return result.toString();
	}
}

Terrible implements!

需求变更:

提升销量,10 % 的可能出两个糖果
在这里插入图片描述
在这里插入图片描述
难以维护:
在这里插入图片描述

二、状态模式

2.1 Intent 意图

  • Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. 状态模式允许一个对象在其内部状态改变的时候改变其行为,这个对象看上去就像改变了它的类一样.

2.2 Applicability 适用性

  • An object’s behavior depends on its state, and it must change its behavior at run-time depending on that state. 对象的行为取决于它的状态,它必须根据运行状态改变它的行为.
  • Operations have large, multipart conditional statements that depend on the object’s state. 操作有依赖于对象状态的大量的、多部分的条件语句.

2.3 类图

在这里插入图片描述

  • Context: Defines the interface of interest to clients; maintains an instance of a ConcreteState that defines the current state.
  • State: Defines an interface for encapsulating the behavior associated with a particular state of the Context.
  • ConcreteState: each implements a behavior associated with a state of the Context.

2.4 Collaborations 合作

  • Context delegates state-specific requests to the current ConcreteState object. Context 把状态相关的请求代理到当前的 ConcreteState 对象。
  • A context may pass itself as an argument to the State object handling the request. This lets the State object access the context if necessary. context 可以将自身作为参数传递给处理该请求的状态对象,以允许状态对象在必要时访问上下文。
  • Context is the primary interface for clients. State objects can be configured to context. Once a context is configured, its clients don’t have to deal with the State objects directly. Context 是 Client 的主要接口,状态对象可以配置到 context,并且一旦配置了状态,Client 就不必直接处理状态对象了。
  • Either Context or the ConcreteState can decide which state succeeds another and under what circumstances. Context 或 ConcreateState 均能决定 state 的后继状态是哪个以及在什么条件下(状态的变化如何)

2.5 Implementation 实现

  • Who defines the state transitions? 谁定义状态转换

    • The State pattern does not specify which participant defines the criteria for state transitions. 状态模式并不指定哪个参与者(Context、State)定义了状态转换的标准.
    • If the criteria are fixed, then they can be implemented entirely in the Context. 如果转换标准是固定的,那么它们就可以完全在 Context 中实现.
    • It is generally more flexible and appropriate to let the State subclasses themselves specify their successor state and when to make the transition. 通常,让状态子类本身指定它们的后继状态以及何时进行转换更加灵活和合适.
      • It is easy to modify or extend the logic by defining new State subclasses. 易于通过定义新 State 子类修改或扩展逻辑
      • A disadvantage is State subclass will have knowledge of at least one other, which introduces implementation dependencies between subclasses. 一个缺点是状态子类至少知道一个其他子类,这就引入了子类之间的实现依赖关系。
  • Creating and destroying State objects. 创建与销毁状态对象

    • Lazy: to create State objects only when they are needed and destroy them thereafter. 只在需要状态对象时创建状态对象,并在此之后销毁它们
      • When the states that will be entered aren’t known at runtime, and contexts change state infrequently. 当运行时不知道将输入的状态,且上下文 contexts 很少改变状态时.
    • Eager: creating them ahead of time and never destroying them. 提前创造出它们,但从不摧毁它们
      • When state changes occur rapidly. 状态改变频繁时

2.5 状态模式与策略模式的对比

  • Same:类图基本一致
  • Diff
    • Strategy: One state with many algorithms
      • 一个具有多个算法实现的抽象状态,采用哪个由 Client 决定
    • State: many States with different behaviors - 多个 具有不同行为的状态,一个状态对应一个行为
      • 采用哪个可以由 Client 决定,且运行过程中状态可以改变,从而行为也得到改变

状态模式与策略模式很相似,状态是将类的"状态"封装了起来,在执行动作时进行自动的转换,从而实现类在不同状态下的同一动作显示出不同结果。
它与策略模式的区别在于,这种转换是"自动","无意识"的。

关于策略模式:https://blog.csdn.net/qq_44992559/article/details/115007554

2.5 状态模式实例:糖果机

变更需求实现

  • 糖果机:即 Context
public class GumballMachine {
 
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
	State winnerState;
 
	State state = soldOutState;
	int count = 0;
 
	public GumballMachine(int numberGumballs) {
		soldOutState = new SoldOutState(this);
		noQuarterState = new NoQuarterState(this);
		hasQuarterState = new HasQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);

		this.count = numberGumballs;
 		if (numberGumballs > 0) {
			state = noQuarterState;
		} 
	}
 
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	public void ejectQuarter() {
		state.ejectQuarter();
	}
 
	public void turnCrank() {
		state.turnCrank();
		state.dispense();
	}

	void setState(State state) {
		this.state = state;
	}
 
	void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;
		}
	}
 
	int getCount() {
		return count;
	}
 
	void refill(int count) {
		this.count = count;
		state = noQuarterState;
	}

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

  • 糖果机状态接口:抽象状态
public interface State {

	public void insertQuarter();

	public void ejectQuarter();

	public void turnCrank();

	public void dispense();
}
  • 有 25 美分硬币状态
public class HasQuarterState implements State {
	Random randomWinner = new Random(System.currentTimeMillis());
	GumballMachine gumballMachine;
 
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
  
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}
 
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	public void turnCrank() {
		System.out.println("You turned...");
		int winner = randomWinner.nextInt(10);
		if ((winner == 0) && (gumballMachine.getCount() > 1)) {
			gumballMachine.setState(gumballMachine.getWinnerState());
		} else {
			gumballMachine.setState(gumballMachine.getSoldState());
		}
	}

    public void dispense() {
        System.out.println("No gumball dispensed");
    }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}

  • 无 25 美分硬币状态
public class NoQuarterState implements State {
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	public void dispense() {
		System.out.println("You need to pay first");
	} 
 
	public String toString() {
		return "waiting for quarter";
	}
}
  • 售罄状态
public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You can't insert a quarter, the machine is sold out");
	}
 
	public void ejectQuarter() {
		System.out.println("You can't eject, you haven't inserted a quarter yet");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there are no gumballs");
	}
 
	public void dispense() {
		System.out.println("No gumball dispensed");
	}
 
	public String toString() {
		return "sold out";
	}
}
  • 售卖(出)状态
public class SoldState implements State {

	GumballMachine gumballMachine;

	public SoldState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}

	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}

	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}

	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}

	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		} else {
			System.out.println("Oops, out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}

	public String toString() {
		return "dispensing a gumball";
	}
}
  • Winner 状态:出两糖状态
public class WinnerState implements State {
    GumballMachine gumballMachine;
 
    public WinnerState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void ejectQuarter() {
		System.out.println("Please wait, we're already giving you a Gumball");
	}
 
	public void turnCrank() {
		System.out.println("Turning again doesn't get you another gumball!");
	}
 
	public void dispense() {
		System.out.println("YOU'RE A WINNER! You get two gumballs for your quarter");
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() == 0) {
			gumballMachine.setState(gumballMachine.getSoldOutState());
		} else {
			gumballMachine.releaseBall();
			if (gumballMachine.getCount() > 0) {
				gumballMachine.setState(gumballMachine.getNoQuarterState());
			} else {
            	System.out.println("Oops, out of gumballs!");
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}
		}
	}
 
	public String toString() {
		return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
	}
}

2.6 状态模式实例:自行车升降档

可以将自身作为参数传递给处理该请求的状态对象,以允许状态对象在必要时访问上下文
状态转换交给 State

  • 自行车:即 Context
public class Bike {
    private GearState gearState;

    public Bike(GearState gearState) {
        this.gearState = gearState;
    }

    public GearState getGearState() {
        return gearState;
    }

    public void setGearState(GearState gearState) {
        this.gearState = gearState;
    }

    public void gearUp() {
        gearState.gearUp(this);
    }

    public void gearDown() {
        gearState.gearDown(this);
    }
}

  • 档数状态:抽象状态
/**
 * 档数状态
 */
public abstract class GearState {
    public GearState() {

    }

    // 升档
    public abstract void gearUp(Bike bike);

    // 降档
    public abstract void gearDown(Bike bike);
}
  • 档数状态:实现类
public class FirstGearState extends GearState {
    @Override
    public void gearUp(Bike bike) {
        System.out.print("Moving Up from FirstGear to SecondGear!\t");
        bike.setGearState(new SecondGearState());
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public void gearDown(Bike bike) {
        System.out.print("Already in the FirstGear - cannot go lower!\t");
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public String toString() {
        return "First Gear";
    }
}

public class SecondGearState extends GearState {

    @Override
    public void gearUp(Bike bike) {
        System.out.print("Moving Up  from SecondGear to ThirdGear!\t");
        bike.setGearState(new ThirdGearState());
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public void gearDown(Bike bike) {
        System.out.print("Moving Down from SecondGear to FirstGear!\t");
        bike.setGearState(new FirstGearState());
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public String toString() {
        return "Second Gear";
    }
}

public class ThirdGearState extends GearState {
    @Override
    public void gearUp(Bike bike) {
        System.out.print("Already in the ThirdGear - cannot go higher!\t");
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public void gearDown(Bike bike) {
        System.out.println("Moving Down from ThirdGear to SecondGear!\t");
        bike.setGearState(new SecondGearState());
        System.out.println("Current GearState: " + bike.getGearState().toString());
    }

    @Override
    public String toString() {
        return "Third Gear";
    }
}

  • Client 测试
public class StateClientDemo {
    public static void main(String[] args) {
        Bike bike = new Bike(new SecondGearState());
        bike.gearDown();
        bike.gearDown();
        bike.gearDown();
        bike.gearUp();
        bike.gearUp();
        bike.gearUp();
        bike.gearUp();
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/392920.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker数据卷及软件部署方式

目录 一、docker数据卷管理 1.docker数据卷概念 2.数据卷命令 3.创建容器并挂载数据卷 二、docker软件部署 1.docker部署mysql方式 下载MySQL5.7镜像文件 创建所需的数据卷目录 创建mysql容器并挂载数据卷 进入数据库授权远程连接用户访问 2. docker部署nginx方式 下…

C语言-基础了解-16-C字符串

C字符串 一、C字符串 在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。 空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符&#x…

剑指 Offer 31. 栈的压入、弹出序列

一、题目描述 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。 例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列…

centos7 安装 hyperf

​​​​​​PHP > 7.4 Swoole PHP 扩展 > 4.5,并关闭了 Short Name OpenSSL PHP 扩展 JSON PHP 扩展 PDO PHP 扩展 Redis PHP 扩展 Protobuf PHP 扩展 composer create-project hyperf/hyperf-skeleton 推荐安装项 全部选n php.ini [swoole] extens…

LQB小板焊接V3版本的小板原理图,PCB图,注意事项和步骤

第一部分,这个部分,可以不焊接,直接用买的下载器进行下载代码,外接一个下载器,网上大概是10元左右,以后学习stm32的芯片的时候,这个下载器就是一个串口转换器,也可以使用。。 当然也…

realloc也可以缩容了??

作者:小树苗渴望变成参天大树 作者宣言:认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧! realloc的细节前言一、realloc的原地扩容和异地扩容二、关于realloc是否可以缩容问题前…

快速搭建本地服务器

一、anywhere 1、npm install anywhere -g 2、打开位于文件夹下的终端页,输入anywhere 9999 9999这里是设置端口号,端口号自行设置,也可以不输入xxx会默认8080端口号 二、http-server 1、npm install http-server -g 2、打开位于文件夹下…

[golang]Go语言从入门到实践-反射

反射三定律: 1.变量---->反射变量 2.变量---->反射变量---->接口 3.变量----->(通过取地址&)反射变量---->修改变量的值 反射的类型和种类: 切片、集合、结构体、指针、函数与反射...... 总结: 内置包函数 reflect 的…

darknet测试yolo

原文链接:https://wangguo.site/posts/38432.html YOLO: Real-Time Object Detection 1、下载yolo权重文件 mkdir model #新建文件夹放权重文件 cd model wget https://pjreddie.com/media/files/yolov3.weights2、测试图片 执行命令 ./darknet detect cfg/yolo…

(socket编程实验中遇到的问题)connect error no route to host

在编写网络编程的时候遇到了这个问题connect error no route to host socket编程(服务端与客户端) 上网一搜全是: 两台机器进行socket通信时,可能在连接时出现错误: connect error: No route to host(errno:113) 出…

产品新人如何培养产品思维?

什么是产品思维?其实很难定义,不同人有不同的定义。有的人定义为以用户为中心打磨一个完美体验的产品;有的定义为从需求调研到需求上线各个步骤需要思考的点,等等。本文想讨论的产品思维是:怎么去发现问题,…

23.3.6打卡 AtCoder Beginner Contest 277 A~D

E题最短路有点生疏了先不写, 之后再补 A 题意 给出一个排列和X 问X在排列中出现的下标是多少 代码 void solve() {cin>>n>>m;for(ll i1;i<n;i) {cin>>arr[i];if(arr[i]m) ansi;}cout<<ans<<endl;return; }B题 题意 这机翻翻译的挺正确的…

5.3中断系统中的设备树——中断号的演变与irq_domain

通过上一节我们知道&#xff0c;在内核中有一个irq_desc数组&#xff0c;数组里面的每一项对应一个中断&#xff0c;数组的下标就是对应中断的虚拟中断号&#xff08;virq&#xff09;。 假设只有一个中断控制器&#xff0c;有32个中断&#xff0c;那么中断和irq_desc数组可以…

654. 最大二叉树

题目 leetcode题目地址 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。 递归地在最大值 左边 的 子数组前缀上 构建左子树。 递归地在最大值 右边 的 子数组后缀上 构建右子树。 返…

项目黑马面面-学科列表-增删改查

查 1.布局2.定义api3.导入api4.进入页面就调用api5.获取数据6.存储并渲染7.与分页建立关联a.请求参数值要与分页组件绑定b.total值存储并绑定到分页组件c.页码改变与页容量改变都要请求api1.布局 <template><div><el-card><el-form :inline"true&q…

C语言例程:猜数字游戏

猜数字游戏 实现一个简单的猜数字游戏&#xff0c;学习 while 循环语句的用法。 实例解析 while 循环语句 while 语句的一般形式为&#xff1a; while(表达式)语句; 其中表达式是循环条件&#xff0c;语句为循环体。 while 语句的语义是&#xff1a;计算表达式的值&#xf…

Vue使用ElemenUI对table的指定列进行合算

前言 最近有一个想法&#xff0c;就是记录自己花销的时候&#xff0c;table中有一项内容是花销的金额。然后想在table的底部有一项内容是该金额的总计。 然后我就顺着elemetui的table组件寻找相关的demo&#xff0c;还真发现了一个这样的demo。 对于这个demo&#xff0c;官方…

嵌入式 Linux进程之间的通信

目录 1、Linux进程间的通信继承 2、Linux进程之间的通信种类 3、管道 3.1 管道概述 3.2 管道文件 3.3 管道特点 3.4 通信框架 3.5 对管道文件进行操作 4、标准流管道 5、无名管道 PIPE 5.1 无名管道特点 5.2 创建管道函数 6、有名管道&#xff08;FIFO&#x…

FPGA lattice 深力科LCMXO3LF-2100C-5BG324I拥有很强的灵活性和适应性可编程内核的FPGA 值得期待

FPGA lattice 深力科LCMXO3LF-2100C-5BG324I MachXO3系列 拥有很强的灵活性和适应性可编程内核的FPGA 值得期待 &#xff0c;FPGA 现场可编程逻辑器件,小尺寸&#xff0c;高性能&#xff01;在工业领域&#xff0c;它可以用于网络控制器&#xff0c;PLC,网络边缘计算&#xff0…

kubernetes--分析容器系统调用:Sysdig

目录 Sysdig介绍&#xff1a; sysdig工作流程 安装Sysdig sysdig常用参数&#xff1a; sysdig过滤&#xff1a; sysdig之Chisels&#xff08;工具箱&#xff09;&#xff1a; 其他常用命令 Sysdig介绍&#xff1a; Sysdig是一个非常强大的系统监控分析和故障排查工具。汇…