Java代码中是否正确实现了组合(Composition)?
你的Java代码是否正确实现了组合?
结论:没有。下面从概念定义、代码问题、修正方案三个层面详细说明:
先理清三个OOP关系的核心区别
- 关联(Association):两个类的松散连接,彼此独立存在。比如你的
SaleItem和Product——商品可以脱离订单项单独存在,这就是典型的关联。 - 聚合(Aggregation):关联的特殊形式,属于"整体-部分"关系,但部分可脱离整体独立存在。比如购物车和商品,商品不在购物车里也能正常存在。
- 组合(Composition):最紧密的"整体-部分"关系,部分的生命周期完全依赖整体:整体创建时部分才被创建,整体销毁时部分也随之销毁,部分无法脱离整体单独存在。比如订单和订单项,没有对应订单的订单项毫无存在意义。
你的Java代码问题分析
你的Sale类虽然在addSaleItem里创建了SaleItem并加入列表,但不符合组合的核心要求:
- 生命周期未绑定:代码结构允许外部创建
SaleItem实例后传入Sale(即使当前方法是内部创建,也没有限制外部操作),SaleItem可以脱离Sale存在。 - 封装性缺失:
listaP是包级访问权限,外部类可以直接修改这个列表(比如移除、替换SaleItem),违背了"整体完全控制部分"的组合原则。 - 无访问控制:外部可以直接持有
SaleItem的引用,导致Sale销毁后,SaleItem可能仍被其他对象引用,无法被回收。
对比你的PHP代码,Venda类的$vendas是private的,且addItemVenda完全内部创建ItemVenda,外部无法直接操作,更接近组合的要求。
修正Java代码实现组合的要点
要正确实现Sale与SaleItem的组合,需做到:
- 完全封装
SaleItem的存储容器,禁止外部直接访问; SaleItem的创建必须由Sale完全控制,外部无法独立创建或传入已有实例;- 限制
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
相关产品推荐
相关产品推荐

