“嵌套”/组合策略模式?

发布于 2024-12-21 11:03:33 字数 4780 浏览 2 评论 0原文

我知道标题很奇怪,所以让我尝试做一些基本设置。

我有一个名为 StyleBundle 的对象。基于两件事,StyleBundle 的持续时间和 StyleBundle 的“类型”(Unlimited 或 PerStyle)将决定 StyleBundle 的总体价格。因此,这是 StyleBundle 的快速片段:

public class StyleBundle
{
    public decimal Price {get; set;}
    public StyleType Type {get; set;} //STyleType is a simple enum, either "Unlimited" or "PerStyle"
    public Duration Duration {get; set;}
}

这是持续时间。基本上,它有一个枚举,即 DurationType,可以是 DurationType.OneYear、DurationType.TwoYears 等值...

public class Duration
{
    public Duration(TimeSpan timeSpan, string name, DurationType type)
    {
        this.TimeSpan = timeSpan;
        this.Name = name;
        this.Type = type;
    }

    public TimeSpan TimeSpan { get; set; }
    public string Name { get; set; }
    public DurationType Type { get; set; }
}

在我的 StyleBundle 类中,我将 Duration 传递给名为 StyleBundlePricingStrategy 的策略工厂。这是该类:

public class StyleBundlePricingFactory
{
     public static IPricingStrategy GetPricing(Duration duration)
     {
         if (duration.Type == DurationType.OneYear) { return new OneYearPricingStrategy(); }
         if (duration.Type == DurationType.TwoYear) { return new TwoYearPricingStrategy(); }
         etc...
         etc...
     }
 }

返回的类实现 IPricingStrategy 接口:

public interface IPricingStrategy
{
    decimal GetPriceFor(StyleBundle aStyleBundle);
}

它获取 StyleBundle 的价格。每个策略类都封装了如何检索给定 DurationType 的价格。以下是 OneYearPricingStrategy 类的示例:

public class OneYearPricingStrategy : IPricingStrategy
{
    public decimal GetPriceFor(StyleBundle aStyleBundle)
    {
        if (aStyleBundle.StylePricingType == StylePricingType.PerStyle)
        {
            return aStyleBundle.Products.Count() * 2500m;
        }
        else
        {
            return  50000m;
        }
    }
}

好的,非常基本的策略设置。

让我烦恼的是,如果您查看“OneYearPricingStrategy”类中的这行代码:

if (aStyleBundle.StylePricingType == StylePricingType.PerStyle)

您会发现我仍然必须在 Strategy 类中使​​用条件来解释 StyleBundle 类型。 StyleBundle 的类型将影响价格的计算方式。

对我来说,这是糟糕的设计,因为我为每个策略类编写“OneYearPricingStratety”、“TwoYearPricingStrategy”等... StylePricingType 条件被复制并粘贴到所有策略类中。

这不好,当我必须添加新的 StylePricingType 时会发生什么?我必须返回到每个 PricingStrategy 类并更新代码,因此整个 SRP(以及其他内容)都被抛到了窗外……

我需要的是一种实现某种类型模式的方法,该模式将允许我将两种“策略”(Duration 与 StyleBundleType)结合起来,并允许规则存在于一个地方......而不是散布在代码中。

当您实现一个策略时,很容易理解策略模式,但这是两个策略的组合,我知道我现在编写的方式不是一个好的实践,并且没有实现我想要的效果。

也许这是错误的模式?

任何指示将不胜感激。

谢谢, Mike

编辑:

针对 Garret 的回答,我想提供一些关于我如何首先获得策略模式的更多细节。我没有首先将实现硬塞到策略中,而是看到了我认为的代码味道,并决定策略模式可以帮助我。

最初,StyleBundle.Price 属性过去看起来像这样:

