rust: Composite Pattern

项目结构:

rust 复制代码
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Component_Code.rs
//! 物料编码值对象
//!
//! 职责单一:规范化承载物料/组件编码,并提供编码相关的查询语义。
 
/// 物料编码(构造时自动规范化:去除首尾空白并转为大写)
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComponentCode {
    /// 规范化之后的编码文本
    value: String,
}
 
impl ComponentCode {
    /// 构造一个物料编码
    ///
    /// # 参数
    /// - `raw`:原始编码文本(大小写与空白均会被规范化)
    pub fn new(raw: &str) -> Self {
        Self {
            // 去除首尾空白并统一转为大写,保证编码比较的稳定性
            value: raw.trim().to_uppercase(),
        }
    }
 
    /// 以字符串切片形式返回编码内容
    pub fn as_str(&self) -> &str {
        &self.value
    }
 
    /// 判断是否与给定编码完全相同(忽略大小写与首尾空白)
    ///
    /// # 参数
    /// - `other`:待比较的编码文本
    pub fn equals(&self, other: &str) -> bool {
        self.value.eq_ignore_ascii_case(other.trim())
    }
 
    /// 判断编码中是否包含给定关键字(忽略大小写)
    ///
    /// # 参数
    /// - `keyword`:搜索关键字
    pub fn contains(&self, keyword: &str) -> bool {
        // 双方统一小写后做子串匹配
        self.value
            .to_lowercase()
            .contains(&keyword.trim().to_lowercase())
    }
 
    /// 返回编码的分类前缀
    ///
    /// 约定形如 `"MAT-GOLD-001"` 的编码以第一个 `-` 之前的片段作为分类,
    /// 若不含分隔符则整段即分类。
    pub fn prefix(&self) -> &str {
        match self.value.split_once('-') {
            // 取第一个分隔符之前的部分
            Some((head, _)) => head,
            // 无分隔符时整段即为分类
            None => &self.value,
        }
    }
 
    /// 判断编码是否为空字符串
    pub fn is_empty(&self) -> bool {
        self.value.is_empty()
    }
}
 
/// 支持 `to_string()` 与 `format!("{}", ...)` 输出编码原文
impl std::fmt::Display for ComponentCode {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(formatter, "{}", self.value)
    }
}
 
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Money.rs
//! 货币金额值对象
//!
//! 职责单一:以「分」为最小单位精确表示金额。
//!
//! 【为什么不用 f64】
//! 浮点数无法精确表示 0.1 元这类十进制小数,多层级递归求和后会累积明显误差。
//! 组合模式的核心操作恰恰是"沿树逐层累加",因此必须使用整数分作为存储单位。
 
use std::iter::Sum;
use std::ops::Add;
 
/// 货币金额(内部以「分」为单位存储)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Money {
    /// 金额数值,单位为「分」
    cents: i64,
}
 
impl Money {
    /// 金额零值常量
    pub const ZERO: Money = Money { cents: 0 };
 
    /// 以「分」为单位构造金额
    ///
    /// # 参数
    /// - `cents`:以分为单位的整数金额
    pub fn from_cents(cents: i64) -> Self {
        // 直接以整数分初始化
        Self { cents }
    }
 
    /// 以「元」为单位构造金额(四舍五入到分)
    ///
    /// # 参数
    /// - `yuan`:以元为单位的浮点金额
    pub fn from_yuan(yuan: f64) -> Self {
        // 先放大 100 倍,再四舍五入,最后转为整数分
        Self {
            cents: (yuan * 100.0).round() as i64,
        }
    }
 
    /// 返回以「分」表示的金额
    pub fn cents(&self) -> i64 {
        self.cents
    }
 
    /// 返回以「元」表示的金额(浮点近似值,仅用于展示与统计)
    pub fn yuan(&self) -> f64 {
        self.cents as f64 / 100.0
    }
 
    /// 判断是否为零金额
    pub fn is_zero(&self) -> bool {
        self.cents == 0
    }
 
    /// 按整数倍数放大金额
    ///
    /// # 参数
    /// - `factor`:放大倍数(例如件数、次数)
    pub fn multiply(&self, factor: i64) -> Self {
        // 整数乘法,结果精确无误差
        Self {
            cents: self.cents * factor,
        }
    }
 
    /// 计算当前金额占总额的比例(0.0 ~ 1.0)
    ///
    /// # 参数
    /// - `total`:用于比较的总额
    ///
    /// # 返回
    /// 占比;当总额为 0 时返回 0.0,避免除零
    pub fn ratio_to(&self, total: Money) -> f64 {
        if total.cents == 0 {
            // 总额为零的兜底处理,避免产生 NaN
            return 0.0;
        }
        self.cents as f64 / total.cents as f64
    }
}
 
/// 支持 `+` 运算,便于沿树累加
impl Add for Money {
    type Output = Money;
 
    fn add(self, other: Money) -> Money {
        Money {
            cents: self.cents + other.cents,
        }
    }
}
 
/// 支持迭代器 `sum()`,使组合体的聚合表达为一行
impl Sum for Money {
    fn sum<I: Iterator<Item = Money>>(iter: I) -> Money {
        // 以零值为初始值逐项累加
        iter.fold(Money::ZERO, |accumulated, item| accumulated + item)
    }
}
 
/// 支持 `to_string()` 与 `format!("{}", ...)` 输出人民币格式
impl std::fmt::Display for Money {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // 整数部分与小数部分分别取绝对值输出,正确处理负数金额
        write!(
            formatter,
            "¥{}.{:02}",
            self.cents / 100,
            (self.cents % 100).abs()
        )
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Weight.rs
//! 重量值对象
//!
//! 职责单一:以「毫克」为最小单位精确表示贵金属/宝石重量。
//!
//! 与金额同理,重量也会沿产品结构树逐层累加,
//! 因此同样采用整数存储以避免浮点误差。
 
use crate::domain::Money;
use std::iter::Sum;
use std::ops::Add;
 
/// 重量(内部以「毫克」为单位存储)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Weight {
    /// 重量数值,单位为「毫克」
    milligrams: i64,
}
 
impl Weight {
    /// 重量零值常量
    pub const ZERO: Weight = Weight { milligrams: 0 };
 
    /// 以「毫克」为单位构造重量
    ///
    /// # 参数
    /// - `milligrams`:以毫克为单位的整数重量
    pub fn from_milligrams(milligrams: i64) -> Self {
        // 直接以整数毫克初始化
        Self { milligrams }
    }
 
    /// 以「克」为单位构造重量(四舍五入到毫克)
    ///
    /// # 参数
    /// - `grams`:以克为单位的浮点重量
    pub fn from_grams(grams: f64) -> Self {
        // 先放大 1000 倍,再四舍五入,最后转为整数毫克
        Self {
            milligrams: (grams * 1000.0).round() as i64,
        }
    }
 
    /// 返回以「毫克」表示的重量
    pub fn milligrams(&self) -> i64 {
        self.milligrams
    }
 
    /// 返回以「克」表示的重量(浮点近似值,仅用于展示)
    pub fn grams(&self) -> f64 {
        self.milligrams as f64 / 1000.0
    }
 
    /// 判断是否为零重量
    ///
    /// 服务类组件(如电镀、镶嵌工时)重量恒为零,可借此识别。
    pub fn is_zero(&self) -> bool {
        self.milligrams == 0
    }
 
    /// 按整数倍数放大重量
    ///
    /// # 参数
    /// - `factor`:放大倍数(例如件数)
    pub fn multiply(&self, factor: i64) -> Self {
        // 整数乘法,结果精确无误差
        Self {
            milligrams: self.milligrams * factor,
        }
    }
 
    /// 按「每克单价」计算对应金额
    ///
    /// # 参数
    /// - `price_per_gram`:每克单价
    ///
    /// # 返回
    /// 该重量对应的金额(四舍五入到分)
    pub fn cost_at(&self, price_per_gram: Money) -> Money {
        // 中间结果使用 i128,避免"毫克 × 分"在高价材料场景下溢出 i64
        let numerator: i128 = self.milligrams as i128 * price_per_gram.cents() as i128;
        // 加 500 后再整除 1000,实现四舍五入到分
        let rounded_cents: i128 = (numerator + 500) / 1000;
        Money::from_cents(rounded_cents as i64)
    }
}
 
/// 支持 `+` 运算,便于沿树累加
impl Add for Weight {
    type Output = Weight;
 
    fn add(self, other: Weight) -> Weight {
        Weight {
            milligrams: self.milligrams + other.milligrams,
        }
    }
}
 
/// 支持迭代器 `sum()`,使组合体的聚合表达为一行
impl Sum for Weight {
    fn sum<I: Iterator<Item = Weight>>(iter: I) -> Weight {
        // 以零值为初始值逐项累加
        iter.fold(Weight::ZERO, |accumulated, item| accumulated + item)
    }
}
 
/// 支持 `to_string()` 与 `format!("{}", ...)` 输出克重格式
impl std::fmt::Display for Weight {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // 固定保留三位小数,符合贵金属称重行业习惯
        write!(formatter, "{:.3} g", self.grams())
    }
}
 
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Component_Spec.rs
//! 组件规格描述
//!
//! 职责单一:以纯数据描述一棵"待构建"的组件树。
//!
//! 【与组件树的区别】
//! `ComponentSpec` 是**数据**(可序列化、可来自配置文件或数据库),
//! 组件树是**对象**(可执行递归聚合)。
//! 由 `ProductBuilder` 负责把前者转换为后者。
//!
//! 新增一种组件类型时,只需在枚举中追加一个变体,
//! 并在 `ProductBuilder` 中补一个分支即可。
 
/// 组件规格
#[derive(Debug, Clone)]
pub enum ComponentSpec {
    /// 材料规格
    Material {
        /// 物料编码
        code: String,
        /// 物料名称
        name: String,
        /// 单件净重(克)
        unit_weight_grams: f64,
        /// 每克单价(元)
        price_per_gram_yuan: f64,
        /// 件数
        quantity: u32,
    },
    /// 工艺服务规格
    Service {
        /// 服务编码
        code: String,
        /// 服务名称
        name: String,
        /// 单次服务费(元)
        unit_price_yuan: f64,
        /// 服务次数
        times: u32,
    },
    /// 装配体规格(可递归嵌套子规格)
    Assembly {
        /// 装配体编码
        code: String,
        /// 装配体名称
        name: String,
        /// 子规格集合
        children: Vec<ComponentSpec>,
    },
}
 
