How to use Dependency Injection without breaking encapsulation?

How can i perform dependency injection without breaking encapsulation?

Using a Dependency Injection example from Wikipedia:

public Car {
    public float getSpeed();
}

Note: Other methods and properties (eg PushBrake(), PushGas(), SetWheelPosition() ) omitted for clarity

This works well; you don't know how my object implements getSpeed - it is "encapsulated".

In reality my object implements getSpeed as:

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

And all is well. Someone constructs my Car object, mashes pedals, the horn, the steering wheel, and the car responds.

Now lets say i change an internal implementation detail of my car:

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

All is well. The Car is following proper OO-principles, hiding details of how something is done. This frees the caller to solve his problems, rather than trying to understand how a car works. It also gives me the freedom to change my implementation as i see fit.

But dependency injection would force me to expose my class to an Engine object that i didn't create or initialize. Even worse is that I've now exposed that my Car even has an engine:

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

And now the outside word is aware that i use an Engine . I didn't always use an engine, i may want to not use an Engine in the future, but i can no longer change my internal implementation:

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

without breaking the caller:

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

But dependency injection opens a whole can of worms: by opening the whole can of worms. Dependency Injection requires that all my objects private implementation details be exposed. The consumer of my Car class now has to understand, and deal with, all of the previously hidden internal intricacies of my class:

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

How can i use the virtues of Dependency Injection to help unit testing, while not breaking the virtues of encapsulation to help usability?

See also

  • Must Dependency Injection come at the expense of Encapsulation? (Must, rather than how)

  • For the sake of fun, i can trim down the getSpeed example to just what is needed:

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

    Update: Perhaps some answer can follow the question's lead, and give sample code?

    public Car {
        public float getSpeed();
    }
    

    Another example is when my class depends on another object:

    public Car {
        private float m_speed;
    }
    

    In this case float is a class that is used to represent a floating-point value. From what i read, every dependant class should be injected - in case i want to mock the float class. This raises the spectre of having to inject every private member, since everything is fundamentally an object:

    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,
            ...
         }
    

    These really are implementation details that i don't want the customer to have to look at.


    Many of the other answers hint at it, but I'm going to more explicitly say that yes, naive implementations of dependency injection can break encapsulation.

    The key to avoiding this is that calling code should not directly instantiate the dependencies (if it doesn't care about them). This can be done in a number of ways.

    The simplest is simply have a default constructor that does the injecting with default values. As long as calling code is only using the default constructor you can change the dependencies behind the scenes without affecting calling code.

    This can start to get out of hand if your dependencies themselves have dependencies and so forth. At that point the Factory pattern could come into place (or you can use it from the get-go so that calling code is already using the factory). If you introduce the factory and don't want to break existing users of your code, you could always just call into the factory from your default constructor.

    Beyond that there's using Inversion of Control. I haven't used IoC enough to speak too much about it, but there's plenty of questions here on it as well as articles online that explain it much better than I could.

    If it should be truly encapsulated to where calling code cannot know about the dependencies then there's the option of either making the injecting (either the constructor with the dependency parameters or the setters) internal if the language supports it, or making them private and have your unit tests use something like Reflection if your language supports it. If you language supports neither then I suppose a possibility might be to have the class that calling code is instantiating a dummy class that just encapsulates the class the does the real work (I believe this is the Facade pattern, but I never remember the names correctly):

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

    If I understand your concerns correctly, you're trying to prevent any class that needs to instantiate a new Car object from having to inject all those dependencies manually.

    I've used a couple patterns to do this. In languages with constructor chaining, I've specified a default constructor that injects the concrete types into another, dependency-injected constructor. I think this is a pretty standard manual DI technique.

    Another approach I've used, which allows some looser coupling, is to create a factory object that will configure the DI'ed object with the appropriate dependencies. Then I inject this factory into any object that needs to "new" up some Cars at runtime; this allows you to inject completely faked Car implementations during your tests, too.

    And there's always the setter-injection approach. The object would have reasonable defaults for its properties, which could be replaced with test-doubles as needed. I do prefer constructor-injection, though.


    Edit to show a code example:

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

    And then consumer classes just interact with it through the interface, completely hiding any constructors.

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

    I think you're breaking encapsulation with your Car constructor. Specifically you're dictating that an Engine must be injected to the Car instead of some type of interface used to determine your speed ( IVelocity in the below example.)

    With an interface, the Car is able to get it's current speed independent of what's determining that speed. For example:

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

    An Engine or GPS can then have multiple interfaces based upon the type of work that it does. The interface is key to DI, without it DI does break encapsulation.

    链接地址: http://www.djcxy.com/p/82236.html

    上一篇: 服务定位器,依赖注入(和容器)和控制反转

    下一篇: 如何在不破坏封装的情况下使用依赖注入?