Java中聚合和组合的实现区别

116

我知道聚合和组合之间的概念差异。有人能告诉我它们在Java中的实现差异并举例说明吗?


3
如果您跟随此链接,您可以获得有关“聚合”和“组合”的差异的答案。1 - thar45
可能是聚合与组合的区别的重复问题。 - Alex K
当我们有任何对象之间的关系时,那被称为关联(Association)。聚合(Aggregation)和组合(Composition)都是关联的专业形式。组合又是聚合的专业形式。 - Raúl
你可以在这里找到更多答案:https://dev59.com/WnNA5IYBdhLWcg3wn_fD - user10283685
9个回答

262

组合

final class Car {

  private final Engine engine;

  Car(EngineSpecs specs) {
    engine = new Engine(specs);
  }

  void move() {
    engine.work();
  }
}
聚合
final class Car {

  private Engine engine;

  void setEngine(Engine engine) {
    this.engine = engine;
  }

  void move() {
    if (engine != null)
      engine.work();
  }
}

在组合的情况下,引擎完全被汽车封装。外部世界无法获取对引擎的引用。引擎与汽车同生共死。使用聚合时,汽车也通过引擎执行其功能,但是引擎并不总是汽车的内部部件。可以更换引擎,甚至完全移除引擎。不仅如此,外部世界仍然可以引用并操纵引擎,无论它是否在汽车中。


11
很棒的例子!它也展示了组合作为强关联(没有发动机的汽车就没有意义),而聚合作为弱关联(没有发动机的汽车可以完全理解,甚至在构造函数中也不需要)。应该使用哪一个?取决于上下文。 - Federico Pugnali
@Anand,你在聚合中给出的例子不是依赖关系的例子吗? 依赖关系是一种较弱的关系形式,在代码术语中表示一个类通过参数或返回类型使用另一个类。 - OOkhan
@Anand:你能详细解释一下为什么你说:“在组合的情况下,外部世界无法获取对引擎的引用,而在聚合的情况下,外部世界可以获取对引擎的引用”吗?你能展示一下代码示例,说明外部世界如何能够或不能够获取对引擎的引用吗?谢谢。 - O Connor
12
这不是正确的例子。外部世界可以访问内部对象,但其标识始终与外部对象相关联,而在聚合中,即使没有汽车,内部对象也可以独立存在。在这种情况下,即使没有汽车,仍可以使用“new Engine(EngineSpecs)”调用创建引擎。实现组合的方法是将Engine创建为内部类,以便始终使用Car对象引用创建一个引擎对象。 - mickeymoon
1
@mickeymoon,非常感谢您的发现。您能给我们指出一个更好的例子吗? - Gayan Weerakutti
显示剩余2条评论

22
我会使用一个不错的UML示例。
以一个有1到20个不同部门的大学为例,每个部门都有1到5名教授。 大学与其部门之间存在组合关系链接。 部门与其教授之间存在聚合关系链接。
组合是一种强聚合,如果大学被销毁,则其部门也应该被销毁。但即使它们所在的部门消失了,我们也不应该杀死教授。
在Java中:
public class University {

     private List<Department> departments;

     public void destroy(){
         //it's composition, when I destroy a university I also destroy the departments. they cant live outside my university instance
         if(departments!=null)
             for(Department d : departments) d.destroy();
         departments.clean();
         departments = null;
     }
}

public class Department {

     private List<Professor> professors;
     private University university;

     Department(University univ){
         this.university = univ;
         //check here univ not null throw whatever depending on your needs
     }

     public void destroy(){
         //It's aggregation here, we just tell the professor they are fired but they can still keep living
         for(Professor p:professors)
             p.fire(this);
         professors.clean();
         professors = null;
     }
}

public class Professor {

     private String name;
     private List<Department> attachedDepartments;

     public void destroy(){

     }

     public void fire(Department d){
         attachedDepartments.remove(d);
     }
}

大概是这样的。
编辑:如要求的一个例子。
public class Test
{
    public static void main(String[] args)
    {
        University university = new University();
        //the department only exists in the university
        Department dep = university.createDepartment();
        // the professor exists outside the university
        Professor prof = new Professor("Raoul");
        System.out.println(university.toString());
        System.out.println(prof.toString());

        dep.assign(prof);
        System.out.println(university.toString());
        System.out.println(prof.toString());
        dep.destroy();

        System.out.println(university.toString());
        System.out.println(prof.toString());

    }


}

