构造函数需要参数的抽象类与具有抽象get-only属性的抽象类之间的区别

时间:2015-07-24 14:48:04

标签: c# inheritance design-patterns abstract-class

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
    }

}

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

4 个答案:

答案 0 :(得分:1)

构造函数参数始终是代码的必填字段。 使其成为属性,使其成为可选的,并且可以使客户端混淆使用。 如果不使用它们,那么它可能会导致运行时出错。

如果你采用第二种方法,我更喜欢界面,而不是抽象类。

答案 1 :(得分:1)

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

在第二个示例中(基类具有abstract get - 仅属性),具体实现更容易提供返回值的更复杂的“计算”,并且它们可能可能假设当前实例已经正确构建(并利用它)。

答案 2 :(得分:1)

我不确定你应用列出的方法的问题的背景是什么,所以我对我的回答在黑暗中有点刺伤。从来没有,这是我的2美分。

第一种方法允许您在基类的方法中使用ILogServiceExecuterPluginName。这对于日志记录等常见活动尤其有用。

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

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

相反,使用接口可能是故障单,如果您尝试做的只是强制具体类遵守其实现中的方法和属性的契约。

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

附注:您可以利用依赖注入(通过DI框架,如Autofac等)来控制传递到具体类的对象的生命周期。即您可以使您的日志服务成为单例,并将其相同的实例传递给您的所有实现。

答案 3 :(得分:1)

考虑以下三种“2d矩阵”抽象类的实现:

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); 
}

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

该类的第二个版本需要客户端做一些工作,但是会允许类似IdentityMatrix的类型可能不需要数组作为后备存储(它可以简单地定义其索引的getter)在rowcolumn相等时返回1.0,否则返回0.0)。不幸的是,如果基类需要同时支持可变和不可变的子类型,它将无法正常工作,因为派生类无法覆盖基类属性getter,也无法定义读写属性。

该类的第三个版本通过具有一个具体的非虚拟属性getter来避免第二个版本的问题,除了链接到虚方法之外什么都不做。派生类在覆盖该方法和定义new非虚拟读写属性方面都没有问题,该属性的getter链接到同一个方法,并且其setter链接到不同的虚方法。

是否应该在基类中实现属性的问题通常取决于是否存在某种派生类可能希望具有不同于大多数情况的不同形式的后备存储的现实可能性,因此可能,没有用于最常见类型的后备存储字段。请注意,如果90%的派生类可以从一个字段和代码中受益,但有一些不会,那么拥有一个“中级”抽象类(例如ArrayBackedMatrix)可能会有所帮助。源自基数,但包括公共字段。