public decimal Price
{
    get
    {
        if (this.StylePricingType == StylePricingType.PerStyle)
        {
            if (this.Duration.Type == DurationType.ThreeDays)
            {
                _price = 1500m;
            }
            else if (this.Duration.Type == DurationType.OneYear)
            {
                _price = 2500m;
            }
            else if (this.Duration.Type == DurationType.TwoYears)
            {
                _price = 2000m;
            }
            else if (this.Duration.Type == DurationType.ThreeYears)
            {
                _price = 1650m;
            }
        }
        else if (this.StylePricingType == StylePricingType.Unlimited)
        {
            if (this.Duration.Type == DurationType.ThreeDays)
            {
                throw new Exception("You can not have a StyleBundle of type Unlimited for a Duration of three days.");
            }
            else if (this.Duration.Type == DurationType.OneYear)
            {
                _price = 50000m;
            }
            else if (this.Duration.Type == DurationType.TwoYears)
            {
                _price = 40000m;
            }
            else if (this.Duration.Type == DurationType.ThreeYears)
            {
                _price = 33500m;
            }
        }
        else
        {
            throw new Exception("Illegal StylePricingType passed to Product.");
        }
        return _price;
    }
    private set
    {
        _price = value;
    }
}

我发现每当我添加另一个 Duration 类型时,我都需要进入 StyleBundle 并更改代码......对我来说,这似乎是足以寻求一个激励原则更好的解决方案。

现在,通过将策略设计模式应用于此问题,我的 StyleBundle.Price 属性如下所示:

public decimal Price
    {
        get
        {
            return _pricingStrategy.GetPriceFor(this);
        }
        private set
        {
            _price = value;
        }
    }

其中 _pricingStrategy 是 IPricingStrategy,并且通过调用 StyleBundlePricingFactory.GetPricing(duration) 类来决定新建哪个实现者在 StyleBundle 的构造函数中。

I know the title is weird, so let me try to do some basic setup.

I have an object called StyleBundle. Based on two things, the Duration of the StyleBundle, and the "type" of the StyleBundle (Unlimited or PerStyle), will determine the overall Price of the StyleBundle. So, here is a quick snipped of StyleBundle:

public class StyleBundle
{
    public decimal Price {get; set;}
    public StyleType Type {get; set;} //STyleType is a simple enum, either "Unlimited" or "PerStyle"
    public Duration Duration {get; set;}
}

Here is Duration. Basically, it has an enum which is DurationType, which can be values like DurationType.OneYear, DurationType.TwoYears, etc...

public class Duration
{
    public Duration(TimeSpan timeSpan, string name, DurationType type)
    {
        this.TimeSpan = timeSpan;
        this.Name = name;
        this.Type = type;
    }

    public TimeSpan TimeSpan { get; set; }
    public string Name { get; set; }
    public DurationType Type { get; set; }
}

In my StyleBundle class, I pass Duration to a Strategy factory called StyleBundlePricingStrategy. Here is that class:

public class StyleBundlePricingFactory
{
     public static IPricingStrategy GetPricing(Duration duration)
     {
         if (duration.Type == DurationType.OneYear) { return new OneYearPricingStrategy(); }
         if (duration.Type == DurationType.TwoYear) { return new TwoYearPricingStrategy(); }
         etc...
         etc...
     }
 }

the classes being returned implement the IPricingStrategy interface:

public interface IPricingStrategy
{
    decimal GetPriceFor(StyleBundle aStyleBundle);
}

Which gets the Price for a StyleBundle. Each strategy class encapsulates how a price is retrieved for a given DurationType. Here is an example of the OneYearPricingStrategy class:

public class OneYearPricingStrategy : IPricingStrategy
{
    public decimal GetPriceFor(StyleBundle aStyleBundle)
    {
        if (aStyleBundle.StylePricingType == StylePricingType.PerStyle)
        {
            return aStyleBundle.Products.Count() * 2500m;
        }
        else
        {
            return  50000m;
        }
    }
}

Okay, so pretty basic Strategy setup.

The thing that's eating me up is that if you look at this line of code in the "OneYearPricingStrategy" class:

if (aStyleBundle.StylePricingType == StylePricingType.PerStyle)

you'll see that I still have to use a conditional in the Strategy class to account for the StyleBundle type. The type of StyleBundle will affect how the Price is calculated.

To me, this is bad design, b/c for each strategy class I write "OneYearPricingStratety", "TwoYearPricingStrategy", etc... the StylePricingType conditional gets copied and pasted to all of the Strategy classes.

This is not good, b/c what happens when I have to add a new StylePricingType? I'll have to go back into each PricingStrategy class and update the code, so there goes the whole SRP out the window (along with other things)...

