我正在查看一些旧代码,并遇到了一种抽象,该抽象为其每种派生/具体类型具有属性。我无法共享确切的代码,但是请想象一下,有许多操作要复杂得多,而不是简单的操作。
我以前从未遇到过类似问题,并且有很多问题吗?首先,这是我不知道的模式吗?如果是这样,那是什么?第二个问题,我应该如何重构它以便遵循扎实的原则?
如果需要,我会尽力想出一个更好的例子。
public enum ToolType
{
Unknown = 0,
HRMonitor,
Dumbell,
SomeForceDevice
}
public abstract class ToolData
{
private ToolData()
{
IsValid = false;
this.ToolType = ToolType.Unknown;
}
public ToolData(ToolType toolType)
{
this.ToolType = toolType;
}
public ToolType ToolType { get; }
public virtual bool IsValid { get; protected set; } = true;
public double LinkQuality { get; set; }
public NullToolDataValue NullData => this as NullToolDataValue;
public DumbellDataValue DumbellData => this as DumbellDataValue;
public HeartRateDataValue HRData => this as HeartRateDataValue;
public SomeForceDataValue SomeForceData => this as SomeForceDataValue;
}
public class NullToolDataValue : ToolData
{
public NullToolDataValue() : base(ToolType.Unknown)
{
IsValid = false;
}
}
public class DumbellDataValue : ToolData
{
public double WeightValue { get; private set; }
public DumbellDataValue(double weightValue) : base(ToolType.Dumbell)
{
this.WeightValue = weightValue;
}
public override string ToString()
{
return WeightValue.ToString(CultureInfo.InvariantCulture);
}
}
public class HeartRateDataValue : ToolData
{
public int HeartRate { get; private set; }
public HeartRateDataValue(int heartRate) : base(ToolType.HRMonitor)
{
this.HeartRate = heartRate;
}
public override string ToString()
{
return HeartRate.ToString(CultureInfo.InvariantCulture);
}
}
public class SomeForceDataValue : ToolData
{
public double LeftHandForceValue { get; private set; }
public double RightHandForceValue { get; private set; }
public int LeftHandPosition { get; private set; }
public int RightHandPosition { get; private set; }
public SomeForceDataValue(double lefthandValue, double rightHandValue, int leftHandPosition, int rightHandPosition) : base(ToolType.SomeForceDevice)
{
this.LeftHandForceValue = lefthandValue;
this.LeftHandPosition = leftHandPosition;
this.RightHandForceValue = rightHandValue;
this.RightHandPosition = rightHandPosition;
}
public override string ToString()
{
return $"{LeftHandForceValue.ToString(CultureInfo.InvariantCulture)}" +
$"| {LeftHandPosition.ToString(CultureInfo.InvariantCulture)}" +
$"| {RightHandForceValue.ToString(CultureInfo.InvariantCulture)}" +
$"| {RightHandPosition.ToString(CultureInfo.InvariantCulture)}";
}
}
正在通过以下方式使用/消耗它,它也缺少一些继承性和简洁性:
public class DumbellExcercise
{
public void ToolDataReceived(ToolData data)
{
if (data?.DumbellData == null) return;
//add value to some collection
Collection.Add(data.DumbellData.WeightValue);
}
}
public class HRExcercise
{
public void ToolDataReceived(ToolData data)
{
if (data?.HRData == null) return;
//add value to some collection
Collection.Add(data.HRData.HeartRate);
}
}
答案 0 :(得分:0)
好的,我要回答一下-希望这会有所帮助。
首先,ToolData不应包含任何References / Enums /列出其子类型的任何内容。因此,首先是斩波块:将对象强制转换为特定子类型的所有lambda属性。我可以理解它的吸引力-您知道 ToolType的一个实例恰好是FloobieTool,因此您调用instance.FloobieTool并神奇地获得了FloobieTool的演员表。但是...好吧,它伴随着一些问题,尤其是您违反了开放/封闭原则。如果知道他们正在使用FloobieTool的工作,则使调用该类的人员使用(FloobieTool)实例显式对其进行强制转换就没错。
下一步:工具类型。你为什么需要这个?如果您在ToolData的实例是FloobieTool的情况下,只需在IF条件下执行“ is”检查,就可以告诉:
void SomeFunc(ToolData toolData)
{
if (!(toolData is FloobieTool)) throw new Exception("Non-Floobie!");
// more code
}
我的意思是,该枚举实际上能为您带来什么?因为它具有一定的成本,所以它必须与实现ToolData的类列表保持同步。
此外,每个这些Exercise类的ToolDataReceived()中的部分似乎都很奇怪。我的意思是,您已经进行了练习,并且正在传递ToolData。为什么要存储Dumbell练习的金额?与仅存储ToolData相反。我的意思是,您要进行大量测试/发布/等等,只是要将哑铃 weight 添加到集合中。有什么原因不能仅仅存储ToolData实例并每天调用它?如果您确实需要专门存储哑铃信息,则可以执行以下操作:
public class DumbbellExercise
{
List<DumbbellDataValue> dumbbellData = new List<DumbbellDataValue>();
public void AddToolData(ToolData toolData)
{
if (toolData is DumbbellDataValue)
this.dumbbellData.Add((DumbbellDataValue)toolData);
}
}
希望这会有所帮助-在我们为您的实际问题制作一个抽象示例时,很难涉及太多细节:-)
答案 1 :(得分:0)
看过您的编辑后,我更加坚信重构此代码的方法是使用模式匹配。模式匹配至少需要C#7.0,因此我将提供一种几乎一样好的方法来实现7.0之前的版本。
使用ObsoleteAttribute
将属性标记为过时,并为true
参数传递error
。
[Obsolete("Use pattern matching instead.", true)]
public NullToolDataValue NullData => this as NullToolDataValue;
[Obsolete("Use pattern matching instead.", true)]
public DumbellDataValue DumbellData => this as DumbellDataValue;
[Obsolete("Use pattern matching instead.", true)]
public HeartRateDataValue HRData => this as HeartRateDataValue;
[Obsolete("Use pattern matching instead.", true)]
public SomeForceDataValue SomeForceData => this as SomeForceDataValue;
在编译器处理的任何代码中使用它们将导致编译器错误。如果您对它们进行了任何思考,则如果您也未更改该代码,则将获得运行时异常(在步骤3完成之后)。
修改使用这些属性的每个呼叫站点,改为使用模式匹配。如果您所做的只是问题中显示的内容,那么应该像这样简单:
public class DumbellExcercise
{
public void ToolDataReceived(ToolData data)
{
if (data is DumbellDataValue dumbell)
Collection.Add(dumbell.WeightValue);
// OR
if (!(data is DumbellDataValue dumbell))
return;
Collection.Add(dumbell.WeightValue);
}
}
第二个变体不是很漂亮,因为条件必须在条件可否定之前加上括号(嘿,至少VB具有IsNot
关键字;请参见图),但是您会获得与现有条件相同的早期回报代码。
您似乎使用的是至少C#6.0,因为您使用的是空分号运算符(?.
),但是如果您使用的不是至少7.0,则可以这样做:
public class DumbellExcercise
{
public void ToolDataReceived(ToolData data)
{
DumbellDataValue dumbell = data as DumbellDataValue;
if (dumbell != null)
Collection.Add(dumbell.WeightValue);
// OR
DumbellDataValue dumbell = data as DumbellDataValue;
if (dumbell == null)
return;
Collection.Add(dumbell.WeightValue);
}
}
删除属性。如果没有更多的编译器错误,那么不会使用这些属性,因此您可以自由地摆脱它们。
附加说明
IsValid
属性具有奇怪的双重性。它可以由派生类分配,但它也是虚拟的,因此也可以被覆盖。你真的应该选一个。如果这是我的决定,则将其保留为虚拟状态并将其设置为只读。
public abstract class ToolData
{
// Continue to assume it's true...
public virtual bool IsValid => true;
}
public class NullToolDataValue : ToolData
{
// ...and indicate otherwise as needed.
public override bool IsValid => false;
}