2014-11-26 3 views
2

У меня есть два класса: 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); 
} 

Но, может быть, есть еще более элегантный способ: отражение или что-то еще?

+1

Вы пробовали идею отражения? –

+0

Я не могу получить конструктор класса, тип которого я не знаю. Проблема заключается в том, как определить, какой из конструкторов классов вызывать. – Konstantin

+0

Спасибо всем за ваши ответы. Это было очень информативно для меня. Теперь, я думаю, самый разумный способ - изготовление фабрики. Но идея о методе расширения тоже хороша. – Konstantin

ответ

1

Это типичный укус для заводской шаблон. Вы знаете, что вам нужен экземпляр Purchase, но вы не знаете точного подтипа. (См. http://www.dotnetperls.com/factory и здесь для немного более сложного и полезного примера: http://msdn.microsoft.com/en-us/library/orm-9780596527730-01-05.aspx)

Ofc. это не исключает логику, которую вы уже реализовали, она просто помогает вам не повторять себя снова и снова, и логика завершается на одной фабрике, которую вы можете использовать в любом месте в любое время.

Взятые из вашего примера, простой завод может выглядеть следующим образом:

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" 

Ну, если речь идет только об этих двух классах, а желает узнать, была ли уценена цена или нет (и вычислить окончательную цену) - вы могли бы уйти с одного Purchase класса, который содержит дисконтированный флаг и метод, чтобы получить FinalPrice в любом случае:

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, то вы не должны принимать забота о том, действительно ли цена дисконтирована или нет.

+0

Но все, что вы сделали, занимает 90% его логики и переводит его на другой метод. Это не улучшает способ написания кода, а просто рефакторинг его в другое место (что, по общему признанию, может сделать его немного чище). –

+0

@TimS. Я согласен с вами (и уже писал, что это не исключает реализации), но только потому, что пример довольно прост. Чем больше возможностей * Class-Types * у вас есть, тем больше возможностей делает завод. Отражение может не работать, потому что он на самом деле не знает, какой КЛАСС использовать в первую очередь. – dognose

+0

@dognose. Точно, я не знаю, что именно я должен использовать конструктор класса. Это основная проблема. – Konstantin

1

Это может быть лучше, чтобы перепроектировать 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); 
} 

Вы можете использовать decimal? и null (вместо 0) для discount, если вам нужно логически разделить разницу между без скидок уточняются и со скидкой 0 уточняются.

0

Я думаю, что все закончили думать об этом. Если это совершенно новый проект, который вы создаете и в будущем, будут другие типы, отличные от Purchase и DiscountPurchase, тогда ответ dognose о создании фабрики может быть путем.

Без того, чтобы разорвать код и сделать много работы, хотя, метод расширения может быть путь:

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"); 
} 

Как видно из вывода, p1 и p2 разные предметы! В зависимости от содержимого строки CSV, он будет знать, какой тип объекта вам нужен. Кроме того, если вам не нравятся методы расширения, вы можете переместить логику в статический метод и передать в строку csv в качестве параметра для метода.

0

Вот как вы можете это сделать, используя отражение. Он работает, пытаясь использовать первый конструктор, который он видит с правильным количеством аргументов. Список типов, которые он ищет, - это тип, который вы ему даете, и любые типы, которые наследуют непосредственно от этого типа в той же сборке, что и этот тип.

Это довольно хрупкий, трудно читаемый и негибкий (например, если вы добавите еще один тип покупки с тремя параметрами на его конструкторе, это не сработает правильно), что дает вам представление о том, как это работает отражение. Я не рекомендую вам это делать.

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); 
+0

Ничего себе, это действительно выглядит жутко. Спасибо за объяснение, теперь мне кажется более понятным. :) – Konstantin