存储库模式:泛型与多态性的实现方式
repository pattern : generics vs polymorphism way of implementation
通用存储库界面如下所示:
public interface IRepository<T> where T : Entity
{
T Get(int key);
IQueryable<T> Get();
void Save(T obj);
void Delete(T obj);
}
实现类似功能的另一种方法是使用多态性,如下所示
public interface IRepository
{
Entity Get(int key);
IQueryable<Entity> Get();
void Save(Entity obj);
void Delete(Entity obj);
}
一般来说,我的问题是在什么场景或用例中我们应该使用泛型?如果我们使用多态性,是否可以完全避免泛型。或者我在这里完全没有意义,这两个完全不相关。
第一个示例和第二个示例之间最重要的区别称为 type safety。
我假设您是从 C# 或 Java 等静态类型语言的角度提出问题的。
在使用泛型的版本中,您的编译器确保您始终使用正确的类型,而在第二个版本(使用更通用类型的版本)中,您需要自己手动检查.此外,编译器会不断强制您将通用类型(例如 Entity
)转换为更具体的类型(例如 Customer
)以使用对象提供的服务。
换句话说,您必须一直与编译器作斗争,因为它始终要求我们转换类型,以便它能够验证我们编写的代码。
使用泛型
第一个示例在接口级别使用 类型变量 T
。这意味着当你为这个接口类型定义一个变量时(或者当你实现它的时候),你也将被迫为 T
.[= 提供一个 type argument 35=]
例如
IRepository<Customer> custRepo = ...;
也就是出现T
的地方,一定要换成Customer
吧?
一旦您将 T
的类型参数定义为 Customer
,就好像您的接口声明在编译器的眼中会变成这样:
public interface IRepository
{
Customer Get(int key);
IQueryable<Customer> Get();
void Save(Customer obj);
void Delete(Customer obj);
}
因此,当您使用它时,编译器会强制您尊重您的类型参数:
Customer customer = custRepo.Get(10);
customer.setEmail("lskywalker@gmail.com");
custRepo.Save(customer);
在上面的示例中,所有存储库方法仅适用于Customer
类型。因此我不能将不正确的类型(例如 Employee)传递给方法,因为编译器将在所有地方强制执行类型安全:
Employee employee = empRepo.Get(10);
custRepo.Save(employee); //Uh oh! Compiler error here
没有泛型
另一方面,在你的第二个例子中,你所做的只是决定你将使用更通用的类型。通过这样做,您牺牲了类型安全性来换取一些灵活性:
例如:
IRepository custRepo = ...;
Entity customer = custRepo.Get(10);
((Customer) customer).setEmail("lskywalker@gmail.com"); //Now you'll need casting
custRepo.Save(customer);
在上述情况下,您必须始终将 Entity
转换为更可用的类型,例如 Customer
才能使用它提供的功能。这种转换是一种不安全的操作,它可能会引入错误(如果我们对所使用的类型的假设有误)。
此外,存储库类型不会阻止您将错误的类型传递给方法,并且您可能会犯语义错误:
Entity employee = empRepo.Get(10);
custRepo.Save(employee); //Uh Oh!
如果您这样做,您可能必须在 CustomerRepo
中确保您的实体实际上是 Customer
的实体,以防止像最后一行中那样的错误上面的例子。
换句话说,您将手动实现编译器在您使用泛型时自动为您提供的那种类型安全。
这听起来好像我们正在尝试使用我们的静态类型语言,就好像它是一种动态类型的语言一样,对吧?这就是为什么我们必须一路与编译器作斗争以强制执行这种编程风格。
关于参数多态
现在,您可能想探索泛型也是多态的一种形式,称为 parametric polymorphism. You may also want to read this another answer 我在另一个问题中提出了一个问题,我引用了一篇关于多态的好论文,它可能会帮助您拓宽对这个主题不仅仅是 class 和接口继承。
动态类型语言与静态类型语言之争
现在,一个有趣的结论可能会帮助您进一步探索这个问题,那就是动态语言(例如 JavaScript、Python、Ruby 等),您不不必进行显式类型声明,实际上就像您的无泛型示例一样工作。
动态语言的工作方式就好像所有变量都是 Object
类型一样,它们只允许您对该对象做任何您想做的事情,以避免您必须不断地将对象转换为不同的类型。程序员有责任编写测试以确保始终正确使用对象。
静态类型语言的捍卫者与动态类型语言的拥护者之间一直存在着激烈的争论。这就是我们通常所说的 holly war.
我相信这实际上是一个您可能想要更深入地探索的主题,以真正理解您的两个示例之间的根本区别,并了解泛型的静态类型和类型安全与 just 的高度灵活性相比有何不同使用动态类型。
我建议您阅读伯恩茅斯大学 Laurence Tratt 的一篇名为 Dynamically–Typed Languages 的优秀论文。
现在,在像 C# 或 Java 这样的静态类型语言中,我们通常希望尽可能地利用类型安全。但是没有什么能阻止我们像使用动态语言那样编写代码,只是编译器会不断地与我们作对。你的第二个例子就是这种情况。
如果这种编程方式更能引起您和您的工作风格的共鸣,或者如果它为您提供了您寻求的灵活性,那么也许您应该考虑使用动态类型语言。也许可以在您当前的平台之上组合(例如 IronRuby or IronPython),这样您还可以重用当前静态类型语言中已有的代码。
通用存储库界面如下所示:
public interface IRepository<T> where T : Entity
{
T Get(int key);
IQueryable<T> Get();
void Save(T obj);
void Delete(T obj);
}
实现类似功能的另一种方法是使用多态性,如下所示
public interface IRepository
{
Entity Get(int key);
IQueryable<Entity> Get();
void Save(Entity obj);
void Delete(Entity obj);
}
一般来说,我的问题是在什么场景或用例中我们应该使用泛型?如果我们使用多态性,是否可以完全避免泛型。或者我在这里完全没有意义,这两个完全不相关。
第一个示例和第二个示例之间最重要的区别称为 type safety。
我假设您是从 C# 或 Java 等静态类型语言的角度提出问题的。
在使用泛型的版本中,您的编译器确保您始终使用正确的类型,而在第二个版本(使用更通用类型的版本)中,您需要自己手动检查.此外,编译器会不断强制您将通用类型(例如 Entity
)转换为更具体的类型(例如 Customer
)以使用对象提供的服务。
换句话说,您必须一直与编译器作斗争,因为它始终要求我们转换类型,以便它能够验证我们编写的代码。
使用泛型
第一个示例在接口级别使用 类型变量 T
。这意味着当你为这个接口类型定义一个变量时(或者当你实现它的时候),你也将被迫为 T
.[= 提供一个 type argument 35=]
例如
IRepository<Customer> custRepo = ...;
也就是出现T
的地方,一定要换成Customer
吧?
一旦您将 T
的类型参数定义为 Customer
,就好像您的接口声明在编译器的眼中会变成这样:
public interface IRepository
{
Customer Get(int key);
IQueryable<Customer> Get();
void Save(Customer obj);
void Delete(Customer obj);
}
因此,当您使用它时,编译器会强制您尊重您的类型参数:
Customer customer = custRepo.Get(10);
customer.setEmail("lskywalker@gmail.com");
custRepo.Save(customer);
在上面的示例中,所有存储库方法仅适用于Customer
类型。因此我不能将不正确的类型(例如 Employee)传递给方法,因为编译器将在所有地方强制执行类型安全:
Employee employee = empRepo.Get(10);
custRepo.Save(employee); //Uh oh! Compiler error here
没有泛型
另一方面,在你的第二个例子中,你所做的只是决定你将使用更通用的类型。通过这样做,您牺牲了类型安全性来换取一些灵活性:
例如:
IRepository custRepo = ...;
Entity customer = custRepo.Get(10);
((Customer) customer).setEmail("lskywalker@gmail.com"); //Now you'll need casting
custRepo.Save(customer);
在上述情况下,您必须始终将 Entity
转换为更可用的类型,例如 Customer
才能使用它提供的功能。这种转换是一种不安全的操作,它可能会引入错误(如果我们对所使用的类型的假设有误)。
此外,存储库类型不会阻止您将错误的类型传递给方法,并且您可能会犯语义错误:
Entity employee = empRepo.Get(10);
custRepo.Save(employee); //Uh Oh!
如果您这样做,您可能必须在 CustomerRepo
中确保您的实体实际上是 Customer
的实体,以防止像最后一行中那样的错误上面的例子。
换句话说,您将手动实现编译器在您使用泛型时自动为您提供的那种类型安全。
这听起来好像我们正在尝试使用我们的静态类型语言,就好像它是一种动态类型的语言一样,对吧?这就是为什么我们必须一路与编译器作斗争以强制执行这种编程风格。
关于参数多态
现在,您可能想探索泛型也是多态的一种形式,称为 parametric polymorphism. You may also want to read this another answer 我在另一个问题中提出了一个问题,我引用了一篇关于多态的好论文,它可能会帮助您拓宽对这个主题不仅仅是 class 和接口继承。
动态类型语言与静态类型语言之争
现在,一个有趣的结论可能会帮助您进一步探索这个问题,那就是动态语言(例如 JavaScript、Python、Ruby 等),您不不必进行显式类型声明,实际上就像您的无泛型示例一样工作。
动态语言的工作方式就好像所有变量都是 Object
类型一样,它们只允许您对该对象做任何您想做的事情,以避免您必须不断地将对象转换为不同的类型。程序员有责任编写测试以确保始终正确使用对象。
静态类型语言的捍卫者与动态类型语言的拥护者之间一直存在着激烈的争论。这就是我们通常所说的 holly war.
我相信这实际上是一个您可能想要更深入地探索的主题,以真正理解您的两个示例之间的根本区别,并了解泛型的静态类型和类型安全与 just 的高度灵活性相比有何不同使用动态类型。
我建议您阅读伯恩茅斯大学 Laurence Tratt 的一篇名为 Dynamically–Typed Languages 的优秀论文。
现在,在像 C# 或 Java 这样的静态类型语言中,我们通常希望尽可能地利用类型安全。但是没有什么能阻止我们像使用动态语言那样编写代码,只是编译器会不断地与我们作对。你的第二个例子就是这种情况。
如果这种编程方式更能引起您和您的工作风格的共鸣,或者如果它为您提供了您寻求的灵活性,那么也许您应该考虑使用动态类型语言。也许可以在您当前的平台之上组合(例如 IronRuby or IronPython),这样您还可以重用当前静态类型语言中已有的代码。