大学课程
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class University {

    private List<Department> departments = new ArrayList<>();

    public Department createDepartment() {
        final Department dep = new Department(this, "Math");
        departments.add(dep);
        return dep;
    }

    public void destroy() {
        System.out.println("Destroying university");
        //it's composition, when I destroy a university I also destroy the departments. they cant live outside my university instance
        if (departments != null)
            departments.forEach(Department::destroy);
        departments = null;
    }

    @Override
    public String toString() {
        return "University{\n" +
                "departments=\n" + departments.stream().map(Department::toString).collect(Collectors.joining("\n")) +
                "\n}";
    }
}

部门分类
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Department {

    private final String name;
    private List<Professor> professors = new ArrayList<>();
    private final University university;

    public Department(University univ, String name) {
        this.university = univ;
        this.name = name;
        //check here univ not null throw whatever depending on your needs
    }

    public void assign(Professor p) {
        //maybe use a Set here
        System.out.println("Department hiring " + p.getName());
        professors.add(p);
        p.join(this);
    }

    public void fire(Professor p) {
        //maybe use a Set here
        System.out.println("Department firing " + p.getName());
        professors.remove(p);
        p.quit(this);
    }

    public void destroy() {
        //It's aggregation here, we just tell the professor they are fired but they can still keep living
        System.out.println("Destroying department");
        professors.forEach(professor -> professor.quit(this));
        professors = null;
    }

    @Override
    public String toString() {
        return professors == null
                ? "Department " + name + " doesn't exists anymore"
                : "Department " + name + "{\n" +
                "professors=" + professors.stream().map(Professor::toString).collect(Collectors.joining("\n")) +
                "\n}";
    }
}

教授课程
import java.util.ArrayList;
import java.util.List;

public class Professor {

    private final String name;
    private final List<Department> attachedDepartments = new ArrayList<>();

    public Professor(String name) {
        this.name = name;
    }

    public void destroy() {

    }

    public void join(Department d) {
        attachedDepartments.add(d);
    }

    public void quit(Department d) {
        attachedDepartments.remove(d);
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Professor " + name + " working for " + attachedDepartments.size() + " department(s)\n";
    }
}

实现方法存在争议,因为它取决于您需要如何处理创建、雇用和删除等事项。这与原帖无关。

我希望不会有任何关于未初始化列表和没有构造函数的评论。我写得很快,缺失的部分是常识,但如果被问到,我会完成解决方案。 - TecHunter
谢谢!你的例子非常清晰。但是我不太理解你的代码示例。你能告诉我这两者之间的基本实现差异吗?如果我要实现聚合或组合,我应该在Java中使用哪些概念? - Rajath
如果你谈论类,那么这个实现是完全相同的,但是组合应该通过管理实例的方式来反映,就像在我的编辑中一样。 - TecHunter
TecHunter,我正在寻找这个例子。您能否扩展Java代码以演示大学示例中的关联。该示例应如何在main()方法中运行(可以显示创建大学和删除教授的场景)。请帮忙。 - deepakl.2000
@deepakl.2000 不确定它会如何帮助,但是给你。 - TecHunter
显示剩余2条评论

5
首先我们需要谈论一下“聚合(Aggregation)”和“组合(Composition)”之间的实际区别,以达成共识。
其中,
聚合是一种关联,其中相关的实体可能独立于关联而存在。例如,一个人可能与一个组织相关联,但他/她在系统中可能具有独立的存在。

组合是指一种情况,其中一个相关的实体与另一个实体密切相关,不能没有另一个实体的存在而存在。实际上,该实体的身份始终与另一个对象的身份相关联。例如,汽车中的轮子。
现在,可以通过在一个实体中持有另一个实体的属性来简单地实现聚合,如下所示:
class Person {
    Organisation worksFor;
}

class Organisation {
    String name;
}

class Main {
    public static void main(String args[]) {

        //Create Person object independently
        Person p = new Person();

        //Create the Organisation independently
        Organisation o = new Organisation();
        o.name = "XYZ Corporation";

        /*
          At this point both person and organisation 
          exist without any association  
        */
        p.worksFor = o;

    }
}

在组合中,依赖对象必须始终使用其关联对象的标识符创建。您可以使用内部类来实现此目的。

class Car {
    class Wheel {
        Car associatedWith;
    }
}

class Main {
    public static void main() {
        //Create Car object independently
        Car car = new Car();

        //Cannot create Wheel instance independently
        //need a reference of a Car for the same.
        Car.Wheel wheel = car.new Wheel();
    }
}

