1

Let suppose we have 2 classes, Pilot and Plane, being in an optional one-to-one relationship. So a Plane might have a pilot when it's flying, but when it's standing in its hangar, it has none. Similar for the Pilot.

It would be just logical to hold a pointer to the Plane in the Pilot and a pointer to the Pilot in the Plane, accessible via getters and setters.

Constructing the setters so that a call on any of them establishes the bidirectional relationship is surprisingly hard. I was even more surprised that I couldn't find a suitable solution on the web as this construct seems to be common to me.

Does anyone know of concepts that would encapsulate such a relationship inside a class? Or an idiom to stick with normal getters/setters and still establish the relationship with one setPilot/setPlane call?

3 Answers 3

3

Let's assume that this will always be 1-1 (e.g., you wont get 2 pilots from a plane to represent pilot and co-pilot). Instead of trying to build this relationship into the Plane or Pilot classes, use a third class that provides the mapping. For lack of a better name, let's call it PilotPlaneMap here. This class could have the following public functions:

class PilotPlaneMap
{
public void RelatePilotAndPlane(Pilot pilot, Plane plane){..} //make the relationship here
public Pilot GetPilot(Plane plane){..} //retrieve the pilot related to given plane
public Plane GetPlane(Pilot pilot){..} //retrieve the plane related to given pilot

}

Not sure what language you are using, but internally you would make use of some hashmap to store the relationships. Two dictionaries could be used. One that maps Pilot->Plane, and the other maps Plane->Pilot. These are both set when calling RelatePilotAndPlane(). That's just one option.

2
  • I had hoped for a solution that keeps the getter/setter schema, without having the same pattern of code all over the project (there are of course many classes that are in a 1-to-1 relation). After all yours seems to be a reasonable solution, thanks.
    – hllnll
    Aug 2, 2014 at 18:33
  • Sounds like the job for a FlightPlan! Aug 3, 2014 at 4:12
0

I will preface this by saying I think that the mapping suggestion is better. However, I believe the code below will work:

public final class Pilot {
    private Plane plane;

    public Plane getPlane() {
        return plane;
    }

    public void setPlane(final Plane newPlane) {
        if (this.plane == newPlane)
            return;

        final Plane oldPlane = this.plane;
        this.plane = newPlane;

        if (oldPlane != null)
            oldPlane.setPilot(null);

        if (newPlane != null)
            newPlane.setPilot(this);

        this.plane = newPlane;
    }
}

public final class Plane {
    private Pilot pilot;

    public Pilot getPilot() {
        return pilot;
    }

    public void setPilot(final Pilot newPilot) {
        if (this.pilot == newPilot)
            return;

        final Pilot oldPilot = this.pilot;
        this.pilot = newPilot;

        if (oldPilot != null)
            oldPilot.setPlane(null);

        if (newPilot != null)
            newPilot.setPlane(this);

        this.pilot = newPilot;
    }
}

And some cursory testing:

    Plane plane1 = new Plane();
    Plane plane2 = new Plane();
    Pilot pilot1 = new Pilot();
    Pilot pilot2 = new Pilot();

    pilot1.setPlane(plane1);
    Assert.assertEquals(plane1, pilot1.getPlane());
    Assert.assertNull(pilot2.getPlane());

    pilot2.setPlane(plane1);
    Assert.assertNull(pilot1.getPlane());
    Assert.assertEquals(plane1, pilot2.getPlane());

    pilot1.setPlane(null);
    pilot2.setPlane(null);
    Assert.assertNull(plane1.getPilot());
    Assert.assertNull(pilot1.getPlane());
    Assert.assertNull(pilot2.getPlane());

    plane1.setPilot(pilot1);
    Assert.assertEquals(plane1, pilot1.getPlane());
    Assert.assertNull(pilot2.getPlane());

    plane1.setPilot(pilot2);
    Assert.assertNull(pilot1.getPlane());
    Assert.assertEquals(plane1, pilot2.getPlane());

    plane1.setPilot(null);
    plane2.setPilot(null);

    plane1.setPilot(pilot1);
    plane2.setPilot(pilot2);
    Assert.assertEquals(plane1, pilot1.getPlane());
    Assert.assertEquals(pilot1, plane1.getPilot());
    Assert.assertEquals(plane2, pilot2.getPlane());
    Assert.assertEquals(pilot2, plane2.getPilot());

    plane1.setPilot(pilot2);
    Assert.assertEquals(plane1, pilot2.getPlane());
    Assert.assertEquals(pilot2, plane1.getPilot());
    Assert.assertNull(pilot1.getPlane());
    Assert.assertNull(plane2.getPilot());
0

It would be just logical to hold a pointer to the Plane in the Pilot and a pointer to the Pilot in the Plane, accessible via getters and setters.

The pattern you are looking for is aggregation (here e.g. in Ruby):

class Plane
    attr_accessor :pilot
end

class Pilot
  attr_accessor :plane
end

plane1=Plane.new
pilot1=Pilot.new
plane1.pilot=pilot1
pilot1.plane=plane1

Every time you want to know, who is pilot of plane1, you could ask the plane:

plane1.pilot

This solution is simple, intuitive - but has a small drawback:

In order to cut the relation between both, you have to manipulate both instances.

Another problem occurs if you at a later point decide, that you need a JSON-Representation of your Planes and their pilots. Then you have a circular dependency: a plane has a pilot, which has a plane, which has a pilot ... you get it?

Of course there are ways around this problem (e.g. in Java you have @JsonManagedReferenc and @JsonBackReference when using JACKSON).

In the context of ORM this is a common solution: building a one-to-one-Relation via aggregation. The framework takes care of the relational mapping.

On the other hand: like @user144552 said in his answer, a solution in form of a Map might be the way to go. If you are working in-memory building a Map is the fastest solution, since you have O(1) with a Map.

But if you were working wih a DB, you would have to query the DB and let the DB doing the filtering

It depends on the usecase, what kind of implementation seems useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.