我一直在阅读协方差和逆变 - Wikipedia讨论以下内容:
假设您有一个代表一个人的班级。一个人可以看医生,所以这个班可能有一个方法虚无效的人::看(医生d)。现在假设您要创建Person类Child的子类。也就是说,孩子是一个人。然后,人们可能想要制作医生,儿科医生的子类。如果孩子只访问儿科医生,我们希望在类型系统中强制执行。然而,一个天真的实现失败:因为一个孩子是一个人,孩子::看(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
现在,我可以实现以下内容,如果成年人试图去看儿童医生,或者如果孩子试图去看望成年医生(抛出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
类型的医生,以便在{{1}类型的医生处理时发生编译时错误被访问过(对ChildDoctor
}类来说反之亦然?
答案 0 :(得分:1)
您不需要co或contra-variance:
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());
这称为curiously recurring template pattern。在这种情况下,它用于通过接口类型Adult
获取具体实现者类型(IPerson
)。这意味着IPerson
可以访问的医生类型可以限制为与实施者相同的类型。
您也可以在Java Enum class中看到它。 compareTo
方法允许您比较枚举,但需要定期模板以确保您只能比较相同类型的枚举。
然而,它非常难看,所以您可能需要考虑将您的设计更改为:
public interface IDoctor<TPatient>
{
void SeePatient(TPatient patient);
}
public interface IAppointments<T>
{
void MakeAppointment(T patient, IDoctor<T> doctor);
}
因此,您可以不再需要IPerson
来获取类型参数。
答案 1 :(得分:1)
我明白你想要达到的目标。但是如果你继续使用这个界面:
public interface IPerson
{
void VisitDoctor(IDoctor doctor);
}
然后对实施者的任何进一步限制将打破 Liskov principle 。而这很可能会在以后对您的代码造成一些问题。
您可以做的是将该限制放入您的界面声明
public interface IPerson<TDoctor>
where TDoctor : class, IDoctor...
{
void VisitDoctor(TDoctor doctor);
}