Pattern per stabilire in modo sicuro una relazione bidirezionale uno a uno

1

Supponiamo di avere 2 classi, Pilot e Plane , in una relazione uno-a-uno opzionale. Quindi un Plane potrebbe avere un pilota quando sta volando, ma quando è nel suo hangar non ne ha. Simile per Pilot .

Sarebbe semplicemente logico tenere un puntatore al Plane in Pilot e un puntatore al Pilot in Plane , accessibile tramite getter e setter.

Costruire i setter in modo che una chiamata su uno di essi stabilisca che la relazione bidirezionale è sorprendentemente difficile. Sono rimasto ancora più sorpreso dal fatto che non sono riuscito a trovare una soluzione adeguata sul web in quanto questo costrutto mi sembra comune.

Qualcuno sa di concetti che potrebbero incapsulare una tale relazione all'interno di una classe? O un idioma per rimanere con normali getter / setter e stabilire ancora la relazione con una chiamata setPilot / setPlane?

    
posta hllnll 02.08.2014 - 13:28
fonte

3 risposte

3

Supponiamo che questo sarà sempre 1-1 (ad esempio, non otterrai 2 piloti da un aereo per rappresentare pilota e copilota). Invece di provare a costruire questa relazione nelle classi Plane o Pilot, usa una terza classe che fornisce la mappatura. Per mancanza di un nome migliore, chiamiamolo PilotPlaneMap qui. Questa classe potrebbe avere le seguenti funzioni pubbliche:

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

}

Non sei sicuro di quale lingua stai usando, ma internamente useresti qualche hashmap per memorizzare le relazioni. Si potrebbero usare due dizionari. Uno che mappa Pilot- > Plane e le altre mappe Plane- > Pilot. Questi sono entrambi impostati quando si chiama RelatePilotAndPlane (). Questa è solo una opzione.

    
risposta data 02.08.2014 - 16:28
fonte
0

Prevedo questo dicendo che penso che il suggerimento di mappatura sia migliore. Tuttavia, credo che il codice seguente funzionerà:

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;
    }
}

E alcuni test superficiali:

    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());
    
risposta data 02.08.2014 - 18:31
fonte
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.

Il modello che stai cercando è aggregazione (qui ad esempio 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

Ogni volta che vuoi sapere chi è il pilota di plane1 , puoi chiedere all'aereo:

plane1.pilot

Questa soluzione è semplice, intuitiva, ma presenta un piccolo inconveniente:

Per tagliare la relazione tra entrambi, devi manipolare entrambe le istanze.

Un altro problema si verifica se in un secondo momento decidi che hai bisogno di una JSON -Rappresentazione dei tuoi aerei e dei loro piloti. Quindi hai una dipendenza circolare: un piano ha un pilota, che ha un piano, che ha un pilota ... hai capito?

Naturalmente ci sono modi per aggirare questo problema (ad es. in Java hai @JsonManagedReferenc e @JsonBackReference quando usi JACKSON ).

Nel contesto di ORM questa è una soluzione comune: costruire un one-to-one -Relazione tramite aggregazione . Il framework si occupa della mappatura relazionale.

D'altra parte: come @ user144552 ha detto nella sua risposta, una soluzione in forma di Mappa potrebbe essere la strada da percorrere. Se lavori in-memory costruendo una Mappa è la soluzione più veloce, dato che hai O (1) con una Mappa .

Ma se lavorassi con un DB, dovresti interrogare il DB e lasciare che il DB faccia il filtro

Dipende dal caso, quale tipo di implementazione sembra utile.

    
risposta data 02.08.2014 - 19:13
fonte

Leggi altre domande sui tag