请注意,在应用场景中,同一使用情况可能会归为聚合/组合。例如,如果您正在开发一个适用于在某个组织中工作的人的应用程序,并且参考组织对注册必须,则Person-Organisation案例可能变为组合。同样,如果您正在维护汽车零部件的库存,Car-Wheel关系可以是聚合。

5
一个简单的组合程序
public class Person {
    private double salary;
    private String name;
    private Birthday bday;

    public Person(int y,int m,int d,String name){
        bday=new Birthday(y, m, d);
        this.name=name;
    }


    public double getSalary() {
        return salary;
    }

    public String getName() {
        return name;
    }

    public Birthday getBday() {
        return bday;
    }

    ///////////////////////////////inner class///////////////////////
    private class Birthday{
        int year,month,day;

        public Birthday(int y,int m,int d){
            year=y;
            month=m;
            day=d;
        }

        public String toString(){
           return String.format("%s-%s-%s", year,month,day);

        }
    }

    //////////////////////////////////////////////////////////////////

}
public class CompositionTst {

    public static void main(String[] args) {
        // TODO code application logic here
        Person person=new Person(2001, 11, 29, "Thilina");
        System.out.println("Name : "+person.getName());
        System.out.println("Birthday : "+person.getBday());

        //The below object cannot be created. A bithday cannot exixts without a Person 
        //Birthday bday=new Birthday(1988,11,10);

    }
}

你能否也添加可工作的Java代码来解释关联和聚合,并像你在组合中所解释的那样解释所有情况?
  1. 如果它们被删除,关联中的场景是什么?
  2. 当父对象被删除时,聚合中的场景是什么?
- deepakl.2000

5

简单来说:

组合和聚合都是关联关系。 组合 -> 强Has-A关系 聚合 -> 弱Has-A关系。


4

聚合 vs 组合

聚合(Aggregation) 表示子对象(child)可以独立于父对象(parent)存在的关系。例如,银行和员工之间的关系,删除银行时员工仍然存在。

组合(Composition) 表示子对象(child)不能独立于父对象(parent)存在的关系。例如,人和心脏之间的关系,没有人就没有单独存在的心脏。

聚合关系是“拥有(has-a)” 的关系,而组合关系是“部分-整体(part-of)” 的关系。

组合(Composition) 是一种强关联,而聚合(Aggregation)是一种弱关联。


1
银行和员工应该是一种组合关系。因为如果你删除了银行,员工就不会继续存在。我们必须从系统的角度来思考,而不是现实世界。是的,人仍然存在。但是如果一个银行彻底破产,员工将被解雇 - 你不能成为不存在的银行的员工。更好的例子是银行分支机构和客户。如果你删除了银行的分支机构(它倒闭了),客户仍然存在,并有可能被分配到另一个分支机构。在移植/实验室的背景下,器官可以与人类分离存在。 - Boris

4

嗨,Rahul,虽然这个链接可能回答了问题,但最好在这里包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。 请看这里:为什么和如何删除一些答案? - bummi

3

区别在于任何组合都是聚合而不是相反。

让我们先搞清楚术语。聚合是UML标准中的元术语,表示组合和共享聚合,简称为“共享”。经常被错误地称为“聚合”,这是不好的,因为组合也是一种聚合。据我所知,你指的是“共享”。

根据UML标准:

组合 - 表示该属性以组合方式聚合,即组合对象对于组成对象(部分)的存在和存储负有责任。

因此,大学与教师协会之间的关系是一种组合,因为教师协会不存在于大学之外(我个人认为)。

共享聚合的精确定义因应用领域和建模者而异。

也就是说,如果您只遵循自己或他人的某些原则,那么所有其他关联都可以绘制为共享聚合。请参见此处


1
两种类型都是关联,而不是严格映射到像语言元素那样的东西。区别在于目的、上下文和系统建模方式。
以一个实际例子为例,比较两种具有类似实体的系统:
- 一个主要跟踪汽车及其所有者等信息的“汽车注册系统”。在这里,我们对发动机不感兴趣,因为它不是一个单独的实体,但我们可能仍然有与发动机相关的属性,如功率和燃料类型。在这里,发动机可能是汽车实体的组合部分。 - 一个管理汽车零件、维修汽车并更换零件(甚至可能是完整发动机)的“汽车维修店管理系统”。在这里,我们甚至可能有备有发动机,并需要将其及其他零件与汽车分开并独立跟踪。在这里,发动机可能是汽车实体的聚合部分。
如何在您的语言中实现这一点并不重要,因为在这个层面上,可读性等问题更加重要。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接