选择正确的构造函数

本文关键字:构造函数 选择 | 更新日期: 2023-09-27 18:32:02

我有两个类:Purchase(父级)和DiscountPurchase(子级,还有一个附加字段"折扣")。每个都有 2 个构造函数(带参数和不带参数)。我需要解析字符串并创建一个实例。我知道,我可以这样做:

string[] parameters = csvString.Split(';');
string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);
if (parameters.Length < 4)
{                   
    newPurchase = new Purchase(productName, cost, productCount);
}
else
{
    decimal discount = decimal.Parse(parameters[3]);
    newPurchase = new FixedDiscountPurchase(productName, cost, productCount, discount);
}

但也许有一些更优雅的方式:反思还是其他什么?

选择正确的构造函数

这是工厂模式的典型用例。你知道,你需要一个Purchase的实例 - 但你不知道确切的子类型。(请参阅 http://www.dotnetperls.com/factory 和此处以获取稍微复杂和有用的示例:http://msdn.microsoft.com/en-us/library/orm-9780596527730-01-05.aspx)

这并不能避免你已经实现的逻辑 - 它只是帮助你,你不必一遍又一遍地重复自己,并将逻辑封装在一个工厂中,你可以随时随地使用。

从您的示例中,一个简单的工厂可能如下所示:

static class PurchaseFactory
{
    public Static Purchase BuildPurchase(String[] parameters){
       string productName = parameters[0];
       decimal cost = decimal.Parse(parameters[1]);
       int productCount = int.Parse(parameters[2]);
       if (parameters.Length < 4)
       {                   
           return new Purchase(productName, cost, productCount);
       }
       else
       {
           decimal discount = decimal.Parse(parameters[3]);
           return new FixedDiscountPurchase(productName, cost, productCount, discount);
       }
    }
}

因此,从代码中的任何位置,您只需要:

string[] parameters = csvString.Split(';');
Purchase p = PurchaseFactory.BuildPurchase(parameters);
//p is now  either "Purchase" or "FixedDiscountPurchase"

好吧,如果只是关于这两个类,并且想知道价格是否打折(并计算最终价格) - 你可以逃脱一个包含折扣标志和在任何情况下获得 FinalPrice 的方法的Purchase类:

public class Purchase
    {
        public Decimal Discount { get; set; }
        public Boolean Discounted { get; set; }
        public String Name { get; set; }
        public Decimal Price { get; set; }
        public Int32 Count { get; set; }
        public Decimal FinalPrice
        {
            get
            {
                if (!Discounted)
                    return Price;
                else
                    return Price - Discount;
            }
        }
        public Purchase (String csvString){
            string[] parameters = csvString.Split(';');
            Name = parameters[0];
            Price = decimal.Parse(parameters[1]);
            Count = int.Parse(parameters[2]);
            if (parameters.Length == 4)
            {
                Discount = decimal.Parse(parameters[3]);
                Discounted = true;
            }
        }
    }

用法:

Purchase p = new Purchase(stringInput);
MessageBox.Show(p.FinalPrice.ToString());

只要确保参考purchase.FinalPrice,那么你就不必关心价格是否真的打折了。

最好重新设计您的Purchase类以具有discount字段,然后执行以下操作:

string[] parameters = csvString.Split(';');
string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);
decimal discount = parameters.Length < 4 ? 0 : decimal.Parse(parameters[3]);
newPurchase = new Purchase(productName, cost, productCount, discount);

或者保留单独的类并对其进行更改,以便if处理是否存在discount,而不是是否存在第四个参数。这在"这些数据从何而来"和"一旦我有了它怎么办"的逻辑之间增加了一点解耦,这是一件好事。

string[] parameters = csvString.Split(';');
string productName = parameters[0];
decimal cost = decimal.Parse(parameters[1]);
int productCount = int.Parse(parameters[2]);
decimal discount = parameters.Length < 4 ? 0 : decimal.Parse(parameters[3]);
if (discount > 0)
{
    newPurchase = new FixedDiscountPurchase(productName, cost,
                                             productCount, discount);       
}
else
{
    newPurchase = new Purchase(productName, cost, productCount);
}
如果需要在

逻辑上区分未指定折扣和指定折扣 0 之间的差异,则可以对discount使用 decimal?null(而不是 0)。

我想每个人都想太多了。如果这是一个你正在创建的全新项目,并且在未来,除了PurchaseDiscountPurchase之外,还会有其他类型,那么狗鼻关于创建工厂的答案可能是要走的路。

无需撕毁代码并做大量工作,扩展方法可能是要走的路:

public static Purchase GetPurchaseObject(this string csvString)
{
    string[] parameters = csvString.Split(';');
    string productName = parameters[0];
    decimal cost = decimal.Parse(parameters[1]);
    int productCount = int.Parse(parameters[2]);
    if (parameters.Length < 4)
    {
        return new Purchase(productName, cost, productCount);
    }
    else
    {
        decimal discount = decimal.Parse(parameters[3]);
        return new DiscountPurchase(productName, cost, productCount, discount);
    }
}

然后要使用它,您需要做的就是从 CSV 字符串调用扩展方法:

string csvString1 = "TestProduct;15.50;5";
string csvString2 = "TestProduct;15.50;5;0.25";
Purchase p1 = csvString1.GetPurchaseObject();
Purchase p2 = csvString2.GetPurchaseObject();
if (p1 is DiscountPurchase)
{
    Console.WriteLine("p1 is a DiscountPurchase item");
}
else
{
    Console.WriteLine("p1 a Purchase item");
}
if (p2 is DiscountPurchase)
{
    Console.WriteLine("p2 is a DiscountPurchase item");
}
else
{
    Console.WriteLine("p2 a Purchase item");
}

从输出中可以看出,p1p2是不同的对象!根据 CSV 字符串的内容,它将知道您需要哪种对象。此外,如果您不喜欢扩展方法,则可以将逻辑移动到静态方法中,并将 csv 字符串作为参数传递给该方法。

以下是使用反射执行此操作的方法。它的工作原理是尝试使用它看到的第一个构造函数和正确数量的参数。它搜索的类型列表是您为其提供的类型,以及直接从该类型继承自该类型与该类型相同的程序集中的任何类型。

非常脆弱,难以阅读且不灵活(例如,如果您在其构造函数上添加具有三个参数的另一种购买类型,它将无法正常工作),这可以让您了解使用反射的感觉。我不建议你这样做。

static object CreateInstance(Type rootType, object[] args)
{
    var types = rootType.Assembly.GetTypes().Where(t =>
        t == rootType || t.BaseType == rootType).ToArray();
    return CreateInstance(types, args);
}
static object CreateInstance(Type[] types, object[] args)
{
    foreach (var type in types)
    {
        foreach (var ctor in type.GetConstructors())
        {
            var parameters = ctor.GetParameters();
            if (args.Length == parameters.Length)
            {
                var newArgs = args.Select((x, i) =>
                   Convert.ChangeType(x, parameters[i].ParameterType)).ToArray();
                return ctor.Invoke(newArgs);
            }
        }
    }
    return null;
}
// use like
var newPurchase = CreateInstance(typeof(Purchase), parameters); // or
var newPurchase = CreateInstance(
         new[] { typeof(Purchase), typeof(FixedDiscountPurchase) }, parameters);