what I need is a way to implement some type of pattern that will allow me to combine the two "stratagies" (the Duration with the StyleBundleType) and allow the rules to live in one place... not to be strewn across code.

It's easy to digest the STrategy pattern when you're implementing for one Strategy, but this is a combination of two, and i know the way I have it written now is not a good practice, and does not accomplish what I want it to.

Maybe it's the wrong pattern?

Any pointers would be more than appreciated.

Thanks,
Mike

EDIT:

in reaction to Garret's answer, I want to provide some more detail on how I got to the Strategy pattern in the first place. I did not shoehorn the implementation into Strategy first, but saw what I thought was a code smell, and decided the Strategy pattern could help me.

Initially, the StyleBundle.Price property used to look like this:

public decimal Price
{
    get
    {
        if (this.StylePricingType == StylePricingType.PerStyle)
        {
            if (this.Duration.Type == DurationType.ThreeDays)
            {
                _price = 1500m;
            }
            else if (this.Duration.Type == DurationType.OneYear)
            {
                _price = 2500m;
            }
            else if (this.Duration.Type == DurationType.TwoYears)
            {
                _price = 2000m;
            }
            else if (this.Duration.Type == DurationType.ThreeYears)
            {
                _price = 1650m;
            }
        }
        else if (this.StylePricingType == StylePricingType.Unlimited)
        {
            if (this.Duration.Type == DurationType.ThreeDays)
            {
                throw new Exception("You can not have a StyleBundle of type Unlimited for a Duration of three days.");
            }
            else if (this.Duration.Type == DurationType.OneYear)
            {
                _price = 50000m;
            }
            else if (this.Duration.Type == DurationType.TwoYears)
            {
                _price = 40000m;
            }
            else if (this.Duration.Type == DurationType.ThreeYears)
            {
                _price = 33500m;
            }
        }
        else
        {
            throw new Exception("Illegal StylePricingType passed to Product.");
        }
        return _price;
    }
    private set
    {
        _price = value;
    }
}

I saw that any time I would add another Duration type, I would need to come into StyleBundle and change code... to me, that seemed like motivating principle enough to seek a better solution.

Now, with the application of the Strategy design pattern to this problem, my StyleBundle.Price property looks like this:

public decimal Price
    {
        get
        {
            return _pricingStrategy.GetPriceFor(this);
        }
        private set
        {
            _price = value;
        }
    }

where _pricingStrategy is IPricingStrategy, and the decision of which implementor to new up is decided on by calling the StyleBundlePricingFactory.GetPricing(duration) class in StyleBundle's constructor.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

梦里°也失望 2024-12-28 11:03:33

在编写代码之前不要尝试思考模式。然后重构代码。有时您会重构为一种模式。

策略模式通常是在您想要委派类的行为逻辑时保留的。例如,如果我有一个类 ChessPlayer,那么 Grandmaster 实现 ChessStrategyNovice Implements ChessStrategy 将是更改行为策略的好方法我的 ChessPlayer,而移动棋子的 ChessPlayer 界面则不必更改。

就您而言,您只拥有数据,而不是复杂的价格计算策略,因此我会寻找合适的数据结构。在 Durations X PricingStyles 上双重嵌套的 HashMap 可以正常工作。示例:

Price calculatePrice(Duration d, PricingStyle s) {
    return map.get(d).get(s);
}

PS:当谈到好的设计时,只要代码少,通常就是赢家。

Don't try to think of a pattern before you write code. Code then refactor. Occasionally you will refactor into a pattern.

The Strategy pattern is typically reserved for when you want to delegate the behavioral logic for a class. For instance, if I have a class ChessPlayer, then Grandmaster implements ChessStrategy and Novice implements ChessStrategy would be a good way to change the behavioral strategy of my ChessPlayer, while the interface of ChessPlayer that moves pieces around would not have to change.

In your case, you simply have data, not a complex price-calculating strategy, so I would look for an appropriate data structure. A doubly nested HashMap over Durations X PricingStyles would work fine. Example:

Price calculatePrice(Duration d, PricingStyle s) {
    return map.get(d).get(s);
}

PS: When it comes to good design, whatever takes less code is usually the winner.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文