我有很多相似的类以几种不同的方式重复出现。我想使用接口和抽象类使事情更加模块化 - 但我无法找到正确的方法。
接口定义了一个契约,它承诺对象将以特定的方式运行。在学习接口之前,你往往会用具体的术语想象对象。例如,假设我们有一列产品:
List<string> products = new List<string>() { "desktop", "laptop", "server" };
我们有一个打印出产品信息的方法:
void PrintProducts(List<string> products)
{
foreach (string product in products)
{
Console.WriteLine(product);
}
}
我们的方法与List的具体类型相关联。但是这样做必要吗?在C#中有很多不同类型的集合:List、Array、ReadOnlyCollection等等。你所需要做的就是遍历它们,其中列表(List)有很多数组没有的方法,但你在这里并没有使用到它们。幸运的是,它们都实现了IEnumerable接口。这意味着它们都“契约性地绑定”能够被枚举。void PrintProducts(IEnumerable<string> products)
{
foreach (string product in products)
{
Console.WriteLine(product);
}
}
这意味着你现在可以传递一个数组,或者列表,或者你自己创建的某些独特容器。
另一个例子:假设你有一个数据存储库:
public class DatabaseRepository
{
public void AddProduct(Product product)
{
// connect to the database
// add the product
}
}
你有一些需要使用这个数据库的类:
public class ProductManager
{
DatabaseRepository _repository;
public ProductManager(DatabaseRepository repository)
{
_repository= repository;
}
}
不幸的是,这个类与您的数据库相关联。如果您决定更改为存储为XML文件或在云键值存储中进行存储,则必须更改您的ProductManager,这很难且容易出错。相反,假设我们定义了一个接口:
public interface IRepository {
void AddProduct(Product product);
}
将我们的ProductManager类更改为使用此接口:
public class ProductManager
{
IRepository _repository;
public ProductManager(IRepository repository)
{
_repository= repository;
}
}
这意味着无论存储库的类型是什么,我们都知道始终会有一个名为AddProduct(Product product)的方法。现在我们可以创建XML存储库:
public class XMLRepository : IRepository
{
public void AddProduct(Product product)
{
// write to an XML file
}
}
现在,我们可以自由地传入任何仓库:
ProductManager manager = new ProductManager(new DatabaseRepository())
或者ProductManager manager = new ProductManager(new XMLRepository())
我们的ProductManager也是完全相同的行为。它对具体类型毫不知情。
当进行单元测试时,这变得非常有用。当您对接口的工作原理有坚实的理解后,反转控制是您想要深入了解的内容。
何时使用接口 接口允许某人从头开始实现您的接口或在一些其他代码中实现您的接口,其原始或主要目的与您的接口完全不同。对于他们来说,您的接口只是附带的东西,必须添加到他们的代码中才能使用您的包。缺点是接口中的每个方法都必须是公共的。您可能不想暴露所有内容。
何时使用抽象类 相比之下,抽象类提供了更多的结构。它通常定义了一些默认实现并提供了一些有用的工具,可用于完整的实现。问题在于,使用它的代码必须将您的类作为基础。如果其他想要使用您的包的程序员已经独立开发了自己的类层次结构,那么这可能非常不方便。在Java中,一个类只能继承自一个基类。