1

我一直在阅读协变和逆变 -维基百科谈到以下内容:

假设你有一个代表一个人的类。一个人可以看医生,所以这个类可能有一个方法 virtual void Person::see(Doctor d)。现在假设您要创建 Person 类 Child 的子类。也就是说,一个孩子就是一个人。然后可能想创建一个子类 Doctor, Pediatrician。如果孩子只看儿科医生,我们希望在类型系统中强制执行。然而,一个简单的实现失败了:因为 Child 是一个 Person,所以 Child::see(d) 必须接受任何医生,而不仅仅是儿科医生。

这是一个“幼稚的实现”:

public interface IDoctor
{
}

public interface IPerson
{
    void VisitDoctor(IDoctor doctor);
}

public class Adult : IPerson
{
    public void VisitDoctor(IDoctor doctor)
    {
        Console.WriteLine("Adult saw doctor of type: {0}", doctor.GetType().Name);
    }
}

public class Child : IPerson
{
    public void VisitDoctor(IDoctor doctor)
    {
        Console.WriteLine("Child saw doctor of type: {0}", doctor.GetType().Name);
    }
}

public class AdultDoctor : IDoctor
{
}

public class ChildDoctor : IDoctor
{
}

这些测试:

[Test]
public void AdultSeesDoctor()
{
    var adult = new Adult();
    adult.VisitDoctor(new AdultDoctor());
    adult.VisitDoctor(new ChildDoctor());  // <-- Would like this to fail
}

[Test]
public void ChildSeesDoctor()
{
    var child = new Child();
    child.VisitDoctor(new AdultDoctor());  // <-- Would like this to fail
    child.VisitDoctor(new ChildDoctor());
}

输出:

成人锯医生类型:AdultDoctor

成人锯医生类型:ChildDoctor

儿童锯医生类型:AdultDoctor

儿童锯医生类型:ChildDoctor

现在,我可以实现以下内容,如果成人尝试去看儿童医生,或者孩子尝试去看成人医生(抛出 a System.InvalidCastException),则会引发运行时错误:

public interface IVisitDoctors<T> where T : IDoctor
{
    void VisitDoctor(T doctor);
}

public class Child : IPerson
{
    private readonly ChildDoctorVisitor _cdv = new ChildDoctorVisitor();

    public void VisitDoctor(IDoctor doctor)
    {
        _cdv.VisitDoctor((ChildDoctor)doctor);
    }
}

public class Adult : IPerson
{
    private readonly AdultDoctorVisitor _adv = new AdultDoctorVisitor();

    public void VisitDoctor(IDoctor doctor)
    {
        _adv.VisitDoctor((AdultDoctor)doctor);
    }
}

您能否强制 的 类Adult仅访问 类型 的医生,以便在访问类型AdultDoctor的医生时引发编译时ChildDoctor错误(反之亦然 的 类Child)?

4

2 回答 2

1

为此,您不需要 co 或逆变:

public interface IDoctor<TPatient> where T : IPerson<TPatient>
{
}

public interface IPerson<T> where T : IPerson<T>
{
    void VisitDoctor(IDoctor<T> doctor);
}

public class Adult : IPerson<Adult>
{
    void VisitDoctor(IDoctor<Adult> doctor) {  }
}

public class AdultDoctor : IDoctor<Adult>
{
}

现在以下将无法编译:

Adult a = new Adult();
a.VisitDoctor(new ChildDoctor());

虽然这将:

Adult a = new Adult();
a.VisitDoctor(new AdultDoctor());

这被称为奇怪重复的模板模式。在这种情况下,它用于Adult通过接口类型获取具体的实现者类型()IPerson。这意味着IPerson可以访问的医生类型可以限制为与实施者相同的类型。

您还可以在Java Enum 类中看到它。该compareTo方法允许您比较枚举,但需要循环模板以确保您只能比较相同类型的枚举。

然而,它非常难看,因此您可能需要考虑将您的设计更改为:

public interface IDoctor<TPatient>
{
    void SeePatient(TPatient patient);
}

public interface IAppointments<T>
{
    void MakeAppointment(T patient, IDoctor<T> doctor);
}

因此,您可以消除对IPerson类型参数的需要。

于 2012-10-07T17:28:38.947 回答
1

我了解您想要达到的目标。但如果你继续玩这个界面:

public interface IPerson
{
    void VisitDoctor(IDoctor doctor);
}

那么对实施者的任何进一步限制都将打破Liskov 原则。这很可能会在以后给您的代码带来一些问题。

您可以做的是将该限制放入您的接口声明中

public interface IPerson<TDoctor>
    where TDoctor : class, IDoctor...
{
    void VisitDoctor(TDoctor doctor);
}
于 2012-10-07T17:30:26.997 回答