Teoria
Si dovrebbe implementare un'interfaccia comune quando:
-
Gli oggetti sono simili e hanno una logica simile, e:
-
La logica condivisa viene utilizzata in altri luoghi in cui la distinzione tra i tipi non è richiesta.
Ad esempio, non si dovrebbe implementare un'interfaccia comune per Dog: Animal
e RSS
, solo perché entrambi hanno un metodo comune Feed
. Non solo la logica dietro questi due metodi è diversa ( new Dog(...).Feed()
lancerebbe un robot che darà del cibo a un cane, mentre new RSS(...).Feed()
semplicemente scaricherà l'RSS più recente dal server), ma non riesci a immaginare un comune Feeder
che sarebbe responsabile del controllo del cibo per cani e durante il download degli ultimi feed RSS.
D'altro canto, sarebbe conveniente avere un'interfaccia comune sia per new Dog(...).Feed()
che new Goldfish(...).Feed()
, perché mentre l'implementazione sarebbe diversa, dal momento che il robot non dovrebbe mettere una ciotola di carne in un acquario , l'interfaccia comune consente comunque ai chiamanti di non fare alcuna differenza tra gli animali:
var myPets = new [] { (IAnimal)new Dog(...), new Goldfish(...) };
...
new FeedingEngine().FeedPets(myPets);
public class FeedingEngine
{
public void FeedPets(IEnumerable<IAnimal> pets)
{
foreach (var pet in pets)
{
if (pet.Status == AnimalStatus.Alive && pet.IsPotentiallyHungry)
{
pet.Feed();
}
}
}
}
Il tuo caso
Entrambe le tue proposte sono corrette. Dal punto di vista del consumatore di ICollisionDetector
, tutto quello che devo sapere è che posso passare qualsiasi coppia di oggetti spaziali e ottenere il risultato della collisione, non importa come.
C'è una leggera differenza tra questi approcci: è il fatto che la prima proposta richiede di ricontrollare se tutti gli oggetti che ereditano ISpatial
sono gestiti correttamente dal rilevatore di collisione. Può essere soggetto a errore in seguito: se, lavorando sul tuo codice in un secondo momento, un altro sviluppatore implementa un altro oggetto territoriale, diciamo Sphere: ISpatial
, ma dimentica di implementare la logica di collisione, Collides(sphere, box)
fallirà e sarebbe difficile capire perché sta fallendo, specialmente se il rilevatore di collisione è usato indirettamente, come attraverso il motore di gioco.
Il secondo approccio è un opt-in. Se il rilevatore di collisione non ha un Collides(Sphere s, Box b)
, nessuno potrebbe chiamare tale conversione. Detto questo, questo stesso crea un altro problema: non sarai in grado di usare generici o astrazioni di alto livello. Ad esempio:
var box = new Box(...);
var torus = new Torus(...);
var engine = new GameEngine();
engine.Show(new [] { box, torus });
engine.StartColliding(); // Moves objects to the center of the map and stops on collision.
è facile da implementare con la tua prima proposta se sia Box
che Torus
implementano ISpatial
, ma sarebbe estremamente difficile da fare con la tua seconda proposta.
Per poter utilizzare la tua prima proposta riducendo il problema che ho notato sopra, ecco una terza proposta:
public interface ISpatial
{
bool IsColliding(ISpatial obj);
}
public class CollisionDetector
{
public bool IsColliding(ISpatial s1, ISpatial s2)
{
return s1.IsColliding(s2);
}
}
public class Torus : ISpatial
{
public bool IsColliding(ISpatial obj)
{
// Check that obj is not null.
// Do the collision logic for known types.
var box = obj as Box;
if (box != null)
{
// Do the collision logic.
return result;
}
return obj.IsColliding(this);
}
}
public class Cube : ISpatial
{
public bool IsColliding(ISpatial obj)
{
// Check that obj is not null.
// Do the collision logic for known types.
return obj.IsColliding(this);
}
}
Poiché la classe cubo viene creata per prima, non è a conoscenza del toro; invece di lanciare un NotImplementedException
, semplicemente delega la logica di rilevamento collisione al toro, che è ben consapevole dell'oggetto cubo, poiché la classe torus era la seconda da creare.
Nota: ovviamente, devi ancora gestire le situazioni in cui né il primo né il secondo oggetto sono a conoscenza l'uno dell'altro (ad esempio se due oggetti sono stati creati da due sviluppatori diversi nello stesso momento). Il codice sopra riportato sarà semplicemente overflow e crash, ma puoi facilmente modificarlo per generare NotImplementedException
.