在以下情况下正确使用抽象类或接口

本文关键字:抽象类 接口 情况下 | 更新日期: 2023-09-27 18:14:00

我有不同类型的文档:DocumentCitizen, documenttother, DocumentResolution等。每个文档类型也有相应的存储库:DocumentCitizenRepository, documenttotherrepository,DocumentResolutionRepository。所有存储库都有一个名为Documents的属性和一个名为SaveDocument的方法。以下是DocumentCitizenRepository的内容示例:

public class DocumentCitizenRepository{
   EFDbContext context= new EFDbContext();//EFDbContext inherits DbContext        
   public IQueryable<DocumentCitizen> Documents{get{context.DocmentsFromCitizens}}
   public void SaveDocument(DocumentCitizen doc)
   {//omitted for brevity}
}

其他存储库的内容是相似的。出于这个原因,我希望所有的存储库都扩展一个名为Repository的抽象类。我还为所有文档类型定义了一个抽象类,名为document,内容为空:

public abstract class Document{}
public abstract class Repository{
  public abstract IQueryable<Document>{get;}
  public abstract void SaveDocument(Document doc)
}

但是我得到关于无效或丢失重写的警告,因为编译器需要属性和方法的签名相同。查看差异:

基抽象类中的方法:

  public abstract void SaveDocument(Document doc)

和继承类中的

  public abstract void SaveDocument(DocumentCitizen doc)

所以我完全不知道是否有可能做我想做的事。你有什么主意吗?

在以下情况下正确使用抽象类或接口

很有可能,你甚至可以更进一步,创建一个完全通用的基本库:

public interface IRepository<T>
{
    IQueryable<T> GetQueryable();
    void Save(T item);
}
public abstract class BaseRepository<T> : IRepository<T>
{
    public IQueryable<T> GetQueryable() { ... }
    public void Save(T item) { ... }
}

您将仍然有特定的存储库接口,允许您为实体创建特定的方法:

public interface IDocumentCitizenRepository : IRepository<DocumentCitizen>
{ 
    // this interface has no extra methods, just the plain ol' CRUD
}
public interface IDocumentResolutionRepository : IRepository<DocumentResolution>
{ 
    // this one can do additional stuff
    void DoSomethingSpecial(DocumentResolution doc);
}

抽象基类用于重用公共功能:

// this class will inherit everything from the base abstact class
public class DocumentCitizenRepository 
   : BaseRepository<DocumentCitizen>, IDocumentCitizenRepository
{ }
// this class will inherit common methods from the base abstact class,
// and you will need to implement the rest manually
public class DocumentResolutionRepository 
   : BaseRepository<DocumentResolution>, IDocumentResolutionRepository
{ 
    public void DoSomethingSpecial(DocumentResolution doc)
    {
        // you still need to code some specific stuff every once in a while
    }
}

这通常与依赖注入一起使用,这样你就只使用业务层中的接口,而不用关心实际的实现:

var repo = container.Resolve<IDocumentCitizenRepository>();
repo.Save(doc);

最后一部分(依赖注入)应该理想地在组合根中完成,这意味着在代码中直接使用容器("服务定位器"模式)并不是抽象这些依赖的理想方式。尝试通过构造函数组织类来接收存储库接口,这将允许您在进行单元测试时轻松模拟每个repo。

我通常倾向于这样做:

public interface IRepository<T> where T: class
{
  bool Delete(T entity);
  bool Save(T entity);
}

你可以使用repo…

private IRepository<DocumentCitizen> _docCitizen;

在你的方法中,你可以像…

DocumentCitizen doc = new DocumentCitizen{....}
_docCitizen.Save(doc);

你可以把它作为构造函数参数传递…

public DocumentService(
            IRepository<DocumentCitizen> docCitizen)
        {
            _docCitizen = docCitizen;
        }

在我看来,使DocumentCitizenRepository从单个抽象DocumentRepository类继承其Save方法是没有意义的,因为DocumentCitizenRepository不能保存任意文档。继承该方法违反了Liskov替换原则。

对于GetDocuments方法,情况就不同了。DocumentCitizenRepository.GetDocuments ()返回特定类型的文档,如果DocumentCitizen继承自Document,则可以将其视为文档,因此,DocumentCitizenRepository.GetDocuments ()是某些DocumentRepository.GetDocuments ()方法的有效实现。

方法:

class DocumentCitizenRepository : DocumentRepository<DocumentCitizen> {
   public override void Save (DocumentCitizen doc) { ... }
   public override IEnumerable<DocumentCitizen> GetDocuments () { ... }
}
// this class is only meant to simplify implementation
abstract class DocumentRepository<T> : IDocumentRepository where T: Document {
   public abstract void Save (T doc);
   public abstract IEnumerable<T> GetDocuments ();
   IEnumerable<Document> IDocumentRepository.GetDocuments () { return this.GetDocuments (); }
}
interface IDocumentRepository {
   IEnumerable<Document> GetDocuments ();
}