假设定义了两个简单的装饰器:
// decorated object
class Product : IComponent {
// properties..
// IComponent implementation
public Decimal GetCost() {
return this.SelectedQuantity * this.PricePerPiece;
}
}
// decorators
class FixedDiscountDecorator : IComponent {
IComponent component;
// IComponent implemantation
public Decimal GetCost() {
// ...
}
public FixedDiscountDecorator(IComponent product, Decimal discountPercentage) {
// ...
}
}
class BuyXGetYFreeDiscountDecorator : IComponent {
IComponent component;
// IComponent implemantation
public Decimal GetCost() {
// ...
}
// X - things to buy
// Y - things you get free
public BuyXGetYFreeDiscountDecorator(IComponent product, Int32 X, Int32 Y) {
// ...
}
}
这些装饰器具有不同的构造函数签名(参数列表)。我正在寻找一种模式来应用于构造装饰器,就像它可以与工厂模式一样。我的意思是我放了一个字符串并获得一个装饰器实例。
因此,我想简单地将一系列装饰器应用于给定的产品:
var product = new SimpleProduct {
Id = Guid.NewGuid(),
PricePerPiece = 10M,
SelectedQuantity = 10,
Title = "simple product"
};
var itemsToApplyTheDiscount = 5;
var itemsYouGetFree = 2;
var discountPercentage = 0.3M;
var discountA = new BuyXGetYFreeDecorator(product, itemsToApplyTheDiscount, itemsYouGetFree);
var discountB = new FixedDiscountDecorator(discountA, discountPercentage);
答案 0 :(得分:3)
这可以使用IOC容器或类似的东西来解决。我头脑中突然出现的一些容器是Unity
,Windsor
和SimpleInjector
。我会把IOC容器留给其他答案,因为我没有经验。
但是,我真的很想知道为什么要注入原生值。
看到如何使用类,在注释构造函数中注入折扣百分比或x购买免费项目这样的注入值会感觉很奇怪。
如果用户输入10(百分比)而不是0.1(十进制)作为折扣参数怎么办?它含糊不清。另外,如果在构造函数中添加检查,则会对该类赋予另一个责任,违反SRP。
我建议添加DiscountPercentValue
或BuyXGetYFreeValue
等DTO。此外,我更喜欢将折扣值设置为上下文,或者为其注入存储库。否则,有一天您需要factories
来处理与折扣相关的if-else业务规则。
EDIT1:
通常我只将构造函数验证作为null
检查。除can be
被视为违规之外的其他验证。
至于存储库的东西,我想象一些这样的接口:
public interface IDiscountPercentageProvider{
DiscountValue Get();
}
public interface IBuyXGetYFreeValueProvider{
BuyXGetYFreeValue Get();
}
然后在您的服务类中,您可以使用以下内容:
class FixedDiscountDecorator : IComponent {
IComponent component;
// IComponent implemantation
IDiscountPercentageProvider discountPercentageProvider;
public Decimal GetCost() {
DiscountValue discount = discountPercentageProvider.Get();
// ...
}
public FixedDiscountDecorator(IComponent product
, IDiscountPercentageProvider discountPercentageProvider) {
// ... just null checks here
}
}
起初这可能很复杂。但是,它提供了更好的API设计(现在使用装饰器时没有歧义)。使用此功能,您可以将DiscountValue
创建为protects its invariants的类,从而使其在其他类中使用更安全。
答案 1 :(得分:3)
在您的示例中,您确实显示了应用于给定产品的装饰链,即:
var discountA = new BuyXGetYFreeDecorator(product, itemsToApplyTheDiscount, itemsYouGetFree);
var discountB = new FixedDiscountDecorator(discountA, discountPercentage);
那么问题是,有哪些模式可用于更改由指定属性管理的product
的状态,从上面的代码构建?使用您的示例并限制我的范围以确定product
成本:
public interface IComponent
{
decimal GetCost { get; set; }
}
我会创建一个代表IComponent
的产品类public class Product
{
public IComponent Price { get; set; }
}
除了装饰器类
之外,您可能还有一个“默认”实现public class BasePrice : IComponent
{
private Decimal _cost;
public decimal GetCost //as a property maybe use Cost with get; set; in IComponent
{
get { return _cost; }
set { _cost = value; }
}
}
我喜欢你的两个装饰器类,它们使用IoC并实现IComponent的成本变化(GetCost())。
到目前为止,我还没有添加任何东西,只有基本价格类。我接下来可能会做的是使用抽象类并将特定操作推迟到子类,如模板方法模式中所示。我的具体类将继承自这个基类。创建的具体类将取决于传递给Factory方法模式的操作类型,该类在下面称为WarrantyProcessFactory。
您提到使用app.config ...我喜欢用于指定要应用于product
的操作类型的枚举。因此,假设我希望将行动与产品的保修联系起来,以便我可以根据此处理产品。
public enum WarrantyAction
{
RefundProduct = 0,
ReplaceProduct = 1
}
我将使用保修申请类来表示客户对缺陷产品的补偿请求
public class WarrantyRequest
{
public WarrantyAction Action { get; set; }
public string PaymentTransactionId { get; set; }
public decimal PricePaid { get; set; }
public decimal PostageCost { get; set; }
public long ProductId { get; set; }
public decimal AmountToRefund { get; set; }
}
最后,我可以实现抽象模板方法,该方法将由代表保修操作枚举的具体类重写
public abstract class WarrantyProcessTemplate
{
protected abstract void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest);
protected abstract void CalculateRefundFor(WarrantyRequest warrantyRequest);
public void Process(WarrantyRequest warrantyRequest)
{
GenerateWarrantyTransactionFor(warrantyRequest);
CalculateRefundFor(warrantyRequest);
}
}
该类和前两个方法是抽象的,需要由子类实现。第三种方法只是调用两个抽象方法,并将WarrantyRequest实体作为参数传递。
客户希望退款的案例
public class RefundWarrantyProcess : WarrantyProcessTemplate
{
protected override void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest)
{
// Code to determine terms of the warranty and devalutionAmt...
}
protected override void CalculateRefundFor(WarrantyRequest warrantyRequest)
{
WarrantyRequest.AmountToRefund = warrantyRequest.PricePaid * devalutionAmt;
}
}
客户想要更换产品的案例
public class ReplaceWarrantyProcess : WarrantyProcessTemplate
{
protected override void GenerateWarrantyTransactionFor(WarrantyRequest warrantyRequest)
{
// Code to generate replacement order
}
protected override void CalculateRefundFor(WarrantyRequest warrantyRequest)
{
WarrantyRequest.AmountToRefund = warrantyRequest.PostageCost;
}
}
public static class WarrantyProcessFactory
{
public static WarrantyProcessTemplate CreateFrom(WarrantyAction warrantyAction)
{
switch (warrantyAction)
{
case (WarrantyAction.RefundProduct):
return new RefundWarrantyProcess();
case (WarrantyAction.ReplaceProduct):
return new ReplaceWarrantyProcess();
default:
throw new ApplicationException(
"No Process Template defined for Warranty Action of " +
warrantyAction.ToString());
}
}
}
我要考虑的另一种模式是策略方法模式。在这种情况下,Context类将所有计算推迟到其抽象类或接口引用的“ConcreteStrategy”。我发现Scott Millet的书“Professional ASP.NET Design Patterns”作为一种有用的资源并经常回归它。
我愿意接受评论和批评。