impl ComponentSpec {
    /// 构造一个材料规格
    ///
    /// # 参数
    /// - `code`:物料编码
    /// - `name`:物料名称
    /// - `unit_weight_grams`:单件净重(克)
    /// - `price_per_gram_yuan`:每克单价(元)
    /// - `quantity`:件数
    pub fn material(
        code: &str,
        name: &str,
        unit_weight_grams: f64,
        price_per_gram_yuan: f64,
        quantity: u32,
    ) -> Self {
        ComponentSpec::Material {
            code: code.to_string(),
            name: name.to_string(),
            unit_weight_grams,
            price_per_gram_yuan,
            quantity,
        }
    }
 
    /// 构造一个工艺服务规格
    ///
    /// # 参数
    /// - `code`:服务编码
    /// - `name`:服务名称
    /// - `unit_price_yuan`:单次服务费(元)
    /// - `times`:服务次数
    pub fn service(code: &str, name: &str, unit_price_yuan: f64, times: u32) -> Self {
        ComponentSpec::Service {
            code: code.to_string(),
            name: name.to_string(),
            unit_price_yuan,
            times,
        }
    }
 
    /// 构造一个不含子规格的装配体规格
    ///
    /// # 参数
    /// - `code`:装配体编码
    /// - `name`:装配体名称
    pub fn assembly(code: &str, name: &str) -> Self {
        ComponentSpec::Assembly {
            code: code.to_string(),
            name: name.to_string(),
            // 初始为空,随后用 with_children 装配
            children: Vec::new(),
        }
    }
 
    /// 以链式风格装配子规格
    ///
    /// # 参数
    /// - `children`:子规格集合
    ///
    /// # 说明
    /// 仅对 [`ComponentSpec::Assembly`] 生效;
    /// 对材料或服务规格调用时子规格会被忽略(详见 [`Self::push_child`] 的返回值判断)。
    pub fn with_children(mut self, children: Vec<ComponentSpec>) -> Self {
        self.set_children(children);
        self
    }
 
    /// 追加一个子规格
    ///
    /// # 参数
    /// - `child`:子规格
    ///
    /// # 返回
    /// 成功追加返回 `true`;当前规格不是装配体时返回 `false` 且不做任何修改
    /// (显式返回结果,避免"静默丢弃"造成的隐蔽错误)
    pub fn push_child(&mut self, child: ComponentSpec) -> bool {
        match self {
            ComponentSpec::Assembly { children, .. } => {
                children.push(child);
                true
            }
            // 非装配体无法容纳子规格
            _ => false,
        }
    }
 
    /// 覆盖式设置子规格集合
    ///
    /// # 参数
    /// - `children`:新的子规格集合
    ///
    /// # 返回
    /// 当前规格不是装配体时静默忽略
    pub fn set_children(&mut self, children: Vec<ComponentSpec>) {
        if let ComponentSpec::Assembly {
            children: slot, ..
        } = self
        {
            *slot = children;
        }
    }
 
    /// 返回本规格的编码
    pub fn code(&self) -> &str {
        match self {
            ComponentSpec::Material { code, .. } => code,
            ComponentSpec::Service { code, .. } => code,
            ComponentSpec::Assembly { code, .. } => code,
        }
    }
 
    /// 返回本规格的显示名称
    pub fn display_name(&self) -> &str {
        match self {
            ComponentSpec::Material { name, .. } => name,
            ComponentSpec::Service { name, .. } => name,
            ComponentSpec::Assembly { name, .. } => name,
        }
    }
 
    /// 返回本规格的种类名称
    pub fn kind_name(&self) -> &'static str {
        match self {
            ComponentSpec::Material { .. } => "Material",
            ComponentSpec::Service { .. } => "Service",
            ComponentSpec::Assembly { .. } => "Assembly",
        }
    }
 
    /// 返回子规格切片(非装配体返回空切片)
    pub fn child_specs(&self) -> &[ComponentSpec] {
        match self {
            ComponentSpec::Assembly { children, .. } => children,
            // 材料与服务规格没有子规格
            _ => &[],
        }
    }
 
    /// 返回直接子规格数量
    pub fn child_count(&self) -> usize {
        self.child_specs().len()
    }
 
    /// 判断本规格是否为装配体
    pub fn is_assembly(&self) -> bool {
        matches!(self, ComponentSpec::Assembly { .. })
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : product_builder.rs
//! 组件构建器
//!
//! 职责单一:把 [`ComponentSpec`] 规格树递归转换为真实的组件树。
//!
//! 【本层是唯一的"类型登记点"】
//! 新增一种组件类型时,需要修改的地方只有两处:
//! 1. `ComponentSpec` 追加一个变体;
//! 2. 本文件 `build` 方法补一个 `match` 分支。
//!
//! 组件层、叶子层、组合层与分析层**均无需改动**,
//! 这正是把"构建知识"集中到单一工厂的价值。
 
use crate::component::{ComponentContainer, ProductComponent};
use crate::composite::ProductAssembly;
use crate::factory::ComponentSpec;
use crate::leaf::{MaterialItem, ServiceItem};
use std::collections::HashSet;
 
/// 构建期错误
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BuildError {
    /// 编码为空
    EmptyCode {
        /// 出错的规格名称,便于定位
        spec_name: String,
    },
    /// 编码重复
    DuplicateCode {
        /// 重复出现的编码
        code: String,
    },
}
 
/// 支持 `to_string()` 输出可读错误信息
impl std::fmt::Display for BuildError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            BuildError::EmptyCode { spec_name } => {
                write!(formatter, "规格「{}」的编码为空,无法构建组件", spec_name)
            }
            BuildError::DuplicateCode { code } => {
                write!(formatter, "编码「{}」在规格树中重复出现", code)
            }
        }
    }
}
 
/// 使错误类型可用于 `?` 与错误链
impl std::error::Error for BuildError {}
 
/// 组件构建器
pub struct ProductBuilder;
 
impl ProductBuilder {
    /// 依据规格**递归**构建组件树(不做校验)
    ///
    /// # 参数
    /// - `spec`:待构建的规格节点
    ///
    /// # 返回
    /// 装箱后的组件树根节点
    pub fn build(spec: &ComponentSpec) -> Box<dyn ProductComponent> {
        match spec {
            // 材料规格 -> 材料单件叶子
            ComponentSpec::Material {
                code,
                name,
                unit_weight_grams,
                price_per_gram_yuan,
                quantity,
            } => Box::new(MaterialItem::new(
                code,
                name,
                *unit_weight_grams,
                *price_per_gram_yuan,
                *quantity,
            )),
            // 服务规格 -> 服务项叶子
            ComponentSpec::Service {
                code,
                name,
                unit_price_yuan,
                times,
            } => Box::new(ServiceItem::new(code, name, *unit_price_yuan, *times)),
            // 装配体规格 -> 组合体,并递归装配每个子规格
            ComponentSpec::Assembly {
                code,
                name,
                children,
            } => {
                // 先创建空装配体
                let mut assembly: ProductAssembly = ProductAssembly::new(code, name);
                for child_spec in children {
                    // 递归构建子组件并挂载
                    assembly.add_child(Self::build(child_spec));
                }
                Box::new(assembly)
            }
        }
    }
 
    /// 依据规格递归构建组件树,并校验整棵规格树的编码合法
    ///
    /// # 校验规则
    /// 1. 任何节点的编码不得为空;
    /// 2. 全树编码必须唯一(BOM 的基本业务约束)。
    ///
    /// # 参数
    /// - `spec`:待构建的规格节点
    ///
    /// # 返回
    /// 构建成功的组件树;校验失败时返回 [`BuildError`]
    pub fn build_checked(spec: &ComponentSpec) -> Result<Box<dyn ProductComponent>, BuildError> {
        // 已出现过的规范化编码集合
        let mut seen_codes: HashSet<String> = HashSet::new();
        // 先递归校验,再真正构建
        Self::validate(spec, &mut seen_codes)?;
        Ok(Self::build(spec))
    }
 
