2010-10-24 21 views
18

Actualmente estoy luchando con un problema de dependencia circular al diseñar mis clases.OO diseño y dependencias circulares

Desde que leí sobre el Anemic Domain Model (algo que hacía todo el tiempo), realmente he estado tratando de evitar crear objetos de dominio que fueran solo "cubos de getters y setters" y regresar a mis raíces OO.

Sin embargo, el problema siguiente es uno con el que me encuentro mucho y no estoy seguro de cómo debería resolverlo.

Digamos que tenemos una clase equipo, que tiene muchos jugadores . No importa qué deporte sea este :) Un equipo puede agregar y eliminar jugadores, de la misma forma en que un jugador puede abandonar un equipo y unirse a otro.

Así que tenemos el equipo, que tiene una lista de jugadores:

public class Team { 

    private List<Player> players; 

    // snip. 

    public void removePlayer(Player player) { 
     players.remove(player); 
     // Do other admin work when a player leaves 
    } 
} 

entonces tenemos el jugador, que tiene una referencia al Equipo:

public class Player { 
    private Team team; 

    public void leaveTeam() { 
     team = null; 
     // Do some more player stuff... 
    } 
} 

Uno puede asumir que tanto Los métodos (eliminar y salir) tienen una lógica específica de dominio que debe ejecutarse siempre que un equipo elimine un jugador y un jugador deje un equipo. Por lo tanto, lo primero que pensé es que cuando un equipodar una patada al jugador, removePlayer (...) También debe llamar al método player.leaveTeam() ...

Pero ¿y si el jugador está impulsando la partida: ¿debería el método leaveTeam() llamar a team.removePlayer (this)? ¡No sin crear un ciclo infinito!

En el pasado, acababa de hacer estos objetos POJOs "tontos" y tenía una capa de servicio que hacía el trabajo. Pero incluso ahora todavía estoy quedo con ese problema: para evitar dependencias circulares, la capa de servicios todavía tiene vincular todo junto - es decir,

public class SomeService { 

    public void leave(Player player, Team team) { 

     team.removePlayer(player); 
     player.leaveTeam(); 

    } 

} 

¿Estoy sobre complicando esto? Tal vez me falta algún defecto obvio de diseño. Cualquier comentario sería muy apreciado.


Gracias a todos por las respuestas. Estoy aceptando la solución Grodriguez ya que es la más obvia (no puedo creer que no se me haya ocurrido) y es fácil de implementar. Sin embargo, DecaniBass es un buen punto. En la situación que describo, es posible que un jugador abandone un equipo (y sepa si está en un equipo o no), así como también el equipo que dirige la eliminación. Pero estoy de acuerdo con su punto y no me gusta la idea de que haya dos "puntos de entrada" en este proceso. Gracias de nuevo.

Respuesta

14

Puede romper la dependencia circular agregando guardias para verificar si el equipo todavía tiene el jugador/el jugador todavía está en el equipo.Por ejemplo:

En la clase Team:

public void removePlayer(Player player) { 
    if (players.contains(player)) 
    { 
     players.remove(player); 
     player.leaveTeam(); 
     // Do other admin work when a player leaves 
    } 
} 

En la clase Player:

public void leaveTeam() { 
    if (team != null) 
    { 
     team.removePlayer(this); 
     team = null; 
     // Do some more player stuff.. 
    } 
} 
+2

Puede ser solo yo, pero me gusta usar si ... lo más con moderación posible. Me he dado cuenta de que hace que el código sea menos fácil de mantener –

+4

players.remove() devolverá true si se cambió la colección; no es necesario hacer los .contains(). – KarlP

+0

@KarlP: Lo sé, pero pensé que la verificación explícita aclararía la lógica. – Grodriguez

1
public void removePlayer(Player player) { 
    if (players.contains(player)) { 
     players.remove(player); 
     player.leaveTeam(); 
    } 
} 

Ditto inside leaveTeam.

2

idea es hacer cosas relacionadas con el dominio de diferentes métodos que no llaman entre sí, pero lo hace de dominio relacionado cosas para su propio objeto, es decir, el método del equipo lo hace para el equipo y el método del jugador lo hace para el jugador

public class Team { 

    private List<Player> players; 

    public void removePlayer(Player player) { 
     removePlayerFromTeam(player); 
     player.removeFromTeam(); 
    } 
    public void removePlayerFromTeam(Player player) { 
     players.remove(player); 
     //domain stuff 
    } 
} 

public class Player { 
    private Team team; 

    public void removeFromTeam() { 
     team = null; 
     //domain stuff 
    } 
    public void leaveTeam() { 
     team.removePlayerFromTeam(this); 
     removeFromTeam(); 
    } 

} 
+1

El método 'leaveTeam()' arrojaría un NPE cuando llame 'team.removePlayerFromTeam()' después de configurar 'team = null'. – Grodriguez

+0

También en esta solución, llamar 'player.leaveTeam()' en realidad no elimina al jugador de la lista del jugador en el objeto del equipo. Del mismo modo, al llamar 'team.removePlayer()' no se establecerá la var 'team' en' null' en el objeto del jugador. – Grodriguez

+1

En este diseño, los métodos que contienen el código específico del dominio deberían ser privados del paquete y no públicos, creo. Pero definitivamente es la ruta que tomaría. – Waldheinz

7

Ben,

Comenzaría preguntando si un jugador puede (lógicamente, legalmente) retirarse del equipo. Diría que el jugador no sabe en qué equipo está (!), Es parte de un equipo. Por lo tanto, elimine Player#leaveTeam() y realice todos los cambios del equipo a través del método Team#removePlayer().

En el caso en el que sólo tiene un jugador y la necesidad de retirarlo de su equipo, entonces usted podría tener un método de búsqueda estática en el equipo public static Team findTeam(Player player) ...

Sé que esto es menos satisfactorio y natural que un método Player#leaveTeam(), pero en mi experiencia todavía puedes tener un modelo de dominio significativo.

2 vías referencias (Padres -> Infantil y Child-> Padres) son a menudo lleno de otras cosas, por ejemplo recolección de basura, el mantenimiento de la "integridad referencial", etc.

El diseño es un compromiso!

Cuestiones relacionadas