Is this considered a cyclic dependency (is this good practice)?

Tags: ,



Hi I am a new Java programmer and have a small question about class design.

I understand that something like this is a cyclic dependency and is probably not a way to structure a project:

public class Course {
    private ArrayList<Student> students;

    public Course (ArrayList<Student> students) {
        this.students = students;
    }
}


public class Student {
    private Course course;

    public Student (Course course) {
        this.course = course;
    }
}

But what if Student.java is changed to:

public class Student {
    private int courseId;

    public Student (int courseId) {
        this.courseId = courseId;
    }
}

so that courseId can be used to retrieve the course from a DAO or something. Is this still a good structure? Since now each Course still cares about Students. and each Student still cares about Courses.

Answer

Reciprocal references are fine, all database entities exist side-by-side referring to each other.

The constructors must allow the creation, not being barred that they only can be created if the other already exists.

Hence maybe two constructors.

public class Course {
    private List<Student> students = new ArrayList<>();

    public Course() {
    }

    public Course (List<Student> students) {
        this.students.addAll(students);
    }
}


public class Student {
    private Course course;

    public Student (Course course) {
        this.course = course;
        course.addStudent(this);
    }
}

With databases one often has numeric IDs, but many Object/Relational mappings can still allow you the classes above, under the hood using the IDs (JPA, eclipseLink, hibernate). It is not needed to have assymetrically IDs and object references.

You should not use concrete implementations (ArrayList) but be most flexible (List).

Also it might be better not to expose the field’s internal data (students) for changing outside.


About generalisations (List) and implementations (ArrayList)

Some (scripting) languages have just one type for a collection. Java was designed to provide several implementations for one interface.

So you can decide whether to implement a data structure for Map as fast HashMap or ordered TreeMap. In general the users only need to now about Map. You need not overspecify your code, and can even redesign simply using a different implementation class.

List<String> list = ...
Collections.sort(list);
list.add(...);

List<String> convert(List<String> list) { ... }

In the code above your convert can deal with any kind of List; you have written a more generic algorithm not just for an ArrayList. And in the method you can either return an ArrayList or a LinkedList. This makes code changes due to the wrong overspecification unlikely.

Coding Against Interfaces of Nick Hodges (pardon the wrong electric outlets and TypeScript).



Source: stackoverflow