是否将构造函数参数从接口类型转换为具体类类型错误?

时间:2014-05-21 17:05:37

标签: c# unit-testing

鉴于以下课程:

public interface ISession {}

public class Session : ISession
{
    internal Uri ServerUri { get; private set; } // not in interface obviously
}

现在我在同一个项目中有另一个类,它取决于Session

public  interface IDataClass {}

internal DataClass : IDataClass
{
    private readonly Session _session;

    public DataClass(ISession session)
    {
        _session = (Session) session;
    }

    // some more methods that access the internal property _session.ServerUri
    // and call methods on the session object
    // ...
}

所以实际上Session类非常复杂。对于单元测试,DataClass我将构造函数参数设为ISession,因此我能够模拟依赖项并验证ISession模拟上的某些方法调用。

但是,DataClass中的某些方法必须访问_session.ServerUri属性,因此该字段必须是Session类型,而不是ISession

我还可以创建ISession类型的字段,并在每次访问内部属性时强制转换字段,这将使得在此特定位置更清楚为什么甚至需要具体的internal类。

每当我以上述方式实现强制转换时,我想知道如果使用更好的代码设计,可以避免使用强制转换。我经常找到一种更好的方法,摆脱演员。

但是上面的例子怎么样?这是好的代码设计吗?还是有更干净的方式?我只是觉得从构造函数初始化字段上面的方式很奇怪。

4 个答案:

答案 0 :(得分:5)

老实说,我认为你现有的系统并不是一个好主意。

你的构造函数声明“只要它实现了ISession”我会接受任何东西,但那不是真的是真的 - 调用代码可以在它们获得时发现它很难尝试创建InvalidCastException实例时DataClass

所以你真的应该在ServerUri 继承自ISession的接口中声明ISession - 但在任何一种情况下,你都需要声明(并且只在DataClass

上通过接口存储此依赖项

答案 1 :(得分:4)

你是对的,像这样的投射很容易出错,因为编译器没有简单的方法来告诉它会失败。

但是,看起来问题的根源是缺少对ServerUri属性的访问权限,因此您添加了强制转换作为变通方法。如果是这种情况,您应该能够两种方式 - 保持接口,并且可以访问属性,如果您可以添加新接口:

public interface ISessionWithServerUri : ISession { // Come up with a better name
    Uri ServerUri {get;}
}

现在,您的Session课程可以实施ISessionWithServerUri,允许您将_session类的声明从Session类更改为ISessionWithServerUri界面:

internal DataClass : IDataClass
{
    private readonly ISessionWithServerUri _session;

    public DataClass(ISessionWithServerUri session)
    {
        _session = session; // Look, no cast!
    }

    // some more methods that access the internal property _session.ServerUri
    // and call methods on the session object
    // ...
}

答案 2 :(得分:3)

由于DataClass取决于Session,因此它应该在其构造函数中声明它。该类的任何客户端都应该能够从构造函数中定义的类型/契约中创建有效的实例。

如果你想依赖一个界面,那么你应该用你需要的成员创建一个新界面。

public interface IUriSession : ISession
{
   Uri ServerUri { get; }
}

并将构造函数更改为

public DataClass(IUriSession session) { ... }

答案 3 :(得分:2)

我总是试图避免像构造函数中那样的显式类型转换。我假设你正在使用IoC容器来解决你的依赖关系,如果具体的类型映射曾经改变为实现该接口的另一个类(并且没有内部属性),这显然会破坏。它也有点破坏了该类中使用的依赖注入的目的。

您是否可以更改DataClass的构造函数以注入其他类型?如果是这样,您可以创建一个实现ISession并且还包含ServerUri属性的接口,创建一个实现新接口的类,然后将该类型注入构造函数(接口)。