32 votes

Comment utiliser l'injection de dépendance sans casser l'encapsulation?

Comment puis-je effectuer l'injection de dépendance sans casser l'encapsulation?

À l'aide d'une Injection de Dépendance exemple de Wikipedia:

public Car {
    public float getSpeed();
}

Remarque: d'Autres méthodes et propriétés (par exemple PushBrake(), PushGas(), SetWheelPosition() ) omis par la clarté

Cela fonctionne bien; vous ne savez pas comment mon objet implémente getSpeed - il est "encapsulé".

En réalité, mon objet implémente getSpeed comme:

public Car {
    private m_speed;
    public float getSpeed( return m_speed; );
}

Et tout est bien. Quelqu'un construit mon Car objet, brasse les pédales, la corne, le volant et la voiture répond.

Maintenant, disons que j'ai modifier une mise en œuvre interne de détail de ma voiture:

public Car {
    private Engine m_engine;
    private float m_currentGearRatio;
    public float getSpeed( return m_engine.getRpm*m_currentGearRatio; );
}

Tout est bien. L' Car est de bonnes OO-principes, en masquant les détails de la façon dont quelque chose est fait. Cela libère de l'appelant pour résoudre ses problèmes, plutôt que d'essayer de comprendre comment une voiture qui fonctionne. Il me donne aussi la liberté de changer de mon oeuvre, comme je l'entends.

Mais l'injection de dépendance serait me forcer à exposer ma classe à un Engine objet que je n'ai pas de créer ou d'initialisation. Même pire, c'est que j'ai maintenant exposés que mes Car même a un moteur:

public Car {
   public constructor(Engine engine);
   public float getSpeed();
}

Et maintenant, la parole extérieure est conscient du fait que j'utilise un Engine. Je n'ai pas toujours l'utilisation d'un moteur, je peux avoir envie de ne pas utiliser un Engine dans le futur, mais je ne peux plus modifier mes internes de mise en œuvre:

public Car {
    private Gps m_gps;
    public float getSpeed( return m_gps.CurrentVelocity.Speed; )
}

sans rupture de l'appelant:

public Car {
   public constructor(Gps gps);
   public float getSpeed();
}

Mais l'injection de dépendance ouvre un ensemble de vers: par l'ouverture de l'ensemble de la boîte de pandore. L'Injection de dépendance exige que tous mes objets privés détails de mise en œuvre de l'exposition. La consommation de mon Car classe a maintenant de comprendre, et de les traiter, tous précédemment cachés interne subtilités de ma classe:

public Car {
   public constructor(
       Gps gps, 
       Engine engine, 
       Transmission transmission,
       Tire frontLeftTire, Tire frontRightTire, Tire rearLeftTire, Tire rearRightTire, 
       Seat driversSeat, Seat passengersSeat, Seat rearBenchSeat,
       SeatbeltPretensioner seatBeltPretensioner,
       Alternator alternator, 
       Distributor distributor,
       Chime chime,
       ECM computer,
       TireMonitoringSystem tireMonitor
       );
   public float getSpeed();
}

Comment puis-je utiliser les vertus de l'Injection de Dépendance à l'aide de tests unitaires, tandis que ne pas rompre les vertus de l'encapsulation pour aider à la convivialité?

Voir aussi


Pour l'amour du plaisir, je peux diminuer le getSpeed exemple pour tout ce qui est nécessaire:

public Car {
   public constructor(
       Engine engine, 
       Transmission transmission,
       Tire frontLeftTire, Tire frontRightTire
       TireMonitoringSystem tireMonitor,
       UnitConverter unitsConverter
       );
   public float getSpeed()
   {
      float tireRpm = m_engine.CurrentRpm * 
              m_transmission.GetGearRatio( m_transmission.CurrentGear);

      float effectiveTireRadius = 
         (
            (m_frontLeftTire.RimSize + m_frontLeftTire.TireHeight / 25.4)
            +
            (m_frontRightTire.RimSize + m_frontRightTire.TireHeight / 25.4)
         ) / 2.0;

      //account for over/under inflated tires
      effectiveTireRadius = effectiveTireRadius * 
            ((m_tireMonitor.FrontLeftInflation + m_tireMontitor.FrontRightInflation) / 2.0);

      //speed in inches/minute
      float speed = tireRpm * effetiveTireRadius * 2 * Math.pi;

      //convert to mph
      return m_UnitConverter.InchesPerMinuteToMilesPerHour(speed);
   }
}

Mise à jour: peut-être certaines réponses peuvent suivre la question et de donner un exemple de code?

public Car {
    public float getSpeed();
}