    /// 递归校验规格树
    ///
    /// # 参数
    /// - `spec`:当前待校验的规格节点
    /// - `seen_codes`:跨层级共享的已见编码集合
    fn validate(
        spec: &ComponentSpec,
        seen_codes: &mut HashSet<String>,
    ) -> Result<(), BuildError> {
        // 规范化编码:去空白 + 转大写,与 ComponentCode 的规则保持一致
        let normalized_code: String = spec.code().trim().to_uppercase();
        // 规则一:编码非空
        if normalized_code.is_empty() {
            return Err(BuildError::EmptyCode {
                spec_name: spec.display_name().to_string(),
            });
        }
        // 规则二:编码全局唯一;insert 返回 false 说明此前已存在
        if !seen_codes.insert(normalized_code.clone()) {
            return Err(BuildError::DuplicateCode {
                code: normalized_code,
            });
        }
        // 递归校验全部子规格
        for child_spec in spec.child_specs() {
            Self::validate(child_spec, seen_codes)?;
        }
        Ok(())
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Material_Item.rs
//! 叶子实现 A:材料单件(MaterialItem)
//!
//! 职责单一:描述一件按「重量 × 每克单价」计价的实物料,
//! 例如 18K 金料、钻石、宝石、配链。
//!
//! 本类型**没有子节点**,因此所有聚合量都由自身字段直接算出。
 
use crate::component::{ProductComponent, NO_CHILDREN};
use crate::domain::{ComponentCode, Money, Weight};
 
/// 材料单件(Leaf)
#[derive(Debug)]
pub struct MaterialItem {
    /// 物料编码
    code: ComponentCode,
    /// 物料名称
    name: String,
    /// 单件净重(克)
    unit_weight: Weight,
    /// 每克单价
    price_per_gram: Money,
    /// 件数
    quantity: u32,
}
 
impl MaterialItem {
    /// 构造一件材料单件
    ///
    /// # 参数
    /// - `code`:物料编码原文
    /// - `name`:物料名称
    /// - `unit_weight_grams`:单件净重(克)
    /// - `price_per_gram_yuan`:每克单价(元)
    /// - `quantity`:件数
    pub fn new(
        code: &str,
        name: &str,
        unit_weight_grams: f64,
        price_per_gram_yuan: f64,
        quantity: u32,
    ) -> Self {
        Self {
            // 编码由值对象负责规范化
            code: ComponentCode::new(code),
            // 名称转为拥有所有权的字符串
            name: name.to_string(),
            // 克重转换为精确的毫克存储
            unit_weight: Weight::from_grams(unit_weight_grams),
            // 单价转换为精确的整数分存储
            price_per_gram: Money::from_yuan(price_per_gram_yuan),
            // 件数原样保存
            quantity,
        }
    }
 
    /// 返回单件净重
    pub fn unit_weight(&self) -> Weight {
        self.unit_weight
    }
 
    /// 返回每克单价
    pub fn price_per_gram(&self) -> Money {
        self.price_per_gram
    }
 
    /// 返回件数
    pub fn quantity(&self) -> u32 {
        self.quantity
    }
 
    /// 计算单件成本(单件净重 × 每克单价,四舍五入到分)
    pub fn unit_cost(&self) -> Money {
        // 先按克重算出单件金额,再乘以件数由调用方决定
        self.unit_weight.cost_at(self.price_per_gram)
    }
}
 
impl ProductComponent for MaterialItem {
    /// 返回物料编码
    fn code(&self) -> &ComponentCode {
        &self.code
    }
 
    /// 返回物料名称
    fn name(&self) -> &str {
        &self.name
    }
 
    /// 返回组件种类名称
    fn kind_name(&self) -> &'static str {
        "Material"
    }
 
    /// 叶子没有子节点,直接复用静态空切片
    fn children(&self) -> &[Box<dyn ProductComponent>] {
        NO_CHILDREN
    }
 
    /// 材料单件不是组合体
    fn is_composite(&self) -> bool {
        false
    }
 
    /// 累计重量 = 单件净重 × 件数
    fn total_weight(&self) -> Weight {
        self.unit_weight.multiply(self.quantity as i64)
    }
 
    /// 累计成本 = 单件成本 × 件数
    ///
    /// 先算单件金额再乘件数,保证"单价 × 件数"的行业计价习惯。
    fn total_cost(&self) -> Money {
        self.unit_cost().multiply(self.quantity as i64)
    }
 
    /// 计件数 = 件数
    fn piece_count(&self) -> u32 {
        self.quantity
    }
 
    /// 一行属性描述
    fn describe(&self) -> String {
        format!(
            "材料单件 | 编码 {} | 名称 {} | 单件净重 {} | 每克单价 {} | 件数 {} | 合计净重 {} | 合计成本 {}",
            self.code,
            self.name,
            self.unit_weight,
            self.price_per_gram,
            self.quantity,
            self.total_weight(),
            self.total_cost()
        )
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Service_Item.rs
//! 叶子实现 B:服务项(ServiceItem)
//!
//! 职责单一:描述一项按「次数 × 单次服务费」计价的工艺服务,
//! 例如电镀、镶嵌工时、抛光、激光刻字。
//!
//! 【设计意图】
//! 本类型刻意与 `MaterialItem` 在语义上不同:
//! **它没有重量**(`total_weight()` 恒为零),只有服务费。
//! 组合体却可以用完全相同的方式对待它们 ------
//! 这正是组合模式"统一对待叶子与组合"的价值体现。
 
use crate::component::{ProductComponent, NO_CHILDREN};
use crate::domain::{ComponentCode, Money, Weight};
 
/// 服务项(Leaf)
#[derive(Debug)]
pub struct ServiceItem {
    /// 服务编码
    code: ComponentCode,
    /// 服务名称
    name: String,
    /// 单次服务费
    unit_price: Money,
    /// 服务次数
    times: u32,
}
 
impl ServiceItem {
    /// 构造一个服务项
    ///
    /// # 参数
    /// - `code`:服务编码原文
    /// - `name`:服务名称
    /// - `unit_price_yuan`:单次服务费(元)
    /// - `times`:服务次数
    pub fn new(code: &str, name: &str, unit_price_yuan: f64, times: u32) -> Self {
        Self {
            // 编码由值对象负责规范化
            code: ComponentCode::new(code),
            // 名称转为拥有所有权的字符串
            name: name.to_string(),
            // 单价转换为精确的整数分存储
            unit_price: Money::from_yuan(unit_price_yuan),
            // 次数原样保存
            times,
        }
    }
 
    /// 返回单次服务费
    pub fn unit_price(&self) -> Money {
        self.unit_price
    }
 
    /// 返回服务次数
    pub fn times(&self) -> u32 {
        self.times
    }
}
 
impl ProductComponent for ServiceItem {
    /// 返回服务编码
    fn code(&self) -> &ComponentCode {
        &self.code
    }
 
    /// 返回服务名称
    fn name(&self) -> &str {
        &self.name
    }
 
    /// 返回组件种类名称
    fn kind_name(&self) -> &'static str {
        "Service"
    }
 
    /// 叶子没有子节点,直接复用静态空切片
    fn children(&self) -> &[Box<dyn ProductComponent>] {
        NO_CHILDREN
    }
 
    /// 服务项不是组合体
    fn is_composite(&self) -> bool {
        false
    }
 
    /// 服务项不计重量,恒为零
    fn total_weight(&self) -> Weight {
        Weight::ZERO
    }
 
    /// 累计成本 = 单次服务费 × 服务次数
    fn total_cost(&self) -> Money {
        self.unit_price.multiply(self.times as i64)
    }
 
    /// 计件数 = 服务次数
    fn piece_count(&self) -> u32 {
        self.times
    }
 
    /// 一行属性描述
    fn describe(&self) -> String {
        format!(
            "工艺服务 | 编码 {} | 名称 {} | 单次服务费 {} | 次数 {} | 合计成本 {} | 不计重量",
            self.code, self.name, self.unit_price, self.times, self.total_cost()
        )
    }
}
rust 复制代码
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : aggregate_analysis.rs
//! 聚合分析
//!
//! 职责单一:对组件树做分组统计与结构度量。
//!
//! 本模块完全建立在组件抽象之上,不依赖任何具体叶子或组合类型,
//! 因此**未来新增任何叶子类型都会被自动纳入统计**,无需修改本文件。
 
use crate::component::{collect_composites, collect_leaves, ProductComponent};
use crate::domain::{Money, Weight};
use std::collections::BTreeMap;
 
/// 成本构成条目(按编码前缀分类)
#[derive(Debug, Clone)]
pub struct CostCompositionEntry {
    /// 分类键(物料编码的第一个 `-` 之前片段,如 "MAT")
    pub category: String,
    /// 分类的中文显示名
    pub category_label: String,
    /// 该分类的累计成本
    pub cost: Money,
    /// 该分类的累计净重
    pub weight: Weight,
    /// 该分类下的叶子节点数量
    pub leaf_count: usize,
    /// 该分类的累计件数
    pub pieces: u32,
    /// 该分类成本占总额的百分比(0.0 ~ 100.0)
    pub share_percent: f64,
}
 
/// 结构统计结果
#[derive(Debug, Clone)]
pub struct StructureStatistics {
    /// 节点总数(含组合体)
    pub node_count: usize,
    /// 叶子节点数量
    pub leaf_count: usize,
    /// 组合体节点数量
    pub composite_count: usize,
    /// 树的最大深度(根为 0)
    pub max_depth: usize,
    /// 合计净重
    pub total_weight: Weight,
    /// 合计成本
    pub total_cost: Money,
    /// 合计件数
    pub total_pieces: u32,
}
 
/// 聚合分析器
pub struct AggregateAnalysis;
 
impl AggregateAnalysis {
    /// 计算整棵树的结构统计指标
    ///
    /// # 参数
    /// - `root`:树的根节点
    pub fn structure_statistics(root: &dyn ProductComponent) -> StructureStatistics {
        StructureStatistics {
            // 节点总数由组件层默认实现递归得出
            node_count: root.node_count(),
            // 叶子数量由组件层默认实现递归得出
            leaf_count: root.leaf_count(),
            // 组合体数量借助遍历工具统计
            composite_count: collect_composites(root).len(),
            // 最大深度由组件层默认实现递归得出
            max_depth: root.max_depth(),
            // 聚合量直接取自根节点的递归求和结果
            total_weight: root.total_weight(),
            total_cost: root.total_cost(),
            total_pieces: root.piece_count(),
        }
    }
 
    /// 按"叶子节点编码前缀"分组,统计成本构成
    ///
    /// # 参数
    /// - `root`:树的根节点
    ///
    /// # 返回
    /// 按成本从高到低排序的分类条目
    pub fn cost_composition(root: &dyn ProductComponent) -> Vec<CostCompositionEntry> {
        // 取出全部叶子节点(仅叶子承载实际计价值)
        let leaves = collect_leaves(root);
        // 总成本用于计算占比
        let total_cost: Money = root.total_cost();
 
        // 使用 BTreeMap 保证分类顺序稳定可复现(按分类键排序)
        let mut buckets: BTreeMap<String, CostCompositionEntry> = BTreeMap::new();
        for visit in &leaves {
            // 分类键取自编码前缀
            let category: String = visit.node.code().prefix().to_string();
            // 取出或初始化该分类的累计条目
            let entry: &mut CostCompositionEntry =
                buckets
                    .entry(category.clone())
                    .or_insert_with(|| CostCompositionEntry {
                        category: category.clone(),
                        category_label: category_label(&category),
                        cost: Money::ZERO,
                        weight: Weight::ZERO,
                        leaf_count: 0,
                        pieces: 0,
                        share_percent: 0.0,
                    });
            // 逐项累加成本、重量、叶子数与件数
            entry.cost = entry.cost + visit.node.total_cost();
            entry.weight = entry.weight + visit.node.total_weight();
            entry.leaf_count += 1;
            entry.pieces += visit.node.piece_count();
        }
 
        // 转为向量并回填占比
        let mut entries: Vec<CostCompositionEntry> = buckets.into_values().collect();
        for entry in entries.iter_mut() {
            // 占比以百分比表示
            entry.share_percent = entry.cost.ratio_to(total_cost) * 100.0;
        }
        // 按成本降序排列,便于直接展示主要成本来源
        entries.sort_by(|left, right| right.cost.cmp(&left.cost));
        entries
    }
 
    /// 计算平均单件成本
    ///
    /// # 参数
    /// - `root`:树的根节点
    ///
    /// # 返回
    /// 平均单件成本;当总件数为 0 时返回零金额
    pub fn average_piece_cost(root: &dyn ProductComponent) -> Money {
        // 总件数
        let pieces: u32 = root.piece_count();
        if pieces == 0 {
            // 除零保护
            return Money::ZERO;
        }
        // 整数分除以件数后截断,保持金额精度可控
        Money::from_cents(root.total_cost().cents() / pieces as i64)
    }
 
    /// 计算材料成本与工艺服务成本的比值
    ///
    /// # 参数
    /// - `root`:树的根节点
    ///
    /// # 返回
    /// `(材料成本, 工艺服务成本)`
    pub fn material_versus_service_cost(root: &dyn ProductComponent) -> (Money, Money) {
        let mut material_cost: Money = Money::ZERO;
        let mut service_cost: Money = Money::ZERO;
        // 遍历全部叶子,按种类归类
        for visit in collect_leaves(root) {
            match visit.node.kind_name() {
                // 材料类
                "Material" => material_cost = material_cost + visit.node.total_cost(),
                // 服务类
                "Service" => service_cost = service_cost + visit.node.total_cost(),
                // 其它自定义叶子类型统一归入材料侧,保证兼容未来扩展
                _ => material_cost = material_cost + visit.node.total_cost(),
            }
        }
        (material_cost, service_cost)
    }
}
 
/// 把编码前缀翻译为中文分类名
///
/// # 参数
/// - `category`:编码前缀
///
/// # 说明
/// 未登记的前缀统一显示为 `其它(前缀)`,
/// 因此新增前缀分类**不需要修改本函数的调用方**。
fn category_label(category: &str) -> String {
    match category {
        // 材料类
        "MAT" => String::from("材料"),
        // 工艺服务类
        "SVC" => String::from("工艺服务"),
        // 装配体类
        "ASM" => String::from("装配体"),
        // 包装类
        "PKG" => String::from("包装"),
        // 未登记前缀的兜底展示
        other => format!("其它({})", other),
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : search_analysis.rs
//! 查找分析
//!
//! 职责单一:在组件树中按编码或名称定位节点。
//!
//! 【为什么查找不放在 Component 接口里】
//! 若把 `find_by_code` 写成 trait 默认方法,实现体内需要 `Some(self)`,
//! 即把 `&Self` 转换为 `&dyn ProductComponent`。
//! 但 trait 默认方法中的 `Self` 是 `?Sized` 的,无法完成这一强制转换
//! (只有 `Self: Sized` 才允许 unsize coercion)。
//! 因此查找逻辑本就应当外置 ------ 借助通用遍历器实现,既绕开了语言限制,
//! 又让"新增一种查找策略"不再触碰核心接口。
 
use crate::component::{depth_first, ProductComponent};
 
/// 查找命中项
#[derive(Debug, Clone)]
pub struct SearchHit<'a> {
    /// 命中的节点引用
    pub node: &'a dyn ProductComponent,
    /// 该节点在树中的深度(根为 0)
    pub depth: usize,
    /// 从根到该节点的名称路径(含首尾两端)
    pub path: Vec<String>,
}
 
impl<'a> SearchHit<'a> {
    /// 把路径渲染为 `A / B / C` 形式
    pub fn path_text(&self) -> String {
        self.path.join(" / ")
    }
 
    /// 返回命中节点的编码
    pub fn code_text(&self) -> String {
        self.node.code().to_string()
    }
}
 
/// 查找分析器
pub struct SearchAnalysis;
 
impl SearchAnalysis {
    /// 按编码精确查找,返回**前序遍历顺序**下的首个命中
    ///
    /// # 参数
    /// - `root`:树的根节点
    /// - `code`:目标编码(忽略大小写与首尾空白)
    pub fn find_by_code<'a>(root: &'a dyn ProductComponent, code: &str) -> Option<SearchHit<'a>> {
        depth_first(root)
            .find(|visit| visit.node.code().equals(code))
            .map(|visit| SearchHit {
                node: visit.node,
                depth: visit.depth,
                path: visit.path,
            })
    }
 
    /// 按编码查找并仅返回名称路径
    ///
    /// # 参数
    /// - `root`:树的根节点
    /// - `code`:目标编码
    pub fn find_path<'a>(root: &'a dyn ProductComponent, code: &str) -> Option<Vec<String>> {
        // 复用精确查找,取路径字段
        Self::find_by_code(root, code).map(|hit| hit.path)
    }
 
