我有一张课程凭证:
public abstract class Voucher
{
public int Id { get; set; }
public decimal Value { get; protected set; }
public const string SuccessMessage = "Applied";
}
和子类GiftVoucher
public class GiftVoucher : Voucher
{
}
和另一个子类DiscountVoucher
public class DiscountVoucher : Voucher
{
public decimal Threshold { get; private set; }
public string FailureMessage { get { return "Please spend £{0} to use this discount"; } }
}
您可以看到,DiscountVoucher有一些特定属性Threshold和FailureMessage,分别代表您需要花费的金额来获得折扣,如果用户没有花这笔钱,则显示失败消息。
我的问题是这个。我有一个Voucher对象的集合,我在代码中不想做的事情是这样的
if (voucher is DiscountVoucher)
{
// cast voucher to a DiscountVoucher and then call the specific methods on it
}
因为这根本不可维护。同时我不想将这些特定方法放在Voucher抽象类中,因为它们不适用于所有类型的优惠券。有谁知道如何设计这个功能?
答案 0 :(得分:4)
一般情况下:不!
在没有任何处理特殊情况的代码的情况下处理通用代码流中的特殊方案不起作用。
然而,在某些情况下,你可以作弊。您可以在抽象基类中实现虚拟方法,该基类提供默认的“无”实现。
可以是返回null,0或者什么也不做的方法。
在这种情况下
public virtual string FailureMessage { get { return string.Empty; } }
可能是一个合理的实施。
我猜你的实现看起来很像template method pattern。然后,对于不适用于某些实现的步骤,使用void实现是完全正常的。
答案 1 :(得分:3)
不,你不能,因为迭代更多通用对象然后调用特定方法将需要使用多态在每个子类中具有专用功能。如果没有超类中的方法来覆盖,则无法获得所需的内容。
答案 2 :(得分:3)
你在这里得到的是战略模式的一个版本。我不认为最终不得不决定你是否有一种类型的优惠券,但你可以限制变种的数量 - 如果你愿意,可以使用界面来限制优惠券类别。
例如,您最终可能会获得五个实现名为“StandardVoucher”的接口的凭证和三个名为“DiscountVoucher”的凭证,但不必处理八个案例,而现在只需要两个。
界面可以涵盖一系列显示可用方法的优惠券,而不必担心每个优惠券实施的细节。
答案 3 :(得分:1)
我认为你对你所描述的代码持怀疑态度是正确的。
我的第一个想法是,如果DiscountVoucher
的成员不够广泛,不能在Voucher
中以虚拟或抽象的形式存在,那么以Voucher
作为参数的函数不应该碰他们。
所以,要解决这个问题,我会说你可以做两件事之一:
首先,您可以向Voucher
添加虚拟方法或属性,例如
public abstract class Voucher
{
public int Id { get; set; }
public decimal Value { get; protected set; }
public const string SuccessMessage = "Applied";
public decimal Threshold { get { return 0.0; } }
public string FailureMessage { get { return ""; } }
}
其次,您可以添加对每个Voucher
执行所需操作的方法。您已将它们组合在一起作为凭证,因此请考虑它们的共同点。例如,如果GiftVoucher
和DiscountVoucher
都在进行自己的计算以确定它们是否适用于当前ShoppingCart
,那么您可以使用名为{{1}的Voucher
方法检测这个。例如,
isValid()
答案 4 :(得分:1)
有些情况下你必须施展。在这里,我将实现一般错误检查机制:
public abstract class Voucher
{
public int Id { get; set; }
public decimal Value { get; protected set; }
public virtual string SuccessMessage { get { return "Applied"; } }
public virtual string FailureMessage { get { return String.Empty; } }
public virtual bool Ok { get { return true; } }
}
public class GiftVoucher : Voucher { }
public class DiscountVoucher : Voucher
{
public decimal Threshold { get; private set; }
public override string FailureMessage { get { return "Please spend £{0} to use this discount"; } }
public override bool Ok { get { return Value >= Threshold; } }
}
然后,您可以测试任何类型的凭证的完整性,而无需强制转换:
if (voucher.Ok) {
Console.WriteLine(voucher.SuccessMessage);
} else {
Console.WriteLine(voucher.FailureMessage);
}
作为一般规则,尝试让对象做自己的东西(这里测试它们是否正常),而不是从“外部”进行。甚至事实上,GiftVoucher中不会出现任何错误也不需要被“外部世界”所知。