Un autre exemple est lorsque ma classe dépend d'un autre objet:

public Car {
    private float m_speed;
}

Dans ce cas - float est une classe qui est utilisée pour représenter une valeur à virgule flottante. De ce que j'ai lu, chaque personne à charge de la classe doit être injecté dans le cas où je veux en moquer float classe. Cela soulève le spectre d'avoir à injecter chaque membre privé, puisque tout ce qui est fondamentalement un objet:

public Car {
    public Constructor(
        float speed,
        float weight,
        float wheelBase,
        float width,
        float length,
        float height,
        float headRoom,
        float legRoom,
        DateTime manufactureDate,
        DateTime designDate,
        DateTime carStarted,
        DateTime runningTime,
        Gps gps, 
        Engine engine, 
        Transmission transmission,
        Tire frontLeftTire, Tire frontRightTire, Tire rearLeftTire, Tire rearRightTire, 
        Seat driversSeat, Seat passengersSeat, Seat rearBenchSeat,
        SeatbeltPretensioner seatBeltPretensioner,
        Alternator alternator, 
        Distributor distributor,
        Chime chime,
        ECM computer,
        TireMonitoringSystem tireMonitor,
        ...
     }

Ce sont vraiment des détails de mise en œuvre que je ne veux pas le client à regarder.

13voto

Davy8 Points 12458

De nombreuses autres réponses allusion à elle, mais je vais plus explicitement dire que oui, naïf implémentations de l'injection de dépendance peut briser l'encapsulation.

La clé pour éviter cela, c'est que le code appelant ne devrait pas instancier directement les dépendances (si il ne se soucie pas d'eux). Cela peut être fait dans un certain nombre de façons.

Le plus simple est tout simplement d'avoir un constructeur par défaut qui ne l'injection avec des valeurs par défaut. Tant que le code appelant est seulement en utilisant le constructeur par défaut vous pouvez modifier les dépendances en arrière-plan sans affecter le code appelant.

Cela peut commencer à sortir de la main si vos dépendances elles-mêmes ont des dépendances et ainsi de suite. À ce stade, l'Usine modèle pourrait être mis en place (ou vous pouvez l'utiliser à partir de l'obtenir-aller, de sorte que le code appelant est déjà à l'aide de l'usine). Si vous introduisez de l'usine et ne voulez pas casser les utilisateurs existants de votre code, vous pouvez toujours juste appeler en usine à partir de votre constructeur par défaut.

Au-delà de qu'il y a à l'aide de l'Inversion de Contrôle. Je n'ai pas utilisé Cio assez de trop parler, mais il y a beaucoup de questions sur elle ainsi que sur les articles en ligne qui expliquent beaucoup mieux que je pouvais.

Si il devrait vraiment être encapsulé à l'endroit où le code appelant ne peut pas connaître les dépendances puis il y a la possibilité de faire de l'injection de drogues (soit le constructeur avec la dépendance des paramètres ou la définition) internal si la langue prend en charge, ou en les rendant privés et ont vos tests unitaires utiliser quelque chose comme Réflexion si votre langue prend en charge. Si votre langue ne prend en charge ni alors je suppose une possibilité pourrait être d'avoir la classe que le code appelant est l'instanciation d'une classe factice qui vient de encapsule la classe, le fait que le travail réel (je crois que c'est la Façade modèle, mais je ne me souviens jamais le nom correctement):

public Car {
   private RealCar _car;
   public constructor(){ _car = new RealCar(new Engine) };
   public float getSpeed() { return _car.getSpeed(); }
}

7voto

Paul Phillips Points 3818

Si je comprends vos préoccupations correctement, vous essayez d'empêcher toute classe qui a besoin d'instancier un nouvel objet Voiture de devoir injecter toutes ces dépendances manuellement.

J'ai utilisé un couple de motifs pour ce faire. Dans les langues avec le constructeur de chaînage, j'ai spécifié un constructeur par défaut qui injecte les types de béton dans un autre, la dépendance à injection constructeur. Je pense que c'est un assez standard manuel DI technique.

Une autre approche que j'ai utilisée, ce qui permet un couplage lâche, est de créer un objet de fabrique qui va configurer le DI ed de l'objet avec les dépendances appropriées. Ensuite, j'ai injecter cette usine dans n'importe quel objet qui doit "nouvelle" quelques Voitures au moment de l'exécution; cela vous permet d'injecter complètement faux de Voiture implémentations lors de vos tests, trop.

Et il y a toujours le setter approche d'injection. L'objet aurait raisonnable par défaut pour ses propriétés, qui pourrait être remplacé par test-double en tant que de besoin. Je ne préfère constructeur-injection, cependant.


