试图避免过多地了解不同类中的类

时间:2013-08-15 10:21:23

标签: oop design-patterns object-design

我需要帮助设计我正在编写的应用程序。在应用程序中,员工可以在项目中预订他们的工作(所谓的预订对象)。预订对象可选择具有预算。当预订对象的预算用尽时,员工必须无法预订该预订对象。

项目负责人应以两种方式之一设定预算金额:

  1. 设置表示某个金额的十进制数
  2. 通过人工日设定金额
  3. 例如,说预订对象X的预算为10,000美元是合法的。同样合法的是,X的预算包括一个大四的人工日,一个大三的3个人日​​,以及一个支持的5.5个人日。在内部,人工金额计算为货币金额(高级价格的2.5倍+初级价格的3倍+支持价格的5.5倍)。人工日数在某种程度上是一个计算器,但它应该是持久的,所以不仅仅是UI的东西。

    问题是如何在没有人工预算知道太多预订对象的情况下将价格注入人工预算。允许每个预订对象具有不同的级别价格(“高级”,“初级”,“支持”等)。

    我想出了以下课程。

    // could be designed as an interface, too
    public abstract class Budget
    {
      public abstract decimal Amount { get; }
    }
    

    货币金额和人日金额的两个特殊类别。

    public class MonetaryBudget : Budget
    {
      private decimal amount;
      public MonetaryBudget(decimal amount)
      {
        this.amount = amount;
      }
    }
    
    public class ManDayBudget : Budget
    {
      private decimal amount;
      public ManDayBudget(IEnumerable<ManDay> manDays)
      {
        this.amount = manDays.Sum(md => md.ManDays * PriceOf(md.Level));
      }
    }
    

    类的使用者现在可以编写如下内容:

    var bo_x = new BookingObject();
    bo_x.Budget = new MonetaryBudget(10000);
    bo_x.Budget = new ManDayBudget(new List<ManDay>
    {
      new ManDay { ManDays = 2.5, Level = "senior" },
      new ManDay { ManDays = 3.0, Level = "junior" },
      new ManDay { ManDays = 5.5, Level = "support" }
    });
    
    var bo_y = new BookingObject();
    bo_y.Budget = new ManDayBudget(new List<ManDay>
    {
      new ManDay { ManDays = 2.5, Level = "senior" },
      new ManDay { ManDays = 3.0, Level = "junior" },
      new ManDay { ManDays = 5.5, Level = "support" }
    });
    
    // bo_x.Budget.Amount == bo_y.Budget.Amount is not guaranteed to evaluate to true
    

    为了便利,我已经在具体类中释放了Budget.Amount属性的实现以及ManDay的定义。

    要求申请中的其他项目也有预算。它还不是很清晰。我应该如何设计我的类,以便ManDayBudget对价格查找逻辑不太了解,最好不要对预订对象有任何了解。我觉得我错过了一个可以进行计算的课程。

    修改

    要明确消费者应该做什么和能做什么:

    var bo_x = new BookingObject();
    bo_x.Budget = new ManDayBudget(new List<ManDay>
    {
      new ManDay { ManDays = 2.5, Level = "senior" },
      new ManDay { ManDays = 3.0, Level = "junior" },
      new ManDay { ManDays = 5.5, Level = "support" }
    });
    
    var bo_y = new BookingObject();
    bo_y.Budget = new ManDayBudget(new List<ManDay>
    {
      new ManDay { ManDays = 2.5, Level = "senior" },
      new ManDay { ManDays = 3.0, Level = "junior" },
      new ManDay { ManDays = 5.5, Level = "support" }
    });
    

    预订对象X(bo_x.Budget.Amount)的金额可以是7.600,Y中的金额可以是9.200,因为对于每个预订对象,定义了不同的价格。消费者说了这么多的人工预算,仅此而已。但是,必须以某种方式计算货币金额而不需要太多关于BookingObject的知识,以便以后重用该类。

    编辑2:

    X可以设置为高级到100的价格,从初级到80等等,Y可以设置高级到125的价格,从初级到100等等。因此即使是人日金额设置为两个预订对象的单数量相同的天数不同。

2 个答案:

答案 0 :(得分:1)

我认为您需要查看Decorator design pattern。我将尝试提出代表您的代码的代码示例;现在,这里有一些链接可以帮助您入门:

