接口实现理念
Interface Implementation Philosophy
在 C# 中,您不允许实现这样的接口:
public interface ITest
{
IEnumerable<int> Numbers { get; set; }
}
public class CTest : ITest
{
public List<int> Numbers { get; set; }
}
我的问题是:是否有某种软件哲学使这种类型的实现出错?它是否是一个有争议的范式,有点像多重继承,争论已经持续了几十年?
我看不出为什么这样的接口实现不能很好地工作。如果您知道 ITest
,那么您只能看到 CTest.Numbers
的 IEnumerable<int>
部分。如果您了解 CTest
,那么您可以使用整个 List<int>
实现。既然 List<int>
派生自 IEnumerable<int>
,为什么它不能满足接口要求?
调用覆盖基方法时子类 return 子类型的能力 return type covariance
在这种情况下,由于 Numbers
属性 的 setter,您的示例不安全:
ITest t = new CTest();
t.Numbers = new HashSet<int>();
如果在 ITest
中只定义了一个 getter 那么它是安全的,尽管 C# 仍然不允许它 - 但是 Java 允许:
public interface Test {
Iterable<Integer> getNumbers();
}
public class CTest implements Test {
public List<Integer> getNumbers() {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
return numbers;
}
}
接口是一个合同。它告诉客户确切什么methods/properties可用,确切输入和输出的类型。
当您告诉客户任何实现 ITest
的 class 将接受任何 IEnumerable<int>
作为 Numbers
的值,但 CTest
class 只会接受 List<int>
,你违反了那个合同。
如果你想实现接口但也公开更具体的集合类型,你可以显式实现接口,但是你必须决定做什么是客户端尝试"set" IEnumerable<int>
是 而不是 List<int>
一种选择是创建一个 List<int>
幕后:
public class CTest : ITest
{
public List<int> Numbers { get; set; }
// satisfies the interface
IEnumerable<int> ITest.Numbers {
get { return Numbers; }
set { Numbers = new List<int>(value); }
}
}
现在知道 CTest
的客户端可以使用 List<int>
属性,但只知道接口的客户端仍然可以设置集合而无需转换。
在 C# 中,您不允许实现这样的接口:
public interface ITest
{
IEnumerable<int> Numbers { get; set; }
}
public class CTest : ITest
{
public List<int> Numbers { get; set; }
}
我的问题是:是否有某种软件哲学使这种类型的实现出错?它是否是一个有争议的范式,有点像多重继承,争论已经持续了几十年?
我看不出为什么这样的接口实现不能很好地工作。如果您知道 ITest
,那么您只能看到 CTest.Numbers
的 IEnumerable<int>
部分。如果您了解 CTest
,那么您可以使用整个 List<int>
实现。既然 List<int>
派生自 IEnumerable<int>
,为什么它不能满足接口要求?
调用覆盖基方法时子类 return 子类型的能力 return type covariance
在这种情况下,由于 Numbers
属性 的 setter,您的示例不安全:
ITest t = new CTest();
t.Numbers = new HashSet<int>();
如果在 ITest
中只定义了一个 getter 那么它是安全的,尽管 C# 仍然不允许它 - 但是 Java 允许:
public interface Test {
Iterable<Integer> getNumbers();
}
public class CTest implements Test {
public List<Integer> getNumbers() {
List<Integer> numbers = new ArrayList<>();
numbers.add(1);
return numbers;
}
}
接口是一个合同。它告诉客户确切什么methods/properties可用,确切输入和输出的类型。
当您告诉客户任何实现 ITest
的 class 将接受任何 IEnumerable<int>
作为 Numbers
的值,但 CTest
class 只会接受 List<int>
,你违反了那个合同。
如果你想实现接口但也公开更具体的集合类型,你可以显式实现接口,但是你必须决定做什么是客户端尝试"set" IEnumerable<int>
是 而不是 List<int>
一种选择是创建一个 List<int>
幕后:
public class CTest : ITest
{
public List<int> Numbers { get; set; }
// satisfies the interface
IEnumerable<int> ITest.Numbers {
get { return Numbers; }
set { Numbers = new List<int>(value); }
}
}
现在知道 CTest
的客户端可以使用 List<int>
属性,但只知道接口的客户端仍然可以设置集合而无需转换。