    /// 按关键字模糊查找全部命中(同时匹配名称与编码,忽略大小写)
    ///
    /// # 参数
    /// - `root`:树的根节点
    /// - `keyword`:搜索关键字
    pub fn find_all_by_keyword<'a>(
        root: &'a dyn ProductComponent,
        keyword: &str,
    ) -> Vec<SearchHit<'a>> {
        // 统一去除首尾空白,避免空白关键字匹配到全部节点
        let trimmed_keyword: &str = keyword.trim();
        if trimmed_keyword.is_empty() {
            // 空关键字直接返回空结果
            return Vec::new();
        }
        depth_first(root)
            .filter(|visit| {
                // 名称命中或编码命中均视为匹配
                visit.node.name().contains(trimmed_keyword)
                    || visit.node.code().contains(trimmed_keyword)
            })
            .map(|visit| SearchHit {
                node: visit.node,
                depth: visit.depth,
                path: visit.path,
            })
            .collect()
    }
 
    /// 按组件种类名称查找全部命中
    ///
    /// # 参数
    /// - `root`:树的根节点
    /// - `kind_name`:组件种类名称(如 "Material" / "Service" / "Assembly")
    pub fn find_all_by_kind<'a>(
        root: &'a dyn ProductComponent,
        kind_name: &str,
    ) -> Vec<SearchHit<'a>> {
        depth_first(root)
            .filter(|visit| visit.node.kind_name() == kind_name)
            .map(|visit| SearchHit {
                node: visit.node,
                depth: visit.depth,
                path: visit.path,
            })
            .collect()
    }
}
 
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : tree_analysis.rs
//! 树形渲染分析
//!
//! 职责单一:把组件树渲染为带连接线的可读文本。
//!
//! 本分析需要"当前节点是否为其父的最后一个子节点"这一信息,
//! 而通用遍历器只提供深度与路径,因此这里采用自有的递归渲染算法。
 
use crate::component::ProductComponent;
 
/// 树形渲染分析器
pub struct TreeAnalysis;
 
impl TreeAnalysis {
    /// 渲染纯结构树
    ///
    /// 每个节点仅展示 `名称 [种类] 编码`,适合快速查看层级。
    ///
    /// # 参数
    /// - `root`:树的根节点
    pub fn render_structure(root: &dyn ProductComponent) -> String {
        // 复用内部渲染函数,关闭指标展示
        let mut buffer: String = String::new();
        render_node(root, "", true, true, &mut buffer, false);
        buffer
    }
 
    /// 渲染带聚合指标的树
    ///
    /// 每个节点展示编码、名称、种类、累计重量、累计成本与累计件数。
    ///
    /// # 参数
    /// - `root`:树的根节点
    pub fn render_with_metrics(root: &dyn ProductComponent) -> String {
        // 复用内部渲染函数,开启指标展示
        let mut buffer: String = String::new();
        render_node(root, "", true, true, &mut buffer, true);
        buffer
    }
 
    /// 渲染指定深度的子树(裁剪过深的层级)
    ///
    /// # 参数
    /// - `root`:树的根节点
    /// - `max_depth`:允许展示的最大深度(根为 0)
    pub fn render_truncated(root: &dyn ProductComponent, max_depth: usize) -> String {
        let mut buffer: String = String::new();
        render_node_truncated(root, "", true, true, &mut buffer, max_depth);
        buffer
    }
}
 
/// 内部递归渲染函数
///
/// # 参数
/// - `node`:当前节点
/// - `prefix`:当前节点左侧已累积的前缀(由祖先的"是否为末子"决定)
/// - `is_last`:当前节点是否为其父的最后一个子节点
/// - `is_root`:当前节点是否为整棵树的根
/// - `buffer`:输出缓冲区
/// - `with_metrics`:是否展示聚合指标
fn render_node(
    node: &dyn ProductComponent,
    prefix: &str,
    is_last: bool,
    is_root: bool,
    buffer: &mut String,
    with_metrics: bool,
) {
    // 节点自身的展示文本
    let label: String = if with_metrics {
        node.summary()
    } else {
        format!("{} [{}] {}", node.name(), node.kind_name(), node.code())
    };
 
    if is_root {
        // 根节点不加连接线
        buffer.push_str(&format!("{}\n", label));
    } else {
        // 末子使用 └──,非末子使用 ├──
        let connector: &str = if is_last { "└── " } else { "├── " };
        buffer.push_str(&format!("{}{}{}\n", prefix, connector, label));
    }
 
    // 取出直接子节点
    let children = node.children();
    let child_total: usize = children.len();
    for (index, child) in children.iter().enumerate() {
        // 该子节点是否为其父的末子
        let child_is_last: bool = index + 1 == child_total;
        // 子节点的前缀 = 当前前缀 + (当前为末子 ? 四个空格 : 竖线加空格)
        let child_prefix: String = if is_root {
            // 根节点的子节点前缀为空
            String::new()
        } else {
            format!("{}{}", prefix, if is_last { "    " } else { "│   " })
        };
        render_node(
            &**child,
            &child_prefix,
            child_is_last,
            false,
            buffer,
            with_metrics,
        );
    }
}
 
