[英]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
}
}
什么样的继承更可取? 我将使用第二种解决方案,但是我对抽象的数量有些怀疑。 您可以对这些方法进行一些解释吗?
构造函数参数始终成为代码的必填字段。 使其具有属性,使其成为可选属性,并且可能使客户端无法使用。 如果不使用它们,则可能会导致运行时错误。
如果您要使用第二种方法,则我希望使用接口而不是抽象类。
区别在于,在第一个示例(基类构造函数需要参数)中,必须在实例完全构造之前(由继承者)指定值。
在第二个例子中(基类有abstract
get
-only属性)它是具体的实施提供返回值的更复杂的“计算”些,他们可能会认为目前的实例已经正确构造(走的优势)。
我不确定您使用列出的方法时所遇到问题的背景如何,因此我的回应有些刺痛。 少不了,这是我的2美分。
第一种方法允许您在基类的方法中使用ILogService
和ExecuterPluginName
。 尤其对于诸如日志记录之类的常见活动,这可以派上用场。
仅出于这个原因,我更喜欢这种方法而不是第二种方法。
但是,如果您没有任何使用共享资源的通用逻辑,则将其保留在基类中几乎没有任何意义。 YAGNI在这里适用。 实际上,我可能会说得更远,如果没有通用逻辑,则基本抽象类是没有意义的。
如果您只是想强迫具体的类在其实现中遵守方法和属性的约定,那么使用接口可能会很麻烦。
此外,使用throw new NotImplementedException();
绝对是代码气味。
旁注:您可以利用依赖项注入(通过DI框架,例如Autofac等)来控制传递到具体类中的对象的生存期。 也就是说,您可以使您的日志记录服务成为单例,然后将其相同实例传递给所有实现。
考虑“二维矩阵”抽象类的以下三种实现:
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以row
和column
时返回1.0的可能性)。相等,否则为0.0)。 不幸的是,如果基类需要同时支持可变和不可变的子类型,那将不能很好地工作,因为派生类无法同时覆盖基类的属性getter和定义读写属性。
该类的第三个版本通过具有一个具体的非虚拟属性获取器避免了第二个版本的问题,该获取器除了链接到虚拟方法外不执行任何操作。 派生类在覆盖该方法和定义new
非虚拟读写属性方面都没有问题,该属性的getter链接至同一方法,setter链接至不同的虚拟方法。
是否应在基类中实现属性的问题通常取决于是否有现实的可能性,即某些派生类可能希望拥有不同于大多数典型形式的后备存储形式,因此可能没有用于最常见类型的后备存储字段。 请注意,如果90%的派生类将从一个字段和代码中受益,而其中一些则没有,那么从基类派生一个“中间级”抽象类(例如ArrayBackedMatrix
)可能会有所帮助但包括通用字段。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.