首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >如何在不破坏封装的情况下使用依赖注入?

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

Stack Overflow用户
提问于 2010-09-29 15:24:17
回答 9查看 4.3K关注 0票数 35

如何在不破坏封装的情况下执行依赖注入?

使用维基百科中的依赖注入示例

代码语言:javascript
运行
复制
public Car {
    public float getSpeed();
}

注释:其他方法和属性(例如PushBrake()、PushGas()、SetWheelPosition() )省略以求清晰

这很好用;您不知道我的对象是如何实现getSpeed的--它是“封装”的。

实际上,我的对象将getSpeed实现为:

代码语言:javascript
运行
复制
public Car {
    private m_speed;
    public float getSpeed( return m_speed; );
}

一切都很好。有人建造我的Car对象,捣碎踏板,喇叭,方向盘,和汽车响应。

现在,假设我更改了我的car的内部实现细节:

代码语言:javascript
运行
复制
public Car {
    private Engine m_engine;
    private float m_currentGearRatio;
    public float getSpeed( return m_engine.getRpm*m_currentGearRatio; );
}

平安无事。Car遵循正确的OO原则,隐藏一些事情是如何完成的细节。这使来电者有时间解决他的问题,而不是试图理解汽车是如何工作的。它也给了我自由地改变我的实现,我认为适合。

但是依赖注入会迫使我将类暴露给我没有创建或初始化的Engine对象。更糟糕的是,我现在暴露了我的Car甚至都有一个引擎

代码语言:javascript
运行
复制
public Car {
   public constructor(Engine engine);
   public float getSpeed();
}

现在,外边的词知道我使用的是Engine。我并不总是使用引擎,将来我可能不想使用Engine,但是我不能再改变我的内部实现:

代码语言:javascript
运行
复制
public Car {
    private Gps m_gps;
    public float getSpeed( return m_gps.CurrentVelocity.Speed; )
}

不打断来电者:

代码语言:javascript
运行
复制
public Car {
   public constructor(Gps gps);
   public float getSpeed();
}

但是依赖注入打开了一整罐蠕虫:通过打开整个蠕虫罐。依赖注入要求公开我的所有对象的私有实现细节。现在,我的Car类的使用者必须理解并处理我的类以前隐藏的所有内部复杂性:

代码语言:javascript
运行
复制
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();
}

我如何利用依赖注入的优点来帮助单元测试,同时又不打破封装的优点来帮助可用性呢?

另请参阅

为了好玩,我可以将getSpeed示例缩减到所需的内容:

代码语言:javascript
运行
复制
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);
   }
}

更新:--也许某些答案可以跟随问题的线索--并给出示例代码?

代码语言:javascript
运行
复制
public Car {
    public float getSpeed();
}

另一个例子是当我的类依赖于另一个对象时:

代码语言:javascript
运行
复制
public Car {
    private float m_speed;
}

在本例中,float是用来表示浮点值的类.根据我所读到的,每个依赖类都应该被注入--以防我想要模拟float类。这引起了必须注入每一个私有成员的恐惧,因为一切从根本上都是一个对象:

代码语言:javascript
运行
复制
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,
        ...
     }

这些都是我不想让客户去看的实现细节。

EN

回答 9

Stack Overflow用户

回答已采纳

发布于 2010-09-29 19:37:15

其他许多答案都暗示了这一点,但我要更明确地说,简单的依赖注入实现可以打破封装。

避免这一点的关键是调用代码不应该直接实例化依赖项(如果它不关心它们)。可以通过多种方式来实现。

最简单的就是有一个默认的构造函数,它用默认值进行注入。只要调用代码只使用默认构造函数,就可以在幕后更改依赖项,而不会影响调用代码。

如果依赖关系本身有依赖关系,这可能会开始失控,等等。在这一点上,工厂模式可以就位(或者您可以从get开始使用它,这样调用代码就已经在使用工厂了)。如果您介绍了工厂,并且不想破坏现有的代码用户,您可以从默认的构造函数中调用工厂。

除此之外,还使用了控制反转。我还没有充分使用IoC来谈论它,但是这里有很多问题,网上的文章也比我能更好地解释它。

如果应该将其真正封装到调用代码无法了解依赖关系的地方,那么可以选择将注入(具有依赖参数的构造函数或setter)(如果语言支持它)设置为internal,或者使它们成为私有的,让单元测试使用类似反射(如果您的语言支持它)。如果您的语言不支持这两种语言,那么我想有一种可能是让调用代码的类实例化一个只是封装类的虚拟类(我相信这是Facade模式,但我从来没有正确记住名称):

代码语言:javascript
运行
复制
public Car {
   private RealCar _car;
   public constructor(){ _car = new RealCar(new Engine) };
   public float getSpeed() { return _car.getSpeed(); }
}
票数 13
EN

Stack Overflow用户

发布于 2010-09-29 16:22:04

如果我正确理解您的关注点,您将试图防止任何需要实例化新Car对象的类不得不手动注入所有这些依赖项。

我用了几种模式来做这个。在带有构造函数链的语言中,我指定了一个默认构造函数,它将具体类型注入到另一个依赖注入构造函数中。我认为这是一种非常标准的手动DI技术。

我使用的另一种方法是创建一个工厂对象,该对象将使用适当的依赖项配置DI‘’ed对象,该方法允许更松散的耦合。然后,我将这个工厂注入到任何在运行时需要“新建”一些汽车的对象中;这也允许您在测试期间注入完全伪造的Car实现。

总有一种引人-注射的方法。对象的属性将有合理的默认值,可以根据需要用test-doubles替换。不过,我更喜欢构造函数注入。

编辑以显示代码示例:

代码语言:javascript
运行
复制
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... }    
}

然后使用者类通过接口与它交互,完全隐藏任何构造函数。

代码语言:javascript
运行
复制
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...
  }
}
票数 7
EN

Stack Overflow用户

发布于 2010-09-29 20:19:11

我认为您正在打破Car构造函数的封装。具体来说,您要求必须将Engine注入Car,而不是使用某种类型的接口来决定您的速度(在下面的示例中是IVelocity)。

有了接口,Car就能够获得它的当前速度,而不依赖于决定该速度的是什么。例如:

代码语言:javascript
运行
复制
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; ); 
}

然后,引擎或GPS可以根据它所做的工作类型而具有多个接口。接口是DI的关键,没有它DI就会破坏封装。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3823062

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档