Edit pour montrer un exemple de code:

interface ICar { float getSpeed(); }
interface ICarFactory { ICar CreateCar(); }

class Car : ICar { 
  private Engine _engine;
  private float _currentGearRatio;

  public constructor(Engine engine, float gearRatio){
    _engine = engine;
    _currentGearRatio = gearRatio;
  }
  public float getSpeed() { return return _engine.getRpm*_currentGearRatio; }
}

class CarFactory : ICarFactory {
  public ICar CreateCar() { ...inject real dependencies... }    
}

Et puis la consommation classes d'interagir avec lui via l'interface, masquant complètement tous les constructeurs.

class CarUser {
  private ICarFactory _factory;

  public constructor(ICarFactory factory) { ... }

  void do_something_with_speed(){
   ICar car = _factory.CreateCar();

   float speed = car.getSpeed();

   //...do something else...
  }
}

3voto

Gavin Miller Points 21752

Je pense que vous êtes briser l'encapsulation avec votre Car constructeur. Plus précisément, vous dictez un Engine doit être injecté à l' Car au lieu d'un certain type d'interface utilisé pour déterminer votre vitesse (IVelocity dans l'exemple ci-dessous.)

Avec une interface, l' Car est en mesure de l'obtenir du courant indépendant de la vitesse de ce qui est de la détermination de cette vitesse. Par exemple:

public Interface IVelocity {
   public float getSpeed();
}

public class Car {
   private m_velocityObject;
   public constructor(IVelocity velocityObject) { 
       m_velocityObject = velocityObject; 
   }
   public float getSpeed() { return m_velocityObject.getSpeed(); }
}

public class Engine : IVelocity {
   private float m_rpm;
   private float m_currentGearRatio;
   public float getSpeed( return m_rpm * m_currentGearRatio; );
}

public class GPS : IVelocity {
    private float m_foo;
    private float m_bar;
    public float getSpeed( return m_foo * m_bar; ); 
}

Un Moteur ou un GPS peut alors avoir plusieurs interfaces basées sur le type de travail qu'elle accomplit. L'interface est la clé de la DI, sans DI ne casser l'encapsulation.

2voto

Steven Rosato Points 1110

C'est là que je pense que vous devez utiliser l'injection de dépendance des conteneurs qui vous permettent d'encapsuler la création de votre voiture, sans laisser votre client appelants ont besoin de savoir comment créer ce que ce soit. Voici comment symfony résolu ce problème (même si elle n'est pas la même langue, les principes restent les mêmes):

http://components.symfony-project.org/dependency-injection/documentation

il y a une section sur l'injection de dépendance des conteneurs.

Pour faire court et résumer toutes les cités de la page de documentation directement:

Lors de l'utilisation du conteneur, nous vous demandons juste de pour un objet mailer [Ce serait votre voiture dans votre exemple], et nous n'avons pas besoin tout savoir sur la façon de créer plus; toutes les connaissances sur comment créer une instance de la mailer [voiture], est désormais intégrée dans le le conteneur.

Il espère que cela vous aide

2voto

kyoryu Points 8281

Les usines et les interfaces.

Vous avez un couple de questions ici.

  1. Comment puis-je avoir plusieurs implémentations de ces opérations?
  2. Comment puis-je masquer les détails de la construction d'un objet de la consommation d'un objet?

Donc, ce que vous avez besoin est de cacher le code réel derrière un ICar interface, créer un distinct EnginelessCar si jamais vous en avez besoin, et utiliser un ICarFactory interface et un CarFactory classe de cacher les détails de la construction à partir de la consommation de la voiture.

Ce sera probablement sembler beaucoup comme une infrastructure d'injection de dépendance, mais vous n'avez pas à l'utiliser.

Comme par ma réponse à l'autre question, si oui ou non cela rompt l'encapsulation dépend entièrement de la façon dont vous définissez l'encapsulation. Il y a deux définitions courantes de l'encapsulation que j'ai vu:

  1. Toutes les opérations sur une entité logique sont exposées en tant que membres de la classe, et un consommateur de la classe n'a pas besoin d'utiliser autre chose.
  2. Une classe a une responsabilité unique, et le code pour gérer cette responsabilité est contenue dans la classe. C'est, lors du codage de la classe, vous pouvez effectivement l'isoler de son environnement et de réduire le champ d'application du code que vous travaillez avec.

(Code comme la première définition peut exister dans une base de code qui fonctionne avec la deuxième condition, il a juste a tendance à être limitée aux façades, et ces façades ont tendance à avoir peu ou pas de logique).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X