/// 内部递归渲染函数(带深度裁剪)
///
/// # 参数
/// - `node`:当前节点
/// - `prefix`:当前节点左侧前缀
/// - `is_last`:当前节点是否为其父的末子
/// - `is_root`:当前节点是否为整棵树的根
/// - `buffer`:输出缓冲区
/// - `remaining_depth`:剩余可展示的深度
fn render_node_truncated(
    node: &dyn ProductComponent,
    prefix: &str,
    is_last: bool,
    is_root: bool,
    buffer: &mut String,
    remaining_depth: usize,
) {
    let label: String = format!("{} [{}] {}", node.name(), node.kind_name(), node.code());
    if is_root {
        buffer.push_str(&format!("{}\n", label));
    } else {
        let connector: &str = if is_last { "└── " } else { "├── " };
        buffer.push_str(&format!("{}{}{}\n", prefix, connector, label));
    }
 
    // 达到深度上限时,仅提示剩余子节点数量
    if remaining_depth == 0 {
        let hidden: usize = node.children().len();
        if hidden > 0 {
            let child_prefix: String = if is_root {
                String::new()
            } else {
                format!("{}{}", prefix, if is_last { "    " } else { "│   " })
            };
            buffer.push_str(&format!("{}└── ...... 省略 {} 个子节点\n", child_prefix, hidden));
        }
        return;
    }
 
    let children = node.children();
    let child_total: usize = children.len();
    for (index, child) in children.iter().enumerate() {
        let child_is_last: bool = index + 1 == child_total;
        let child_prefix: String = if is_root {
            String::new()
        } else {
            format!("{}{}", prefix, if is_last { "    " } else { "│   " })
        };
        render_node_truncated(
            &**child,
            &child_prefix,
            child_is_last,
            false,
            buffer,
            remaining_depth - 1,
        );
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : bom_report.rs
//! BOM 报表生成
//!
//! 职责单一:把聚合分析、树形渲染与查找分析的结果编排为可读报表。
//!
//! 本模块只消费 `&dyn ProductComponent`,
//! 因此无论传入的是叶子、组合体还是自定义组件,都能生成同样的报表。
 
use crate::analysis::{AggregateAnalysis, SearchAnalysis, StructureStatistics, TreeAnalysis};
use crate::component::ProductComponent;
use crate::domain::Money;
 
/// 报表中使用的横向分隔线
const SEPARATOR: &str = "--------------------------------------------------------------------------";
 
/// BOM 报表生成器
pub struct BomReport;
 
impl BomReport {
    /// 生成完整的 BOM 报表文档
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    /// - `search_keywords`:需要附带定位的关键字列表
    pub fn generate(root: &dyn ProductComponent, search_keywords: &[&str]) -> String {
        let mut sections: Vec<String> = Vec::new();
        // 标题区
        sections.push(Self::document_header(root));
        // 一、结构总览
        sections.push(Self::structure_overview(root));
        // 二、产品结构树
        sections.push(Self::structure_tree(root));
        // 三、成本构成分析
        sections.push(Self::cost_composition_table(root));
        // 四、材料与服务成本对比
        sections.push(Self::material_versus_service(root));
        // 五、关键节点定位
        sections.push(Self::search_footprint(root, search_keywords));
        // 各区之间以分隔线连接
        sections.join(&format!("\n{}\n\n", SEPARATOR))
    }
 
    /// 生成文档头
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    pub fn document_header(root: &dyn ProductComponent) -> String {
        format!(
            "========================= 产品结构 BOM 报表 =========================\n根节点:{}({})\n编码:{}",
            root.name(),
            root.kind_name(),
            root.code()
        )
    }
 
    /// 生成「一、结构总览」区
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    pub fn structure_overview(root: &dyn ProductComponent) -> String {
        // 取得全部结构指标
        let statistics: StructureStatistics = AggregateAnalysis::structure_statistics(root);
        // 平均单件成本
        let average_cost: Money = AggregateAnalysis::average_piece_cost(root);
        let mut lines: Vec<String> = Vec::new();
        lines.push(String::from("【一、结构总览】"));
        lines.push(format!("  节点总数(含装配体):{}", statistics.node_count));
        lines.push(format!("  叶子节点数量:{}", statistics.leaf_count));
        lines.push(format!("  装配体数量:{}", statistics.composite_count));
        lines.push(format!("  最大层级深度:{}", statistics.max_depth));
        lines.push(format!("  合计净重:{}", statistics.total_weight));
        lines.push(format!("  合计成本:{}", statistics.total_cost));
        lines.push(format!("  合计件数:{}", statistics.total_pieces));
        lines.push(format!("  平均单件成本:{}", average_cost));
        lines.join("\n")
    }
 
    /// 生成「二、产品结构树」区
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    pub fn structure_tree(root: &dyn ProductComponent) -> String {
        let mut lines: Vec<String> = Vec::new();
        lines.push(String::from("【二、产品结构树】"));
        // 逐行缩进展示随指标的结构树
        for tree_line in TreeAnalysis::render_with_metrics(root).lines() {
            lines.push(format!("  {}", tree_line));
        }
        lines.join("\n")
    }
 
    /// 生成「三、成本构成分析」区
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    pub fn cost_composition_table(root: &dyn ProductComponent) -> String {
        let entries = AggregateAnalysis::cost_composition(root);
        let mut lines: Vec<String> = Vec::new();
        lines.push(String::from("【三、成本构成分析】"));
        for entry in &entries {
            lines.push(format!(
                "  ▸ {}(分类键 {})",
                entry.category_label, entry.category
            ));
            lines.push(format!(
                "      成本 {} | 占总成本 {:.1}%",
                entry.cost, entry.share_percent
            ));
            lines.push(format!("      净重 {} | 叶子数 {} | 件数 {}", entry.weight, entry.leaf_count, entry.pieces));
        }
        // 合计行
        lines.push(String::from("  ── 合计"));
        lines.push(format!(
            "      成本 {} | 净重 {} | 合计件数 {}",
            root.total_cost(),
            root.total_weight(),
            root.piece_count()
        ));
        lines.join("\n")
    }
 
    /// 生成「四、材料与服务成本对比」区
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    pub fn material_versus_service(root: &dyn ProductComponent) -> String {
        // 取得材料与服务两侧的成本
        let (material_cost, service_cost) = AggregateAnalysis::material_versus_service_cost(root);
        // 总成本用于计算占比
        let total_cost: Money = root.total_cost();
        let mut lines: Vec<String> = Vec::new();
        lines.push(String::from("【四、材料与服务成本对比】"));
        lines.push(format!(
            "  材料成本合计:{}({:.1}%)",
            material_cost,
            material_cost.ratio_to(total_cost) * 100.0
        ));
        lines.push(format!(
            "  工艺服务成本合计:{}({:.1}%)",
            service_cost,
            service_cost.ratio_to(total_cost) * 100.0
        ));
        lines.join("\n")
    }
 
    /// 生成「五、关键节点定位」区
    ///
    /// # 参数
    /// - `root`:产品结构树的根节点
    /// - `keywords`:需要定位的关键字列表
    pub fn search_footprint(root: &dyn ProductComponent, keywords: &[&str]) -> String {
        let mut lines: Vec<String> = Vec::new();
        lines.push(String::from("【五、关键节点定位】"));
        if keywords.is_empty() {
            // 无关键字时给出明确提示,而非空段落
            lines.push(String::from("  未提供待查找关键字。"));
            return lines.join("\n");
        }
        for keyword in keywords {
            // 逐个关键字做模糊查找
            let hits = SearchAnalysis::find_all_by_keyword(root, keyword);
            lines.push(format!("  关键字「{}」命中 {} 个节点", keyword, hits.len()));
            for (index, hit) in hits.iter().enumerate() {
                lines.push(format!(
                    "    {}. [{}] {} | 深度 {} | 路径:{}",
                    index + 1,
                    hit.node.kind_name(),
                    hit.node.name(),
                    hit.depth,
                    hit.path_text()
                ));
            }
        }
        lines.join("\n")
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : component_container.rs
//! 容器角色接口(Container Role)
//!
//! 职责单一:把"管理子节点"这一能力从 Component 主体接口中**隔离**出来。
//!
//! 【接口隔离原则】
//! 叶子类型(单件物料、服务项)根本不需要增删子节点的能力。
//! 若把 `add_child` 写在 `ProductComponent` 上,
//! 叶子就只能实现为空操作(或直接抛错),既污染接口又埋下运行期隐患。
//!
//! 因此本工程采用两个 trait:
//! - [`ProductComponent`]:所有节点都必须具备的"读"能力(统一对待);
//! - [`ComponentContainer`]:仅组合体具备的"写"能力(构建树时使用)。
//!
//! 泛型代码若需要装配子树,只需在约束中加上 `ComponentContainer`,
//! 而需要遍历与统计时只需 `ProductComponent`。
 
use crate::component::ProductComponent;
 
/// 子节点容器能力接口 ------ 仅由「组合体(Composite)」实现
pub trait ComponentContainer {
    /// 以只读切片形式访问子节点
    fn child_slice(&self) -> &[Box<dyn ProductComponent>];
 
    /// 以可变引用形式访问子节点向量
    ///
    /// 供构建器批量装配子树使用。
    fn child_slice_mut(&mut self) -> &mut Vec<Box<dyn ProductComponent>>;
 
    /// 追加一个子组件
    ///
    /// # 参数
    /// - `child`:子组件实例(装箱后的 trait 对象)
    fn add_child(&mut self, child: Box<dyn ProductComponent>) {
        // 默认实现直接复用可变切片访问器
        self.child_slice_mut().push(child);
    }
 
    /// 批量追加多个子组件
    ///
    /// # 参数
    /// - `children`:子组件迭代器
    fn extend_children<I>(&mut self, children: I)
    where
        I: IntoIterator<Item = Box<dyn ProductComponent>>,
    {
        // 一次性扩展,避免逐条 push 的重复容量检查
        self.child_slice_mut().extend(children);
    }
 
    /// 按编码移除并返回子组件
    ///
    /// 只移除**直接子节点**,不递归到更深层级。
    ///
    /// # 参数
    /// - `code`:目标子组件的物料编码
    ///
    /// # 返回
    /// 被移除的子组件;若未找到则返回 `None`
    fn remove_child_by_code(&mut self, code: &str) -> Option<Box<dyn ProductComponent>> {
        // 先定位目标下标,避免在查找过程中同时可变借用
        let target_index: Option<usize> = self
            .child_slice()
            .iter()
            .position(|child| child.code().equals(code));
        match target_index {
            // 找到则移除并返回
            Some(index) => Some(self.child_slice_mut().remove(index)),
            // 未找到则返回 None
            None => None,
        }
    }
 
    /// 返回直接子节点数量
    fn child_count(&self) -> usize {
        self.child_slice().len()
    }
 
    /// 判断当前容器是否为空
    fn is_empty_container(&self) -> bool {
        self.child_slice().is_empty()
    }
 
    /// 清空全部直接子节点
    fn clear_children(&mut self) {
        self.child_slice_mut().clear();
    }
}
 
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : product_component.rs
//! 组件抽象接口(Component)
//!
//! ============================================================================
//! 组合模式的灵魂:统一的 Component 接口
//! ============================================================================
//!
//! 叶子(单件物料)与组合体(装配套件)实现**同一个** trait,
//! 客户端因此可以把它们当作同一种东西处理,
//! 无需写 `if 是目录 then ... else ...` 这类分支判断。
//!
//! # 本设计的三个关键取舍
//!
//! 1. **`children()` 是唯一必须由各类型实现的结构方法。**
//!    只要它能正确返回子节点,所有递归操作都可由默认实现自动完成,
//!    新增叶子/组合类型时几乎零负担。
//!
//! 2. **递归查询用默认实现,但"返回自身引用"的查询不能写在 trait 里。**
//!    Rust 的 trait 默认方法中 `Self` 是 `?Sized` 的,
//!    无法把 `&Self` 强制转换为 `&dyn ProductComponent`。
//!    因此 `find_by_code` / `render_tree` 等需要返回自身引用的操作,
//!    被下沉到 `analysis` 层以自由函数实现(见 `analysis/search_analysis.rs`)。
//!
//! 3. **展示与结构分离。**
//!    本 trait 只保留"身份 + 结构 + 聚合"三类能力,
//!    树形渲染、占比分析等展示型操作全部放在 `analysis` 层,
//!    从而满足"职责单一",也让新增报表无需触碰核心接口。
 
use crate::domain::{ComponentCode, Money, Weight};
 
/// 叶子节点共用的静态空子节点切片
///
/// 所有叶子实现都直接返回它,避免每次调用都构造新对象。
pub const NO_CHILDREN: &[Box<dyn ProductComponent>] = &[];
 
/// 产品组件抽象接口 ------ 组合模式中的「Component」
///
/// # 实现须知
/// - **叶子**:`children()` 返回 [`NO_CHILDREN`],聚合方法返回自身数值;
/// - **组合体**:`children()` 返回子节点集合,聚合方法对子节点求和。
///
/// 本 trait 保持对象安全(所有方法无泛型参数、不返回 `Self`),
/// 因此可以自由装箱为 `Box<dyn ProductComponent>` 并按树形嵌套。
pub trait ProductComponent: std::fmt::Debug {
    // ==================== 一、身份(Identity) ====================
    /// 返回物料编码
    fn code(&self) -> &ComponentCode;
 
    /// 返回组件名称
    fn name(&self) -> &str;
 
    /// 返回组件种类名称(如 "Material" / "Service" / "Assembly")
    ///
    /// 用于报表分类与诊断输出。
    fn kind_name(&self) -> &'static str;
 
    // ==================== 二、结构(Structure) ====================
    /// 返回子组件切片
    ///
    /// 这是本 trait 中**唯一**必须由各类型自行实现的结构方法,
    /// 也是全部递归默认实现的基石。
    fn children(&self) -> &[Box<dyn ProductComponent>];
 
    /// 判断本节点是否为组合体
    ///
    /// 采用显式方法而非"子节点非空"推断,
    /// 因为一个尚未装配任何子件的空套件仍是组合体。
    fn is_composite(&self) -> bool;
 
    // ==================== 三、聚合(Aggregate) ====================
    /// 返回本节点及其所有后代的累计重量
    fn total_weight(&self) -> Weight;
 
    /// 返回本节点及其所有后代的累计成本
    fn total_cost(&self) -> Money;
 
    /// 返回本节点及其所有后代的累计计件数
    fn piece_count(&self) -> u32;
 
    // ==================== 四、衍生查询(默认实现,基于结构与聚合递归) ====================
    /// 返回子树中的节点总数(含自身)
    fn node_count(&self) -> usize {
        // 自身 1 个,加上所有子树节点数之和
        1 + self
            .children()
            .iter()
            .map(|child| child.node_count())
            .sum::<usize>()
    }
 
    /// 返回子树中的叶子节点数量
    fn leaf_count(&self) -> usize {
        if self.children().is_empty() {
            // 无子节点者即为叶子
            return 1;
        }
        // 组合体的叶子数等于其子节点叶子数之和
        self.children()
            .iter()
            .map(|child| child.leaf_count())
            .sum::<usize>()
    }
 
    /// 返回子树的最大深度(叶子为 0,根到最远叶子的边数)
    fn max_depth(&self) -> usize {
        // 无子节点时深度为 0;否则为子树最大深度加一
        self.children()
            .iter()
            .map(|child| child.max_depth() + 1)
            .max()
            .unwrap_or(0)
    }
 
    /// 判断子树中是否存在指定编码的组件
    ///
    /// # 参数
    /// - `code`:待查找的物料编码
    fn contains_code(&self, code: &str) -> bool {
        // 命中自身
        if self.code().equals(code) {
            return true;
        }
        // 递归检查所有子树
        self.children()
            .iter()
            .any(|child| child.contains_code(code))
    }
 
    /// 判断子树中是否存在指定种类的组件
    ///
    /// # 参数
    /// - `kind_name`:组件种类名称
    fn contains_kind(&self, kind_name: &str) -> bool {
        if self.kind_name() == kind_name {
            return true;
        }
        self.children()
            .iter()
            .any(|child| child.contains_kind(kind_name))
    }
 
    // ==================== 五、展示(Presentation) ====================
    /// 返回本节点自身的一行属性描述(不包含子节点、不做递归)
    ///
    /// 由各类型自行实现,以体现叶子与组合体在字段上的差异。
    fn describe(&self) -> String;
 
    /// 返回带编码、种类与聚合指标的精简摘要
    ///
    /// 提供默认实现,供报表统一排版使用。
    fn summary(&self) -> String {
        format!(
            "[{}] {} ({}) | 重 {} | 成本 {} | 件数 {}",
            self.code(),
            self.name(),
            self.kind_name(),
            self.total_weight(),
            self.total_cost(),
            self.piece_count()
        )
    }
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : traversal.rs
//! 通用树遍历工具
//!
//! 职责单一:提供与具体业务无关的树遍历能力。
//!
//! 【为什么单独成层】
//! `ProductComponent` 上的默认递归方法虽然能完成统计,
//! 但它们无法在递归过程中附带"路径"这类上下文信息
//! (因为"返回自身引用"在 trait 默认方法中无法转换为 `&dyn`)。
//! 本模块用显式栈把遍历过程外置,从而:
//! - 产出节点时同时给出**深度**与**从根到该节点的路径**;
//! - 让 `analysis` 层可以直接"站在遍历结果之上"写新报表,
//!   **新增一种分析无需修改组件层任何一个文件**。
 
use crate::component::ProductComponent;
 
/// 一次节点访问记录
#[derive(Debug, Clone)]
pub struct NodeVisit<'a> {
    /// 被访问的节点引用
    pub node: &'a dyn ProductComponent,
    /// 该节点所在深度(根节点为 0)
    pub depth: usize,
    /// 从根节点到该节点的名称路径(含首尾两端)
    pub path: Vec<String>,
}
 
impl<'a> NodeVisit<'a> {
    /// 把路径渲染为 `A / B / C` 形式
    pub fn path_text(&self) -> String {
        self.path.join(" / ")
    }
}
 
/// 深度优先遍历器
///
/// 使用显式栈而非递归,避免深层产品结构可能造成的调用栈溢出。
/// 遍历顺序为**从左到右的前序遍历**(父节点先于其全部子节点)。
pub struct DepthFirstWalker<'a> {
    /// 待处理栈,元素为(节点引用, 深度, 从根到该节点父级的路径)
    stack: Vec<(&'a dyn ProductComponent, usize, Vec<String>)>,
}
 
impl<'a> DepthFirstWalker<'a> {
    /// 以指定根节点创建遍历器
    ///
    /// # 参数
    /// - `root`:树的根节点
    pub fn new(root: &'a dyn ProductComponent) -> Self {
        Self {
            // 根节点深度为 0,其父级路径为空
            stack: vec![(root, 0, Vec::new())],
        }
    }
}
 
impl<'a> Iterator for DepthFirstWalker<'a> {
    type Item = NodeVisit<'a>;
 
    fn next(&mut self) -> Option<NodeVisit<'a>> {
        // 取出栈顶元素;栈空则遍历结束
        let (node, depth, parent_path) = self.stack.pop()?;
        // 当前节点路径 = 父级路径 + 自身名称
        let mut current_path: Vec<String> = parent_path;
        current_path.push(node.name().to_string());
        // 子节点**逆序**压栈,以保证弹出顺序为从左到右
        let children = node.children();
        for child in children.iter().rev() {
            self.stack
                .push((&**child, depth + 1, current_path.clone()));
        }
        Some(NodeVisit {
            node,
            depth,
            path: current_path,
        })
    }
}
 
/// 便捷入口:以深度优先方式遍历整棵树
///
/// # 参数
/// - `root`:树的根节点
pub fn depth_first(root: &dyn ProductComponent) -> DepthFirstWalker<'_> {
    DepthFirstWalker::new(root)
}
 
/// 收集子树中所有满足条件的节点
///
/// # 参数
/// - `root`:树的根节点
/// - `predicate`:判定函数,接收节点引用与其深度
pub fn collect_matching<'a>(
    root: &'a dyn ProductComponent,
    predicate: impl Fn(&dyn ProductComponent, usize) -> bool,
) -> Vec<NodeVisit<'a>> {
    depth_first(root)
        .filter(|visit| predicate(visit.node, visit.depth))
        .collect()
}
 
