Java中的关联,组合和聚合

作者: Arvin Chen 分类: Java 来源: Break易站(www.breakyizhan.com)
  •   Java 面向对象/Java 多态

    Java中的关联

    关联是通过其对象建立的两个不同类别之间的关系。Java中的关联 可以是一对一,一对多,多对一,多对多的。
    在面向对象编程中,对象与其他对象通信以使用该对象提供的功能和服务。组合和聚合是两种形式的关联。
    Java中的关联,组合和聚合

    // Java program to illustrate the 
    // concept of Association
    import java.io.*;
    
    // class bank
    class Bank 
    {
        private String name;
        
        // bank name
        Bank(String name)
        {
            this.name = name;
        }
        
        public String getBankName()
        {
            return this.name;
        }
    } 
    
    // employee class 
    class Employee
    {
        private String name;
        
        // employee name 
        Employee(String name) 
        {
            this.name = name;
        }
        
        public String getEmployeeName()
        {
            return this.name;
        } 
    }
    
    // Association between both the 
    // classes in main method
    class Association 
    {
        public static void main (String[] args) 
        {
            Bank bank = new Bank("Axis");
            Employee emp = new Employee("Neha");
            
            System.out.println(emp.getEmployeeName() + 
                   " is employee of " + bank.getBankName());
        }
    }
    

    输出:

    Neha is employee of Axis

    在上面的例子中,银行和员工通过他们的对象关联了两个独立的类。银行可以有很多员工,所以它是一对多的关系。
    Java中的关联,组合和聚合

    Java中的聚合

    这是一种特殊的协会形式,其中:

    • 它代表了Has-A的关系。
    • 这是一种单向关联,即单向关系。例如,部门可以有学生,反之亦然,因此是单向的。
    • 在汇总中,两个条目都可以单独存活,这意味着结束一个实体不会影响另一个实体
    // Java program to illustrate
    //the concept of Aggregation.
    import java.io.*;
    import java.util.*;
    
    // student class
    class Student 
    {
        String name;
        int id ;
        String dept;
        
        Student(String name, int id, String dept) 
        {
            
            this.name = name;
            this.id = id;
            this.dept = dept;
            
        }
    }
    
    /* Department class contains list of student
    Objects. It is associated with student
    class through its Object(s). */
    class Department 
    {
        
        String name;
        private List<Student> students;
        Department(String name, List<Student> students) 
        {
            
            this.name = name;
            this.students = students;
            
        }
        
        public List<Student> getStudents() 
        {
            return students;
        }
    }
    
    /* Institute class contains list of Department
    Objects. It is asoociated with Department
    class through its Object(s).*/
    class Institute 
    {
        
        String instituteName;
        private List<Department> departments;
        
        Institute(String instituteName, List<Department> departments)
        {
            this.instituteName = instituteName;
            this.departments = departments;
        }
        
        // count total students of all departments
        // in a given institute 
        public int getTotalStudentsInInstitute()
        {
            int noOfStudents = 0;
            List<Student> students; 
            for(Department dept : departments)
            {
                students = dept.getStudents();
                for(Student s : students)
                {
                    noOfStudents++;
                }
            }
            return noOfStudents;
        }
        
    } 
    
    // main method
    class GFG
    {
        public static void main (String[] args) 
        {
            Student s1 = new Student("Mia", 1, "CSE");
            Student s2 = new Student("Priya", 2, "CSE");
            Student s3 = new Student("John", 1, "EE");
            Student s4 = new Student("Rahul", 2, "EE");
        
            // making a List of 
            // CSE Students.
            List <Student> cse_students = new ArrayList<Student>();
            cse_students.add(s1);
            cse_students.add(s2);
            
            // making a List of 
            // EE Students
            List <Student> ee_students = new ArrayList<Student>();
            ee_students.add(s3);
            ee_students.add(s4);
            
            Department CSE = new Department("CSE", cse_students);
            Department EE = new Department("EE", ee_students);
            
            List <Department> departments = new ArrayList<Department>();
            departments.add(CSE);
            departments.add(EE);
            
            // creating an instance of Institute.
            Institute institute = new Institute("BITS", departments);
            
            System.out.print("Total students in institute: ");
            System.out.print(institute.getTotalStudentsInInstitute());
        }
    }
    

    输出:

    Total students in institute: 4

    在这个例子中,有一个研究所没有。像CSE,EE这样的部门。每个部门都没有。学生的。因此,我们创建一个具有Object或No的引用的Institute类。部门类的对象(即对象列表)。这意味着Institute类通过其对象与Department类相关联。而且Department类还引用了Object类或Object对象(即对象列表),这意味着它通过其对象与Student类相关联。
    它代表了一种Has-A关系。
    Java中的关联,组合和聚合

    我们什么时候使用Aggregation?
    代码重用最好通过聚合来实现。

    Java中的组合

    组合是一种聚合的限制形式,其中两个实体高度相互依赖。

    • 它代表了部分关系。
    • 在构成中,两个实体都相互依赖。
    • 当两个实体之间存在合成时,如果没有其他实体,合成对象就不能存在。

    让我们以图书馆为例。

    // Java program to illustrate 
    // the concept of Composition
    import java.io.*;
    import java.util.*;
    
    // class book
    class Book 
    {
    
        public String title;
        public String author;
        
        Book(String title, String author)
        {
            
            this.title = title;
            this.author = author;
        }
    }
    
    // Libary class contains 
    // list of books.
    class Library 
    {
    
        // reference to refer to list of books.
        private final List<Book> books;
        
        Library (List<Book> books)
        {
            this.books = books; 
        }
        
        public List<Book> getTotalBooksInLibrary(){
            
           return books;  
        }
        
    }
    
    // main method
    class GFG 
    {
        public static void main (String[] args) 
        {
            
            // Creating the Objects of Book class.
            Book b1 = new Book("EffectiveJ Java", "Joshua Bloch");
            Book b2 = new Book("Thinking in Java", "Bruce Eckel");
            Book b3 = new Book("Java: The Complete Reference", "Herbert Schildt");
            
            // Creating the list which contains the 
            // no. of books.
            List<Book> books = new ArrayList<Book>();
            books.add(b1);
            books.add(b2);
            books.add(b3);
            
            Library library = new Library(books);
            
            List<Book> bks = library.getTotalBooksInLibrary();
            for(Book bk : bks){
                
                System.out.println("Title : " + bk.title + " and " 
                +" Author : " + bk.author);
            }
        }
    }
    
    

    输出

    Title : EffectiveJ Java and  Author : Joshua Bloch
    Title : Thinking in Java and  Author : Bruce Eckel
    Title : Java: The Complete Reference and  Author : Herbert Schildt

    在上面的例子中,一个库可能没有。关于相同或不同主题的书籍。所以,如果图书馆被破坏,那么在该图书馆内的所有图书都将被销毁。即没有图书馆的书不能存在。这就是为什么它是组合。

    Java中的聚合与组合

    1. 依赖关系:聚合意味着孩子可以独立于父母而存在的关系。例如,银行和员工,删除银行和员工仍然存在。而构图意味着孩子不能独立于父母而存在的关系。例如:人类和心脏,不存在与人类分开的心脏
    2. 关系类型:聚合关系是“has-a”,组合是“部分关系”。
    3. 结社类型:结构是一个强大的协会,而汇总是一个弱联合。
    // Java program to illustrate the
    // difference between Aggregation
    // Composition.
    
    import java.io.*;
    
    // Engine class which will 
    // be used by car. so 'Car'
    // class will have a field 
    // of Engine type.
    class Engine 
    {
        // starting an engine.
        public void work()
        {
            
            System.out.println("Engine of car has been started ");
            
        }
        
    }
    
    // Engine class
    final class Car 
    {
        
        // For a car to move, 
        // it need to have a engine.
        private final Engine engine; // Composition
        //private Engine engine;     // Aggregation
        
        Car(Engine engine)
        {
            this.engine = engine;
        }
        
        // car start moving by starting engine
        public void move() 
        {
            
            //if(engine != null)
            {
                engine.work();
                System.out.println("Car is moving ");
            }
        }
    }
    
    class GFG 
    {
        public static void main (String[] args) 
        {
            
            // making an engine by creating 
            // an instance of Engine class.
            Engine engine = new Engine();
            
            // Making a car with engine.
            // so we are passing a engine 
            // instance as an argument while
            // creating instace of Car.
            Car car = new Car(engine);
            car.move();
            
        }
    }
    

    输出:

    Engine of car has been started 
    Car is moving

    在聚合的情况下,汽车也通过引擎执行其功能。但发动机并不总是汽车的内部部件。发动机可以换出来,甚至可以从车上取下。这就是为什么我们使引擎类型字段不是最终的。

    说明Java中继承与组合的概念,以及它们之间的联系与区别。首先文章会给出一小段代码示例,用于展示到底什么是继承。然后演示如何通过“组合”来改进这种继承的设计机制。最后总结这两者的应用场景,即到底应该选择继承还是组合。

    对比Java中的继承和组合

    Java 继承

    假设我们有一个名为Insect(昆虫)的类,这个类包含两个方法:1)移动move(); 2)攻击attack()。
    代码如下:

    class Insect {
    private int size;
    private String color;
    
    public Insect(int size, String color) {
    this.size = size;
    this.color = color;
    }
    
    public int getSize() {
    return size;
    }
    
    public void setSize(int size) {
    this.size = size;
    }
    
    public String getColor() {
    return color;
    }
    
    public void setColor(String color) {
    this.color = color;
    }
    
    public void move() {
    System.out.println("Move");
    }
    
    public void attack() {
    move();  //假设昆虫在攻击前必须要先移动一次
    System.out.println("Attack");
    }
    }

    现在,你想要定义一个名为Bee(蜜蜂)的类。Bee(蜜蜂)是Insect(昆虫)的一种,但实现了不同于Insect(昆虫)的attack()和move方法。这时候我们可以用继承的设计机制来实现Bee类,就像下面的代码一样:

    class Bee extends Insect {
    public Bee(int size, String color) {
    super(size, color);
    }
    
    public void move() {
    System.out.println("Fly");
    }
    
    public void attack() {
    move();
    super.attack();
    }
    }
    public class InheritanceVSComposition {
    public static void main(String[] args) {
    Insect i = new Bee(1, "red");
    i.attack();
    }
    }

    InheritanceVSComposition作为一个测试类,在其main方法中生成了一个Bee类的实例,并赋值给Insect类型的引用变量 i。所以调用i的attack方法时,对应的是Bee类实例的attack方法,也就是调用了Bee类的attack方法。

    类的继承结构图如下,非常简单:

    输出:

    Fly
    Fly
    Attack

    Fly被打印了两次,也就是说move方法被调用了两次。但按理来讲,move方法只应当被调用一次,因为无论是昆虫还是蜜蜂,一次攻击前只移动一次。

    问题出在子类(即Bee类)的attack方法的重载代码中,也就是super.attack()这一句。因为在父类(即Insect类)中,调用 attack方法时会先调用move方法,所以当子类(Bee)调用super.attack()时,相当于也同时调用了被重写的move方法(注意是子 类被重载的move方法,而不是父类的move方法)。

    为了解决这个问题,我们可以采取以下办法:

    1. 删除子类的attack方法。这么做会使得子类的attack方法的实现完全依赖于父类对于该方法的实现(因为子类继承了父类的attack方法)。如果 父类的attack方法不受控制而产生了变更。比如说,父类的attack方法中调用了另外的move方法,那么子类的attack方法也会产生相应的变 化,这是一种很糟糕的封装。
    2. 也可以重写子类的attack方法,像下面这样:
    public void attack() {
    move();
    System.out.println("Attack");
    }

    这样保证了结果的正确性,因为子类的attack方法不再依赖于父类。但是,子类attack方法的代码与父类产生了重复(重复的attack方法会使得很多事情变得复杂,不仅仅是多打印了一条输出语句)。所以第二种办法也不行,它不符合软件工程中关于重用的思想。

    如此看来,继承机制是有缺点的:子类依赖于父类的实现细节,如果父类产生了变更,子类的后果将不堪设想。

    2、组合

    在上面的例子中,可以用组合的机制来替代继承。我们先看一下运用组合如何实现。

    attack这一功能不再是一个方法,而是被抽象为一个接口。

    interface Attack {
    public void move();
    public void attack();
    }

    通过对Attack接口的实现,就可以在实现类当中定义不同类型的attack。

    class AttackImpl implements Attack {
    private String move;
    private String attack;
    
    public AttackImpl(String move, String attack) {
    this.move = move;
    this.attack = attack;
    }
    
    @Override
    public void move() {
    System.out.println(move);
    }
    
    @Override
    public void attack() {
    move();
    System.out.println(attack);
    }
    }

    因为attack功能已经被抽象为一个接口,所以Insect类不再需要有attack方法。

    class Insect {
    private int size;
    private String color;
    
    public Insect(int size, String color) {
    this.size = size;
    this.color = color;
    }
    
    public int getSize() {
    return size;
    }
    
    public void setSize(int size) {
    this.size = size;
    }
    
    public String getColor() {
    return color;
    }
    
    public void setColor(String color) {
    this.color = color;
    }
    }

    Bee类一种Insect类,它具有attack的功能,所以它实现了attack接口:

    // 这个封装类封装了一个Attack类型的对象
    class Bee extends Insect implements Attack {
    private Attack attack;
    
    public Bee(int size, String color, Attack attack) {
    super(size, color);
    this.attack = attack;
    }
    
    public void move() {
    attack.move();
    }
    
    public void attack() {
    attack.attack();
    }
    }

    类图:

    测试类代码,将AttackImpl的实例作为Attack类型的参数传给Bee类的构造函数:

    public class InheritanceVSComposition2 {
    public static void main(String[] args) {
    Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
    a.attack();
    
    // if you need another implementation of move()
    // there is no need to change Insect, we can quickly use new method to attack
    
    Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
    b.attack();
    }
    }
    fly
    move
    fly
    sting

    3、什么时候该用继承,什么时候该用组合?

    以下两条原则说明了应该如何选择继承与组合:

    • 如果存在一种IS-A的关系(比如Bee“是一个”Insect),并且一个类需要向另一个类暴露所有的方法接口,那么更应该用继承的机制。
    • 如果存在一种HAS-A的关系(比如Bee“有一个”attack功能),那么更应该运用组合。

    总结来说,继承和组合都有他们的用处。只有充分理解各对象和功能之间的关系,才能充分发挥这两种机制各自的优点。

  •   Java 面向对象/Java 多态
  •   本文标题:Java中的关联,组合和聚合 - Break易站
    转载请保留页面地址:https://www.breakyizhan.com/java/3845.html
      微信返利机器人
      免费:淘宝,京东,拼多多优惠券
      腾讯,爱奇艺,优酷的VIP视频免费解析,免费看
      即刻扫描二维码,添加微信机器人!

    发表笔记

    电子邮件地址不会被公开。 必填项已用*标注