【问题标题】:Ensure that all classes will share same method and fields确保所有类将共享相同的方法和字段
【发布时间】:2017-02-19 22:22:45
【问题描述】:

我的数据访问层我想使用 DTO 并确保所有网关(表数据网关)都将实现返回特定于给定网关的某些 DTO 列表的方法。 为了实现这一点,我创建了这样的 empty 界面

public interface IDataTransferObject{}

所有 DTO 都像这样实现了这个接口

public class UserAccountTypeDTO : IDataTransferObject
{
    public int Id { get; set; }
    public int AccountTitle { get; set; }
    public int CreditTypeId { get; set; }
    public bool Active { get; set; }
}

然后我为我的所有网关制作了通用接口

public interface IDefaultGateway<D> where D:IDataTransferObject
{
    List<D> Read(SqlDataReader sqlReader);
}

最后通过我的网关实现了 IDefaultGateway

class UserAccountTypeGW : IDefaultGateway<UserAccountTypeDTO>
{
    private const string Table = " UserAccountType ";

    private string SelectAll = "select * from" + Table + "";
    private string SelectById = ...;


    public List<UserAccountTypeDTO> Read(SqlDataReader sqlReader)
    {
        throw new NotImplementedException();
    }
}

是否可以创建空接口只是为了以某种方式将类组合在一起,即使它们不共享任何行为?

网关中的字段是相同的,只是它的值在变化。我确实将字段复制并粘贴到每个网关,我想知道是否有更快/更懒的方法来做到这一点。

  • 接口和字段不是朋友,因此它可以被定义为属性。由于需要在每个 DTO 中创建构造函数来初始化属性并将 set 设为私有,因此结果比我所做的要差一些。
  • 抽象类与接口几乎相同
  • 在这种情况下,创建基类、定义字段和方法并在网关构造函数中初始化(或覆盖)它们更好,或者与我所做的几乎相同?

【问题讨论】:

  • 为什么所有类都需要共享相同的方法和字段?什么代码会使用IDataTransferObjectIDefaultGateway&lt;D&gt;?似乎它们的存在只是为了提醒程序员遵循代码中的特定模式,但实际上没有人会通过您描述的接口或基类来使用它们。
  • 是的,他们在那里只是为了提醒我不要忘记那个方法。

标签: c# .net


【解决方案1】:

您需要搜索的单词称为Marker Interfaces。这是来自MSDN的指南:

避免使用标记接口(没有成员的接口)。 如果您需要将一个类标记为具有特定特征(标记),通常使用自定义属性而不是接口。

话虽如此,检查一个类是否实现了接口比检查它是否具有属性更容易。

正如@Groo 在这个答案的评论部分提到的那样,这是一个非常好的观点:

与属性相比,标记接口至少还提供了一些编译时检查

【讨论】:

  • 与属性相比,标记接口至少也提供了一些编译时检查。
  • 所以在正确性和便利性之间进行权衡。非常感谢。我发现两个关于抽象类的 cmets 都很有用,但我将其标记为正确答案。
【解决方案2】:

关于问题的第二部分(如何避免代码重复),这就是抽象类的用途。

如果需要,您可以提供一些派生类可以覆盖的默认值:

abstract class BaseGateway<T> : IDefaultGateway<T> where T : IDataTransferObject
{
    readonly string _tableName;
    readonly string _selectAll;

    public BaseGateway()
    {
        // default table name
        _tableName = this.GetType().Name.Replace("DTO", "");
        _selectAll = $"select * from {_tableName}";
    }

    // these members are virtual, so that they can be overriden
    protected virtual string TableName => _tableName;
    protected virtual string SelectAll => _selectAll;

    // derived classes should implement their own 'Read' method
    public abstract List<T> Read(IDataReader sqlReader);
}

但是,请注意,执行此类操作会使您的代码容易受到 SQL 注入攻击。最好使用 ORM,或者至少像 Dapper 这样的“微型”ORM。

Dapper 通过几个简单的 IDbConnection 扩展方法为您提供了这种简单性:

public class Dog
{
    public int Age { get; set; }
    public string Name { get; set; }
}            

using (IDbConnection conn = OpenConnection())
{
    var dog = conn
        .Query<Dog>("Select * from Dog where Age = @Age", new { Age = 10 })
        .FirstOrDefault();
}

【讨论】:

  • 谢谢,这看起来不错。请问您何时/如何使用sql注入?我并不是说这是不可能的,但我不熟悉这个问题,我不明白怎么可能捕获我发送到 db 的 sql 命令并更改它。我使用 SqlCommand 及其属性参数来设置 where 子句参数。我听说它应该处理危险的输入。
  • @Bendom:哦,那没问题,我只是想确保您和其他读者都知道这一点,因为看起来您自己会构建大量的 sql 字符串。参数化查询可防止注入,而 Dapper 还会使用返回的数据自动填充类,并且是 very fast in doing so
【解决方案3】:

避免使用那些标记界面。现在使用标记接口的唯一“好”理由是,如果您打算使用反射来操作某些类型的对象(这次不是这种情况)。

在这种情况下——抽象类似乎是最好的选择。在抽象类和属性中定义 Read() 方法及其默认实现。根据需要在所有后代中使用 virtual 方法覆盖它。这样,您甚至不需要包装类来读取每种特定类型的对象,因为您将能够引用该对象并直接在其上调用 Read()

【讨论】:

    猜你喜欢
    • 2014-12-01
    • 1970-01-01
    • 2012-09-20
    • 2019-06-10
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 2019-09-26
    • 2019-06-27
    相关资源
    最近更新 更多