http://www.codeproject.com/Articles/42042/Decorator-Design-Pattern http://www.dofactory.com/Patterns/PatternDecorator.aspx

编辑:

你的装饰师看起来像这样:

public class BudgetDecorator : Budget
{
    private readonly IPriceCalculator _calc;

    private Budget budget;

    public BudgetDecorator(Budget budget, IPriceCalculator calc)
    {
        _calc = calc;
        budget = budget;
    }

    public override decimal Amount
    {
        get
        {
            ManDayBudget dayBudget = budget as ManDayBudget;

            if (dayBudget != null)
            {
                return dayBudget.ManDays.Sum(md => md.ManDays * _calc.PriceOf(md.Level));
            }

            return budget.Amount;
        }
    }
}

你将一个进行价格计算的对象传递给它的ctor。这就是预算本身永远不需要知道如何计算价格的方法,另一个对象负责这一点。然后你像这样使用装饰器:

var b2 = new ManDayBudget(new List<ManDay>
{
    new ManDay { ManDays = 2.5, Level = "senior" },
    new ManDay { ManDays = 3.0, Level = "junior" },
    new ManDay { ManDays = 5.5, Level = "support" }
});

var d = new BudgetDecorator(b2, null /* Replace with object that does calcuation. */);

Console.WriteLine(d.Amount);

我稍微改变了装饰器。如果你稍微改变了ManDayBudget:

public class ManDayBudget : Budget
{
    public IEnumerable<ManDay> ManDays { get; set; } // Add this property.

    public ManDayBudget(IEnumerable<ManDay> manDays)
    {
        this.ManDays = manDays;        
    }

    // Rest of code.....
}

编辑:

忘了提一下,装饰器示例是否是一个geernic,因为它不关心传递给它的Budget的类型。实现所需的另一种方法是更改​​ManDayBudget类的构造函数以接受ICalculator对象并在ManDayBudget类中使用它,而不是使用装饰器。它只取决于您希望代码的通用程度。

答案 1 :(得分:0)

你的问题有点不清楚。

如果我明白你只需要注入基本预算价格,看起来解决方案可能有点简单。请注意,这被视为装饰模式。

public class ManDayBudget : Budget
{
  private decimal amount;
  public ManDayBudget(IEnumerable<ManDay> manDays, Budget baseBudget)
  {
    decimal baseAmount = baseBudget.Amount;
    this.amount = manDays.Sum(md => md.ManDays * PriceOf(md.Level));
    // do other things with baseAmount
  }
}

编辑:

虽然目前还不清楚,但我能掌握一些要求。假设这是BookingObject的当前设计:

public class BookingObject{
    public decimal Price { get; set; }
    public Budget Budget{ get{ /*return */ }
        set{
            value.BookingObject = this;
            /*set to private*/
        }
    }
}

使用此设计,Budget.Amount显然会依赖BookingObject的{​​{1}}进行Price计算,从而在每个计算之间建立依赖关系。更糟糕的是,每个派生预算都有不同的计算参数(静态货币,人工日等),这使得实施起来更加困难。

重建预算类

目前的Budget.Amount课很麻烦。它具有Budget计算的逻辑,但需要来自其他来源的Amount。您可以像这样重新设计Price,请注意您可以使用界面执行相同的操作:

Budget

如果需要,您可以将输入参数重新设计为另一个合约,例如public abstract class Budget{ public abstract decimal GetAmount(decimal price); } 。然后,您可以重新设计IPrice如下:

BookingObject

public class BookingObject{ public decimal Price { get; set; } public Budget Budget{ get; set; } public decimal Amount{ get{ return this.Budget.GetAmount(this.Price); } } } ManDayBudget的示例。

MonetaryBudget

这种设计虽然存在缺陷。首先,现在有两种方法可以检索预算金额。

  1. public class ManDayBudget : Budget{ public ManDayBudget(IEnumerable<ManDay> manDays) { this.manDays = manDays; } private readonly IEnumerable<ManDay> manDays; public override decimal Amount(decimal price){ return price * // the calculation here } } public class MonetaryBudget : Budget{ public MonetaryBudget(decimal amount) { this.amount = amount; } private readonly decimal amount; public override decimal Amount(decimal price){ return amount; } }
  2. decimal amount = bo.Amount
  3. 其次,decimal amount = bo.Budget.Amount(bo.Price);依赖于预算类。如果Budget类是构造函数注入的,那么它将使预订对象实例化更难。