You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java代码中是否正确实现了组合(Composition)?

你的Java代码是否正确实现了组合?

结论:没有。下面从概念定义、代码问题、修正方案三个层面详细说明:

先理清三个OOP关系的核心区别

  • 关联(Association):两个类的松散连接,彼此独立存在。比如你的SaleItem和Product——商品可以脱离订单项单独存在,这就是典型的关联。
  • 聚合(Aggregation):关联的特殊形式,属于"整体-部分"关系,但部分可脱离整体独立存在。比如购物车和商品,商品不在购物车里也能正常存在。
  • 组合(Composition):最紧密的"整体-部分"关系,部分的生命周期完全依赖整体:整体创建时部分才被创建,整体销毁时部分也随之销毁,部分无法脱离整体单独存在。比如订单和订单项,没有对应订单的订单项毫无存在意义。

你的Java代码问题分析

你的Sale类虽然在addSaleItem里创建了SaleItem并加入列表,但不符合组合的核心要求:

  1. 生命周期未绑定:代码结构允许外部创建SaleItem实例后传入Sale(即使当前方法是内部创建,也没有限制外部操作),SaleItem可以脱离Sale存在。
  2. 封装性缺失:listaP是包级访问权限,外部类可以直接修改这个列表(比如移除、替换SaleItem),违背了"整体完全控制部分"的组合原则。
  3. 无访问控制:外部可以直接持有SaleItem的引用,导致Sale销毁后,SaleItem可能仍被其他对象引用,无法被回收。

对比你的PHP代码,Venda类的$vendas是private的,且addItemVenda完全内部创建ItemVenda,外部无法直接操作,更接近组合的要求。

修正Java代码实现组合的要点

要正确实现Sale与SaleItem的组合,需做到:

  1. 完全封装SaleItem的存储容器,禁止外部直接访问;
  2. SaleItem的创建必须由Sale完全控制,外部无法独立创建或传入已有实例;
  3. 限制SaleItem的访问权限,确保只有Sale能管理它的生命周期。

修正后的Sale类

package Loja_produtos_escolares;

import java.text.SimpleDateFormat;
import java.util.*;

public class Sale {
    private Date data;
    private final SimpleDateFormat dataformat = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
    private double total;
    private String obs;
    private Customer cliente;
    private int protocolo;
    private final int qntProt = 1000000;
    private String output;
    private boolean cfp = true;

    // 改为private,完全封装订单项列表
    private final ArrayList<SaleItem> listaP = new ArrayList<>();
    private final Scanner input = new Scanner(System.in);

    public Sale() {
        this.data = new Date();
        this.protocolo = qntProt++; // 初始化唯一协议号
    }

    // 组合核心:完全由Sale控制SaleItem的创建,外部无法传入已有实例
    public void addSaleItem(Product produto) {
        System.out.println("Insira a quantidade: ");
        int qntd = input.nextInt();

        System.out.println("Insira o desconto:  [ obs: Não utilize \"%\" (porcentagem)!!! ]");
        int desc = input.nextInt();

        // 仅在Sale内部创建SaleItem,外部无法直接获取该实例引用
        SaleItem item = new SaleItem(produto, qntd, desc);
        listaP.add(item);
        this.total += item.getPrice(); // 同步更新订单总价
    }

    // 提供受控的访问方法,返回不可修改的列表,防止外部篡改
    public List<SaleItem> getSaleItems() {
        return Collections.unmodifiableList(listaP);
    }

    // 必要的getter方法,暴露订单核心信息
    public double getTotal() {
        return total;
    }

    public String getFormattedDate() {
        return dataformat.format(data);
    }
}

修正后的SaleItem类(强化封装)

package Loja_produtos_escolares;

public class SaleItem {
    private final double salePrice;
    private final int amount;
    private final float discount;
    private final double iTotal;
    private final Product produto;

    // 构造器改为包级私有,仅允许同包的Sale类创建实例
    SaleItem(Product produto, int amount, int d) {
        this.produto = produto;
        this.salePrice = produto.price;
        this.amount = amount;
        this.discount = (float) d / 100;

        double itemSum = amount * salePrice;
        double discountValue = itemSum * this.discount;
        this.iTotal = itemSum - discountValue;
    }

    public String getItemName() {
        return produto.unity;
    }

    public double getPrice() {
        return iTotal;
    }

    public int getAmount() {
        return amount;
    }

    public float getDiscountPercentage() {
        return discount * 100;
    }

    @Override
    public String toString() {
        return "produto: " + produto;
    }

    public double getOriginalPrice() {
        return produto.price;
    }
}

额外概念对应说明

  • 关联:你的SaleItem与Product的关系已经正确实现关联;
  • 聚合:如果你的系统中有Inventory(库存)类与Product类,二者就是聚合关系——商品可以脱离库存独立存在;
  • 组合:修正后的Sale与SaleItem就是标准的组合关系——订单项完全依赖订单存在,生命周期由订单控制。

内容的提问来源于stack exchange,提问作者Gabriel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 11:30:50