/// 收集子树中的全部叶子节点
///
/// # 参数
/// - `root`:树的根节点
pub fn collect_leaves<'a>(root: &'a dyn ProductComponent) -> Vec<NodeVisit<'a>> {
    // 叶子即"没有子节点"的节点
    collect_matching(root, |node, _depth| node.children().is_empty())
}
 
/// 收集子树中的全部组合体节点
///
/// # 参数
/// - `root`:树的根节点
pub fn collect_composites<'a>(root: &'a dyn ProductComponent) -> Vec<NodeVisit<'a>> {
    collect_matching(root, |node, _depth| node.is_composite())
}
 
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : Product_Assembly.rs
//! 组合体实现:产品装配体(ProductAssembly)
//!
//! 职责单一:承载子组件集合并把聚合操作**递归委托**给子组件。
//!
//! 【组合模式的核心机制】
//! 组合体与叶子实现同一个 `ProductComponent` 接口,
//! 但组合体的 `total_weight()` / `total_cost()` 等实现是
//! "对每个子节点再调一次同名方法,然后求和"------
//! 递归就此自然产生,客户端完全无需关心当前节点是叶子还是组合体。
//!
//! 同时,组合体额外实现 `ComponentContainer`,
//! 提供叶子不具备的 `add_child` / `remove_child_by_code` 等能力。
 
use crate::component::{ComponentContainer, ProductComponent};
use crate::domain::{ComponentCode, Money, Weight};
 
/// 产品装配体 / 套件(Composite)
///
/// 例如「龙凤呈祥三件套」,其子节点可以是单件物料、工艺服务,
/// 也可以是更小的装配体(如「项链主体」),从而形成任意深度的树。
#[derive(Debug)]
pub struct ProductAssembly {
    /// 装配体编码
    code: ComponentCode,
    /// 装配体名称
    name: String,
    /// 子组件集合(递归嵌套的关键)
    children: Vec<Box<dyn ProductComponent>>,
}
 
impl ProductAssembly {
    /// 构造一个不含子组件的空装配体
    ///
    /// # 参数
    /// - `code`:装配体编码原文
    /// - `name`:装配体名称
    pub fn new(code: &str, name: &str) -> Self {
        Self {
            // 编码由值对象负责规范化
            code: ComponentCode::new(code),
            // 名称转为拥有所有权的字符串
            name: name.to_string(),
            // 初始为空集合,随后通过 add_child 装配
            children: Vec::new(),
        }
    }
 
    /// 构造一个已装配好子组件的装配体
    ///
    /// # 参数
    /// - `code`:装配体编码原文
    /// - `name`:装配体名称
    /// - `children`:子组件集合
    pub fn with_children(
        code: &str,
        name: &str,
        children: Vec<Box<dyn ProductComponent>>,
    ) -> Self {
        Self {
            code: ComponentCode::new(code),
            name: name.to_string(),
            children,
        }
    }
 
