一类的多种配置

时间:2018-10-08 11:53:04

标签: c# solid-principles

最近几天,我试图了解如何更改代码以遵循SOLID原则。

但是在这个示例中,我找不到合适的处理方式。

一个产品有一个Name和一个BarCode

public partial class Product
{
    public string Name { get; set; }
    public string BarCode { get; set; }
}

此外,某些产品必须具有Batch Number和/或Sale Deadline Date

public partial class Product
{
    public bool IsBatchNumberManaged { get; set; }
    public bool IsSaleDeadlineDateManaged { get; set; }
}

我有一些Stock,其中涉及这些Products,还有一些数据:QuantityBatch Number(如果需要),Sale Deadline Date(如果需要)。

public class Stock
{
    public Product Product { get; set; }
    public int Quantity { get; set; }
    // should be null if Product is not managed by BN,
    // else it has not to be null
    public string BatchNumber { get; set; }
    // should be null if Product is not managed by SDD,
    // else it has not to be null
    public DateTime? SaleDeadlineDate { get; set; }
}

我假设我的Stock记录应这样构建:

public interface IStock
{
    Product Product { get; set; }
    int Quantity { get; set; }
}

public interface IBatchNumberManagedStock : IStock
{
    string BatchNumber { get; set; }
}

public interface ISaleDeadlineDateManagedStock : IStock
{
    DateTime SaleDeadlineDate { get; set; }
}

public class Stock : IStock
{
    public Product Product { get; set; }
    public int Quantity { get; set; }
}

这是我不满意的课程:

public class BatchNumberManagedStock : IStock, IBatchNumberManagedStock
{ ... }

public class SaleDeadlineDateManagedStock : IStock, ISaleDeadlineManagedStock
{ ... }

public class BatchNumberAndSaleDeadlineDateManagedStock : IStock, IBatchNumberManagedStock, ISaleDeadlineDateManagedStock
{ ... }

是否必须为每种可能的配置创建另一个类?
如果我现在有这个怎么办?:

public partial class Product
{
    public bool IsSerialNumberManaged { get; set; }
}

我必须创建四个新类,只是为了实现这个非常新的属性

public interface ISerialNumberManagedStock : IStock
{
    string SerialNumber { get; set; }
}

public class SerialNumberManagedStock : IStock, ISerialNumberManagedStock { ... }
public class BatchNumberAndSerialNumberManagedStock : ... { ... }
public class SaleDeadlineDateAndSerialNumberManagedStock : ... { ... }
public class BatchNumberAndSaleDeadlineDateAndSerialNumberManagedStock : ... { ... }

是解决此问题的正确方法吗?我真的觉得我创建了一个海妖,对于Product类的每个新配置,它将涉及大量的代码编写工作……


未来一点: << em>嘿,对于这些Product我们需要将BatchNumber设置为格式为“ yyyyMMdd”的SaleDeadlineDate 。>
我应该再次 建立新类吗?


哦,我忘了告诉你! 序列号管理产品的库存数量为1!

2 个答案:

答案 0 :(得分:1)

这可能会帮助您解决当前的问题:

您是否注意到所有有关“额外”属性的示例都与管理有关?将其分解成自己的概念然后应用visitor pattern怎么样?

public interface IProductManagement
{
  void Accept(IProductManagementVisitor visitor);
}

public interface IManagedByBatchNumber
  : IProductManagement
{
  public int BatchNo { get; set; }      
}

public interface IManagedBySerialNumber
  : IProductManagement
{
  public int SerialNo { get; set; }   
}

... etc ...

public interface IProductManagementVisitor
{
  void Visit(IManagedByBatchNumber management);
  void Visit(IManagedBySerialNumber management);
  ...etc...
}

并更新库存:

public class Stock
{
  public Product Product { get; set; }
  public int Quantity { get; set; }
  public IProductManagement Management { get; set; }
}

添加访问者:

public class BatchNumberPrintingVisitor
  : IProductManagementVisitor
{
  public void Visit(IManagedByBatchNumber management)
  {
    Console.WriteLine($"Batch: {management.BatchNo}");
  }
  public void Visit(IManagedBySerialNumber management)
  { /* ignore */ }
}

添加管理员:

public class BatchNumberManagement
  : IManagedByBatchNumber
{
  public int BatchNo { get; set; }

  public void Accept(IProductManagementVisitor visitor)
  {
    visitor.Visit(this);
  }
}

public class SerialNumberManagement
  : IManagedBySerialNumber
{
  public int SerialNo { get; set; }

  public void Accept(IProductManagementVisitor visitor)
  {
    visitor.Visit(this);
  }
}

public class CompositeProductManagement
  : IProductManagement
{
  private readonly IEnumerable<IProductManagement> parts_; 

  public CompositeProductManagement(params IProductManagement[] parts)
  {
    parts_ = parts.ToArray();
  }

  public void Accept(IProductManagementVisitor visitor)
  {
    foreach (var part in parts_)
    {
      part.Accept(visitor);
    }
  }
}

并使用:

var stockManagedByBatch = new Stock
{
  Product =  "A",
  Quantity = 1,
  Management = new BatchNumberManagement
  {
    BatchNo = 123456
  }
};

var stockManagedByBatchAndSerialNo = new Stock
{
  Product = "B",
  Quantity = 1,
  Management = new CompositeProductManagement(
    new BatchNumberManagement { BatchNo = 123456 },
    new SerialNumberManagement { SerialNo = 9870 }
  }
};

var stocks = new [] { stockManagedByBatch, stockManagedByBatchAndSerialNo };

// print batch# of all stocks managed by batch to console
var printingVisitor = new BatchNumberPrintingVisitor();

foreach (var stock in stocks)
{
  stock.Management.Accept(printingVisitor);
}

请注意,IProductManagementVisitor界面也可以视为违反SOLID原则,因为添加新的管理概念需要更新所有访客。如果这真的开始令人感到不适,则可以切换到动态访客模式:

public interface IProductManagement
{
  void Accept(IProductManagementVisitor visitor);
}

public interface IManagedByBatchNumber
  : IProductManagement
{
  public int BatchNo { get; set; }      
}

public interface IProductManagementVisitor
{
  void Visit(IProductManagement management);
}

public class BatchNumberPrintingVisitor
  : IProductManagementVisitor
{
  void Visit(IProductManagement management)
  {
    var batchManagement = management as IManagedByBatchNumber;
    if (Object.ReferenceEquals(null, batchManagement))
      return;

    Console.WriteLine($"Batch number: {batchManagement.BatchNo}");
  }
}

答案 1 :(得分:0)

我认为您做得太过分了。除非有充分的理由为所有属性扩展创建单独的接口,否则为什么不

public interface IStock
{
    Product Product { get; set; }
    int Quantity { get; set; }
    bool IsBatchNumberManaged { get; }
    string BatchNumber { get; set; }
    bool IsSaleDeadlineDateManaged { get; }
    DateTime? SaleDeadlineDate { get; set; }
}

public class Stock : IStock
{
    public Product Product { get; set; }
    public int Quantity { get; set; }

    public IsBatchNumberManaged { get { return BatchNumber != null;} }
    string BatchNumber { get; set; }

    IsSaleDeadlineDateManaged { get { return SaleDeadlineDate != null;} }
    DateTime? SaleDeadlineDate { get; set; }    

}