其构造函数需要参数的抽象 class 与具有抽象仅获取属性的抽象 class 之间的区别

Difference between abstract class whose constructor requires arguments, and abstract class with abstract get-only properties

public abstract class BaseProcessor
{

    public abstract void Initialize();

    private readonly string _executerPluginName;
    private readonly ILogService _logService;

    public BaseProcessor(string executerPluginName, ILogService logService)
    {
        this._executerPluginName = executerPluginName;
        this._logService = logService;
    }

    protected void CallExecutor()
    {
        this.Initialize();
        //common logic
    }
}

public class ConcreteProcessor : BaseProcessor
{

    public override void Initialize()
    {
        //concrete logic
    }

    public ConcreteProcessor(string executerPluginName, ILogService logService) : base(executerPluginName, logService)
    {
    }
}

public abstract class BaseProcessor
{

    public abstract void Initialize();

    protected abstract string ExecuterPluginName { get; }
    protected abstract ILogService LogService { get; }

    protected void CallExecutor()
    {
        this.Initialize();
        //common logic
    }
}

public class ConcreteProcessor : BaseProcessor
{
    protected override string ExecuterPluginName { get{ throw new NotImplementedException(); } }
    protected override ILogService LogService { get{ throw new NotImplementedException(); } }

    public override void Initialize()
    {
        //concrete logic
    }

}

什么样的继承更可取?我将使用第二种解决方案,但我对抽象数量有一些疑问。你能解释一下这些方法吗?

构造函数参数始终是您代码的必填字段。 使其成为 属性,使其成为可选的,并且会使客户感到困惑。 如果不使用这些,则可能会在 运行 时间内导致错误。

如果您要使用第二种方法,我更喜欢接口,而不是抽象 class。

一个区别是在第一个例子中(基础 class 构造函数需要参数),必须在实例完全构造之前指定值(由继承者)。

在第二个例子中(base class 有 abstract get-only properties)具体实现更容易提供更复杂的 "calculation" return 个值,它们可能可能 假设当前实例已经正确构造(并利用它)。

我不确定您应用所列方法的问题背景是什么,所以我的回答有点摸不着头脑。尽管如此,这是我的 2 美分。

第一种方法允许您在基础 class 的方法中使用 ILogServiceExecuterPluginName。这可以派上用场,尤其是对于日志记录等常见活动。

仅出于这个原因,我更喜欢这种方法而不是第二种方法。

但是,如果您没有任何使用共享资源的通用逻辑,那么将其保存在基础 class 中就没有什么意义了。 YAGNI 适用于此。 事实上,我可能会这么说,如果没有共同的逻辑,基本抽象 class 就没有意义。

如果您试图做的只是强制具体 classes 在其实现中遵守方法和属性的约定,那么使用接口可能是个不错的选择。

此外,使用throw new NotImplementedException();绝对是一种代码味道。

旁注:您可以利用依赖项注入(通过 DI 框架,如 Autofac 等)来控制传递到具体 class 中的对象的生命周期。 IE。你可以让你的日志记录服务成为一个单例,并将它的同一个实例传递给你的所有实现。

考虑“二维矩阵”抽象的以下三种实现方式class:

abstract public class Matrix2dv1
{
    double[,] data;
    protected Matrix2dv1(double[,] source)
    {
        data = (double[,])source.Clone();
    }
    public double this[int row,int column]
    {
        get { return data[row, column]; }
    }
}
abstract public class Matrix2dv2
{
    abstract public double this[int row, int column] { get; }
}
abstract public class Matrix2dv3
{
    public double this[int row, int column]
    {
        get { return getRowColumn(row,column); }
    }
    protected abstract double getRowColumn(int row, int column); 
}

class 的第一个版本代表衍生 classes 处理了一些更多的工作,但它要求每个实现都使用 double[,] 作为后备店。如果索引 getter 是虚拟的,派生的 classes 可以将虚拟数组(甚至 null)传递给构造函数并使用它们自己的后备存储,但在许多情况下它们仍然最终至少会浪费用于保存基础 class data 字段的存储空间。

class 的第二个版本需要客户端做一些工作,但会允许像 IdentityMatrix 这样的类型可能不需要数组作为后备存储的可能性(它当 rowcolumn 相等时,可以简单地将其索引 getter 定义为 return 1.0,否则为 0.0)。不幸的是,如果基础 class 需要同时支持可变和不可变子类型,它就不会很好地工作,因为派生的 class 不可能同时覆盖基础 class 属性 getter 并且还定义了一个可读写的 属性.

class 的第三个版本通过使用具体的非虚拟 属性 getter 避免了第二个版本的问题,它除了链接到虚拟方法外什么都不做。派生的 class 可以覆盖该方法并定义 new 非虚拟读写 属性 ,其 getter 链接到相同的方法并且其 setter 链接到不同的虚拟方法。

属性是否应该在基础 class 中实现的问题通常取决于是否存在一些派生的 classes 可能希望拥有与之前不同形式的后备存储的现实可能性。大多数情况下都是典型的,因此可能对最常见类型的后备存储字段没有用处。请注意,如果 90% 的派生 classes 会从字段和代码中受益,但有一些不会,那么拥有一个 "intermediate-level" 摘要 class 可能会有所帮助(例如 ArrayBackedMatrix)派生自基础但包含公共字段。