    /// 返回直接子节点数量
    pub fn direct_child_count(&self) -> usize {
        self.children.len()
    }
}
 
impl ProductComponent for ProductAssembly {
    /// 返回装配体编码
    fn code(&self) -> &ComponentCode {
        &self.code
    }
 
    /// 返回装配体名称
    fn name(&self) -> &str {
        &self.name
    }
 
    /// 返回组件种类名称
    fn kind_name(&self) -> &'static str {
        "Assembly"
    }
 
    /// 返回子组件切片 ------ 递归的入口
    fn children(&self) -> &[Box<dyn ProductComponent>] {
        &self.children
    }
 
    /// 装配体恒为组合体(即使当前尚未装配任何子件)
    fn is_composite(&self) -> bool {
        true
    }
 
    /// 累计重量 = 全部子组件累计重量之和
    fn total_weight(&self) -> Weight {
        self.children
            .iter()
            .map(|child| child.total_weight())
            .sum()
    }
 
    /// 累计成本 = 全部子组件累计成本之和
    fn total_cost(&self) -> Money {
        self.children
            .iter()
            .map(|child| child.total_cost())
            .sum()
    }
 
    /// 累计件数 = 全部子组件累计件数之和
    fn piece_count(&self) -> u32 {
        self.children
            .iter()
            .map(|child| child.piece_count())
            .sum()
    }
 
    /// 一行属性描述
    fn describe(&self) -> String {
        format!(
            "产品装配体 | 编码 {} | 名称 {} | 直接子件 {} 个 | 节点总数 {} | 叶子数 {} | 最大深度 {} | 合计净重 {} | 合计成本 {} | 合计件数 {}",
            self.code,
            self.name,
            self.direct_child_count(),
            self.node_count(),
            self.leaf_count(),
            self.max_depth(),
            self.total_weight(),
            self.total_cost(),
            self.piece_count()
        )
    }
}
 
impl ComponentContainer for ProductAssembly {
    /// 以只读切片形式访问子节点
    fn child_slice(&self) -> &[Box<dyn ProductComponent>] {
        &self.children
    }
 
    /// 以可变引用形式访问子节点集合
    fn child_slice_mut(&mut self) -> &mut Vec<Box<dyn ProductComponent>> {
        &mut self.children
    }
}

调用:

rust 复制代码
//!# encoding: utf-8
//!# 版权所有  2026 ©涂聚文有限公司™ ®
//!# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
//!# 描述:组合模式Composite Pattern
//!# Author    : geovindu,Geovin Du 涂聚文.
//!# IDE       : RustRover  2025.1.1 Rust  rustc 1.98.1
//!# os        : windows 10
//!# database  : mysql 9.0 sql server 2025, postgreSQL 18.0  Oracle 21c Neo4j
//!# Datetime  : 2026/10/1 19:24
//!# User      :  geovindu
//!# Product   : RustRover
//!# Project   : compositepattern
//!# File      : main.rs
 
// 本示例属于"可扩展框架式"代码:各层刻意暴露了完整的公开 API 面
// (构造器、访问器、分析入口等),以便读者按需扩展组合。
// 这些接口不会被当前演示入口全部调用,故在此关闭未使用代码告警。
#![allow(dead_code)]
 
//! 组合模式(Composite Pattern)演示入口
//!
//! # 模式意图
//! 将对象组合成**树形结构**来表示"部分-整体"的层次关系。
//! 组合模式使得客户端可以**一致地**对待单个对象与对象组合。
//!
//! # 本工程的统一对待机制
//! [`component::ProductComponent`] 同时被两类角色实现:
//! - **叶子(Leaf)**:材料单件、工艺服务 ------ 没有子节点,直接给出自身数值;
//! - **组合体(Composite)**:产品装配体 ------ 持有子节点,把聚合递归委托给它们。
//!
//! 客户端(`app` 层与分析层)只依赖 `&dyn ProductComponent`,
//! 因此完全不需要判断"当前节点是叶子还是组合体"。
//!
//! # 严格分层结构
//! ```text
//!   main.rs               入口,仅做流程编排
//!     └── app             应用层:报表编排(只取数 + 排版)
//!     └── analysis        分析层:树渲染 / 查找 / 聚合(可无限新增)
//!           └── component    组件层:Component 抽象 + 容器角色 + 通用遍历
//!     └── factory         工厂层:规格数据 → 组件树
//!           └── composite     组合层:ProductAssembly(递归委托)
//!           └── leaf          叶子层:MaterialItem / ServiceItem
//!                 └── domain      领域层:Money / Weight / ComponentCode
//! ```
 
// 声明各分层模块(编译期强制分层可见性)
mod analysis;
mod app;
mod component;
mod composite;
mod domain;
mod factory;
mod leaf;
 
// ---------- 引入组件层 ----------
use component::{
    collect_matching, depth_first, ComponentContainer, DepthFirstWalker, NodeVisit,
    ProductComponent, NO_CHILDREN,
};
// ---------- 引入组合层 ----------
use composite::ProductAssembly;
// ---------- 引入领域层 ----------
use domain::{ComponentCode, Money, Weight};
// ---------- 引入叶子层 ----------
use leaf::{MaterialItem, ServiceItem};
// ---------- 引入工厂层 ----------
use factory::{BuildError, ComponentSpec, ProductBuilder};
// ---------- 引入分析层 ----------
use analysis::{CostCompositionEntry, SearchAnalysis, SearchHit, TreeAnalysis};
// ---------- 引入应用层 ----------
use app::BomReport;
 
// ============================================================================
// 第四幕所需的「自定义叶子类型」
//
// 本类型定义在 main.rs 中,**不属于工程的任何一个分层**。
// 它只实现 `ProductComponent` 一个 trait,即可:
//   - 被挂载进任何 `ProductAssembly`;
//   - 被组件层的默认递归方法自动统计;
//   - 被分析层自动纳入成本构成分类;
//   - 被应用层自动渲染进报表。
//
// 全程未修改任何既有分层文件 ------ 这就是"完全可扩展"的实证。
// ============================================================================
 
/// 礼品包装项(自定义叶子类型)
#[derive(Debug)]
struct PackagingItem {
    /// 包装物料编码
    code: ComponentCode,
    /// 包装名称
    name: String,
    /// 单套包装费
    unit_price: Money,
    /// 套数
    sets: u32,
}
 
impl PackagingItem {
    /// 构造一个礼品包装项
    ///
    /// # 参数
    /// - `code`:包装编码原文
    /// - `name`:包装名称
    /// - `unit_price_yuan`:单套包装费(元)
    /// - `sets`:套数
    fn new(code: &str, name: &str, unit_price_yuan: f64, sets: u32) -> Self {
        Self {
            code: ComponentCode::new(code),
            name: name.to_string(),
            unit_price: Money::from_yuan(unit_price_yuan),
            sets,
        }
    }
}
 
/// 实现组合模式的 Component 接口
impl ProductComponent for PackagingItem {
    /// 返回包装编码
    fn code(&self) -> &ComponentCode {
        &self.code
    }
 
    /// 返回包装名称
    fn name(&self) -> &str {
        &self.name
    }
 
    /// 返回组件种类名称
    fn kind_name(&self) -> &'static str {
        "Packaging"
    }
 
    /// 叶子没有子节点
    fn children(&self) -> &[Box<dyn ProductComponent>] {
        NO_CHILDREN
    }
 
    /// 不是组合体
    fn is_composite(&self) -> bool {
        false
    }
 
    /// 包装不计贵金属重量
    fn total_weight(&self) -> Weight {
        Weight::ZERO
    }
 
    /// 累计成本 = 单套包装费 × 套数
    fn total_cost(&self) -> Money {
        self.unit_price.multiply(self.sets as i64)
    }
 
    /// 计件数 = 套数
    fn piece_count(&self) -> u32 {
        self.sets
    }
 
    /// 一行属性描述
    fn describe(&self) -> String {
        format!(
            "礼品包装 | 编码 {} | 名称 {} | 单套包装费 {} | 套数 {} | 合计成本 {}",
            self.code,
            self.name,
            self.unit_price,
            self.sets,
            self.total_cost()
        )
    }
}
 
