【问题标题】:Its is a good practice to cast from an interface to some concrete class when needed?在需要时从接口转换为某个具体类是一种好习惯吗?
【发布时间】:2023-03-20 10:00:01
【问题描述】:

我正在开发一个小型系统,并且我开发了经典的通用存储库。目前,我的 DAL 具有以下架构。

public interface IRepositorio<T> where T : class
{
    T Get(long id);
    long Insert(T obj);
    bool Update(T obj);
    bool Delete(T obj);
}

public abstract class Repositorio<T> : IRepositorio<T> where T : class
{
    public IDbConnection Connection
    {
        get
        {
            return new SqlConnection(ConfigurationManager.ConnectionStrings["DBFila"].ConnectionString);
        }
    }

    public T Get(long id)
    {
        //...
    }

    public long Insert(T obj)
    {
        //...
    }

    public bool Update(T obj)
    {
        //...
    }

    public bool Delete(T obj)
    {
        //...
    }
}

我的具体存储库如下所示:

public class FilaRepositorio : Repositorio<FilaRepositorio>
{
    public FilaRepositorio() 
    {
    }

    public void SomeCustomMethod()
    {
        // Some custom method
    }
}

我也在使用 Simple Injector 来遵循 IoC 和 DI 模式,因此,当我尝试调用“SomeCustomMethod()”时,我无法访问它(显然)。看:

public class Processador
{
    private IRepositorio<FilaModel> _repoFila;
    public Processador(IRepositorio<FilaModel> repoFila)
    {
        _repoFila = repoFila;
    }

    public void Processar()
    {
        _repoFila.SomeCustomMethod(); // <-- wrong

        ((FilaRepositorio)_repoFila).SomeCustomMethod();// <-- works
    }
}

鉴于此,我有一些问题:

  • 制作该演员表 (FilaRepositorio) 是一种好的或可接受的做法吗?
  • 如果这不是一个好的做法,如何为这种情况编写好的代码?

【问题讨论】:

  • 正如两个答案已经描述的那样,从接口转换回实现是一个坏主意。如果你这样做,绝对没有理由再拥有这个界面。更一般地说,如果您的代码依赖于具体类型,则您违反了Dependency Inversion Principle 声明类应该依赖于抽象的规定。此外,将接口显式转换为这种具体类型违反了Liskov Substitution Principle
  • IMO,我不认为这个问题“主要基于意见”,因此它应该被关闭。文献和常见的设计原则对此非常清楚,所以这个问题实际上可以通过参考文献来相当客观地回答(就像我在之前的评论中所做的那样)。

标签: c# oop design-patterns dependency-injection simple-injector


【解决方案1】:

有几个选项可用。制作演员表的主要问题是它是一个实现问题。

如果注入的对象不是FilaRepositorio会怎样?

通过进行强制转换,您将类与不能保证是注入依赖项的实现问题紧密耦合。因此,构造函数对于执行其功能所需的内容并不完全真实。

这表明需要练习Explicit Dependencies Principle

显式依赖原则指出:

方法和类应该明确要求(通常通过 方法参数或构造函数参数)任何协作对象 他们需要才能正常工作。

避免这种情况的一种方法是创建一个派生接口,显式公开其依赖项的所需功能。

public interface IFilaRepositorio : IRepositorio<FilaModel> {
    void SomeCustomMethod();
}

public class FilaRepositorio : Repositorio<FilaModel>, IFilaRepositorio {
    public void SomeCustomMethod() {
        //...other code removed for brevity.
    }
}

并让Processador 依赖于更有针对性的抽象。

现在根本不需要演员表,类明确表达了它的需要。

public class Processador {
    private readonly IFilaRepositorio _repoFila;

    public Processador(IFilaRepositorio  repoFila) {
        _repoFila = repoFila;
    }

    public void Processar() {
        _repoFila.SomeCustomMethod(); // <-- works
    }
}

【讨论】:

  • 但是如果在 Visual Studio 中我想导航到具体的实现怎么办。当许多类使用相同的接口时,这将成为一场噩梦。
【解决方案2】:

如果您需要从应用程序的任何部分访问特定方法,则该特定方法必须是抽象的一部分,否则无法保证您在更改具体类时可以使用它。

我不相信你使用强制转换是一个好主意,在这种情况下通常做的是创建一个特定的接口来定义你可能需要使用的任何其他方法:

public interface IFilaRepositorio : IRepositorio<Fila>
{
     void SomeCustomMethod();
}

然后在您认为需要使用它的代码的任何部分中使用和声明该特定接口:

public class Processador
{
    private IFilaRepositorio _repoFila;
    public Processador(IFilaRepositorio  repoFila)
    {
        _repoFila = repoFila;
    }

    public void Processar()
    {
        _repoFila.SomeCustomMethod();
    }
}

【讨论】:

  • 不要像IFilaRepositorio 接口那样为存储库创建一对一的接口,还可以看看thisthis 等更多的SOLID 方法。
  • 感谢@Steven,我在 S.O. 的回答中学到了很多东西。我的下一个问题是:为每种具体类型创建一个接口是否正确?但我认为你的评论回答了这个问题。
  • @Ewerton 这不是一个非黑即白的答案。应用程序越大,您就越有可能获得您可以定义的非常常见的概念(如查询、用例、验证等),这些概念应该由整个概念使用它们自己的抽象。所以我会说你通常会重用抽象。这将带来最佳的可维护性
  • 一如既往地感谢您的宝贵评论,@Steven。很高兴看到抽象概念以有用的方式实现。
猜你喜欢
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
相关资源
最近更新 更多