/// 程序入口
fn main() {
    // 全局分隔线,用于终端输出美化
    let global_separator: &str =
        "================================================================================";
 
    // ========================================================================
    // 第一幕:以「规格数据」驱动,由工厂递归构建组件树
    // ========================================================================
    println!("{}", global_separator);
    println!("第一幕:以规格数据驱动,由工厂递归构建产品结构树");
    println!("{}", global_separator);
 
    // 构造规格树:嵌套深度即产品结构的层级
    let product_specification: ComponentSpec =
        ComponentSpec::assembly("ASM-SET-001", "龙凤呈祥三件套").with_children(vec![
            // ---------- 项链主体 ----------
            ComponentSpec::assembly("ASM-NECK-001", "项链主体").with_children(vec![
                // 金链身
                ComponentSpec::material("MAT-GOLD-001", "18K金链身", 3.25, 520.0, 1),
                // 吊坠主体(再嵌一层装配体)
                ComponentSpec::assembly("ASM-PEND-001", "吊坠主体").with_children(vec![
                    ComponentSpec::material("MAT-PLAT-001", "铂金吊坠托", 2.80, 380.0, 1),
                    ComponentSpec::material("MAT-DIA-001", "碎钻群镶", 0.60, 6800.0, 1),
                    ComponentSpec::service("SVC-SET-001", "钻石镶嵌工时", 180.0, 1),
                ]),
                // 抛光打磨
                ComponentSpec::service("SVC-POLISH-001", "抛光打磨", 45.0, 3),
            ]),
            // ---------- 戒指主体 ----------
            ComponentSpec::assembly("ASM-RING-001", "戒指主体").with_children(vec![
                ComponentSpec::material("MAT-GOLD-002", "18K金戒圈", 4.10, 520.0, 1),
                ComponentSpec::service("SVC-PLATE-001", "电镀铑", 35.0, 1),
            ]),
        ]);
 
    // 统计规格树的规模,验证递归遍历在数据侧同样成立
    println!(
        "规格树构建完毕:根节点 {},直接子规格 {} 个",
        product_specification.display_name(),
        product_specification.child_count()
    );
 
    // 带校验地构建(校验编码非空且全树唯一)
    let build_result: Result<Box<dyn ProductComponent>, BuildError> =
        ProductBuilder::build_checked(&product_specification);
    let product_tree: Box<dyn ProductComponent> = match build_result {
        // 校验通过,成功构建
        Ok(tree) => {
            println!("编码校验通过:全树编码唯一且非空。");
            tree
        }
        // 校验失败(本演示数据不会触发,保留分支以展示错误处理路径)
        Err(build_error) => {
            println!("构建失败:{}", build_error);
            return;
        }
    };
 
    // 用精确查找验证某一关键物料确实位于预期位置
    let diamond_hit: Option<SearchHit<'_>> =
        SearchAnalysis::find_by_code(product_tree.as_ref(), "MAT-DIA-001");
    if let Some(hit) = diamond_hit {
        println!(
            "关键物料定位:{} 位于深度 {},路径为 {}",
            hit.code_text(),
            hit.depth,
            hit.path_text()
        );
    }
 
    // ========================================================================
    // 第二幕:生成完整 BOM 报表
    // ========================================================================
    println!();
    println!("{}", global_separator);
    println!("第二幕:生成完整 BOM 报表");
    println!("{}", global_separator);
 
    // 传入根节点的 trait 对象引用;分析层与报表层完全无需知道具体类型
    let bom_document: String = BomReport::generate(
        product_tree.as_ref(),
        &["金", "SVC-SET-001", "吊坠"],
    );
    println!("{}", bom_document);
 
    // ========================================================================
    // 第三幕:运行时动态重构 ------ 子树的挂载、摘除与迁移
    // ========================================================================
    println!();
    println!("{}", global_separator);
    println!("第三幕:运行时动态重构 ------ 子树的挂载、摘除与迁移");
    println!("{}", global_separator);
 
    // 说明:要修改"嵌套子节点",必须持有具体类型的可变句柄。
    // 这是因为 `ProductComponent` 只提供只读访问(读取用统一接口),
    // 而增删能力被隔离在 `ComponentContainer` 中(构建用具体类型)。
    // 因此这里用「对象驱动」方式直接装配,与第一幕的「数据驱动」互为补充。
 
    // 独立构建"项链主体"
    let mut necklace_assembly: ProductAssembly = ProductAssembly::new("ASM-NECK-900", "项链主体");
    necklace_assembly.add_child(Box::new(MaterialItem::new(
        "MAT-GOLD-900",
        "18K金链身",
        3.25,
        520.0,
        1,
    )));
    necklace_assembly.add_child(Box::new(ServiceItem::new("SVC-POLISH-900", "抛光打磨", 45.0, 1)));
 
    // 独立构建"吊坠主体"(一个完整的子树)
    let mut pendant_assembly: ProductAssembly = ProductAssembly::new("ASM-PEND-900", "吊坠主体");
    pendant_assembly.add_child(Box::new(MaterialItem::new(
        "MAT-PLAT-900",
        "铂金吊坠托",
        2.80,
        380.0,
        1,
    )));
    pendant_assembly.add_child(Box::new(MaterialItem::new(
        "MAT-DIA-900",
        "碎钻群镶",
        0.60,
        6800.0,
        1,
    )));
    pendant_assembly.add_child(Box::new(ServiceItem::new(
        "SVC-SET-900",
        "钻石镶嵌工时",
        180.0,
        1,
    )));
 
    // 迁移前的状态
    println!("① 迁移前 ------ {}", necklace_assembly.summary());
 
    // 把整棵"吊坠主体"子树挂到项链上(组合体可容纳组合体,形成任意深度)
    necklace_assembly.add_child(Box::new(pendant_assembly));
    println!("② 挂载吊坠子树后 ------ {}", necklace_assembly.summary());
 
    // 从项链上摘除吊坠子树:返回的是一株**结构完整**的子树
    let detached_pendant: Option<Box<dyn ProductComponent>> =
        necklace_assembly.remove_child_by_code("ASM-PEND-900");
    println!("③ 摘下吊坠后 ------ {}", necklace_assembly.summary());
 
    // 把摘下的子树原样迁移到新的装配体中
    let mut single_item_box: ProductAssembly =
        ProductAssembly::new("ASM-SINGLE-900", "单品礼盒");
    if let Some(pendant_component) = detached_pendant {
        // 子树连同其全部后代一并迁移,聚合值保持不变
        single_item_box.add_child(pendant_component);
    }
    println!("④ 迁移后 ------ {}", single_item_box.summary());
    println!();
    println!("迁移后的单品礼盒完整结构:");
    for tree_line in TreeAnalysis::render_with_metrics(&single_item_box).lines() {
        println!("  {}", tree_line);
    }
 
    // ========================================================================
    // 第四幕:扩展性验证 ------ 挂载一个定义在本文件中的自定义叶子类型
    // ========================================================================
    println!();
    println!("{}", global_separator);
    println!("第四幕:扩展性验证 ------ 挂载工程之外定义的自定义叶子类型");
    println!("{}", global_separator);
 
    // 新建"臻品礼盒套装"装配体
    let mut luxury_gift_set: ProductAssembly =
        ProductAssembly::new("ASM-LUX-900", "臻品礼盒套装");
    // 子件一:由工厂构建的既有子树(复用第二幕的规格)
    luxury_gift_set.add_child(ProductBuilder::build(&product_specification));
    // 子件二:自定义叶子类型(来自本文件,不属于任何分层)
    luxury_gift_set.add_child(Box::new(PackagingItem::new(
        "PKG-GIFT-900",
        "臻品绒面礼盒",
        158.0,
        1,
    )));
    // 子件三:再挂一个自定义叶子
    luxury_gift_set.add_child(Box::new(PackagingItem::new(
        "PKG-CARD-900",
        "品牌贺卡",
        28.0,
        2,
    )));
 
    // 自定义类型被组件层默认递归方法自动纳入统计
    println!("自定义叶子已挂载,以下指标由组件层默认递归方法自动得出:");
    println!("  节点总数:{}", luxury_gift_set.node_count());
    println!("  叶子数量:{}", luxury_gift_set.leaf_count());
    println!(
        "  装配体数:{}",
        analysis::AggregateAnalysis::structure_statistics(&luxury_gift_set).composite_count
    );
    println!("  最大深度:{}", luxury_gift_set.max_depth());
    println!("  合计净重:{}", luxury_gift_set.total_weight());
    println!("  合计成本:{}", luxury_gift_set.total_cost());
    println!("  合计件数:{}", luxury_gift_set.piece_count());
    println!();
    println!("自定义类型「Packaging」是否已被分析层识别:{}", luxury_gift_set.contains_kind("Packaging"));
    println!();
    println!("臻品礼盒套装的成本构成(自定义叶子被自动归类到「包装」):");
    let composition_entries: Vec<CostCompositionEntry> =
        analysis::AggregateAnalysis::cost_composition(&luxury_gift_set);
    for entry in &composition_entries {
        println!(
            "  ▸ {} ------ 成本 {}({:.1}%)| 净重 {} | 叶子 {} 个 | 件数 {}",
            entry.category_label,
            entry.cost,
            entry.share_percent,
            entry.weight,
            entry.leaf_count,
            entry.pieces
        );
    }
 
    // ========================================================================
    // 第五幕:仅用公开遍历 API 现场撰写全新分析,核心层零改动
    // ========================================================================
    println!();
    println!("{}", global_separator);
    println!("第五幕:仅用公开遍历 API 现场撰写新分析,核心层零改动");
    println!("{}", global_separator);
 
    // 需求一:列出"深度不小于 2 的全部叶子节点"及其完整路径
    // 实现只需一次 collect_matching 调用,无需新增任何分层文件
    let deep_leaf_visits: Vec<NodeVisit<'_>> = collect_matching(
        product_tree.as_ref(),
        |node, depth| node.children().is_empty() && depth >= 2,
    );
    println!("深度不小于 2 的叶子节点共 {} 个:", deep_leaf_visits.len());
    for visit in &deep_leaf_visits {
        println!(
            "  · {} [{}] | 深度 {} | 路径:{}",
            visit.node.name(),
            visit.node.kind_name(),
            visit.depth,
            visit.path_text()
        );
    }
 
    // 需求二:统计"每一层各有多少个节点"
    let mut depth_histogram: Vec<usize> = Vec::new();
    for visit in depth_first(product_tree.as_ref()) {
        // 首次触达更深的层级时先扩容
        if depth_histogram.len() <= visit.depth {
            depth_histogram.resize(visit.depth + 1, 0);
        }
        depth_histogram[visit.depth] += 1;
    }
    println!();
    println!("各层节点数分布(第 0 层为根节点):");
    for (level, node_count) in depth_histogram.iter().enumerate() {
        println!("  第 {} 层:{} 个节点", level, node_count);
    }
 
    // 需求三:直接持有遍历器类型,验证其满足标准迭代器协议
    let walker: DepthFirstWalker<'_> = depth_first(product_tree.as_ref());
    let visited_total: usize = walker.count();
    println!();
    println!(
        "遍历器直接计数:完整前序遍历共访问 {} 个节点(应等于节点总数)",
        visited_total
    );
 
    // ========================================================================
    // 收尾:组合规模总结
    // ========================================================================
    println!();
    println!("{}", global_separator);
    println!("收尾:组合模式的可组合规模");
    println!("{}", global_separator);
    println!("  抽象角色(Component):1 个统一接口");
    println!("  叶子角色(Leaf):3 种(MaterialItem / ServiceItem / 自定义 PackagingItem)");
    println!("  组合角色(Composite):1 种(ProductAssembly,可自我嵌套)");
    println!(
        "  可组合层级:不受限制(本演示最大深度 {},装配体可再套装配体)",
        product_tree.max_depth()
    );
    println!("  分析操作:树形渲染 / 模糊查找 / 成本构成 / 结构统计(新增操作零侵入核心层)");
    println!("{}", global_separator);
    println!("演示结束。");
}

输出:

相关推荐
SearchMan1 小时前
还有人不知道&和&&的区别?
后端
想用offer打牌1 小时前
Personal Agent爆火 - 它到底是个什么
人工智能·后端·ai编程
IT_陈寒1 小时前
Vue的v-if和v-for混用居然是个天坑
前端·人工智能·后端
周杰偷奶茶2 小时前
【Java】数组的定义和使用(附管理系统实战案例)
java·开发语言
橙子圆1233 小时前
JUC之集合类不安全
java·开发语言
阿里嘎多学长3 小时前
2026-09-29 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Bruce_Liuxiaowei3 小时前
网页版 DeepSeek “harness desktop“ WebGL 上下文创建失败排查实录
开发语言·javascript·webgl
狗凯之家源码网3 小时前
PHP 用户反馈系统源码深度评测与实战指南
开发语言·php·用户反馈系统
打工仔折腾 AI4 小时前
从Attention到BERT:双向预训练语言模型到底解决了什么问题
人工智能·后端·python·深度学习·语言模型·bert