C #: объект, имеющий два конструктора: как ограничить какие свойства устанавливаются вместе? - PullRequest
4 голосов
/ 29 мая 2010

Скажем, у вас есть объект Price, который принимает либо (целое число, десятичная цена), либо строку, содержащую "4 / $ 3.99". Есть ли способ ограничить, какие свойства могут быть установлены вместе? Не стесняйтесь поправлять меня в моей логике ниже.

Тест: A и B равны друг другу, но пример C не должен быть разрешен. Таким образом, вопрос Как обеспечить, чтобы все три параметра не вызывались, как в примере C?

AdPrice A = new AdPrice { priceText = "4/$3.99"};                        // Valid
AdPrice B = new AdPrice { qty = 4, price = 3.99m};                       // Valid
AdPrice C = new AdPrice { qty = 4, priceText = "2/$1.99", price = 3.99m};// Not

Класс:

public class AdPrice {
    private int _qty;
    private decimal _price;
    private string _priceText;

Конструкторы:

    public AdPrice () : this( qty: 0, price: 0.0m) {} // Default Constructor
    public AdPrice (int qty = 0, decimal price = 0.0m) { // Numbers only
        this.qty = qty;
        this.price = price; }

    public AdPrice (string priceText = "0/$0.00") { // String only
        this.priceText = priceText; }

Методы:

    private void SetPriceValues() {
       var matches = Regex.Match(_priceText, 
           @"^\s?((?<qty>\d+)\s?/)?\s?[$]?\s?(?<price>[0-9]?\.?[0-9]?[0-9]?)");
       if( matches.Success) {
           if (!Decimal.TryParse(matches.Groups["price"].Value, 
                                 out this._price))
               this._price = 0.0m;
           if (!Int32.TryParse(matches.Groups["qty"].Value, 
                                 out this._qty))
               this._qty = (this._price > 0 ? 1 : 0);
           else
               if (this._price > 0 && this._qty == 0)
                   this._qty = 1; 
    }  }

    private void SetPriceString() {
        this._priceText = (this._qty > 1 ? 
                               this._qty.ToString() + '/' : "") +
            String.Format("{0:C}",this.price);
    }

Аксессоры:

    public int qty { 
        get { return this._qty; } 
        set { this._qty = value; this.SetPriceString(); } }
    public decimal price { 
        get { return this._price; } 
        set { this._price = value; this.SetPriceString(); } }
    public string priceText { 
        get { return this._priceText; } 
        set { this._priceText = value; this.SetPriceValues(); } }
}

Ответы [ 5 ]

11 голосов
/ 29 мая 2010

Хмммм .... вместо борьбы с компилятором, может быть, вам просто нужно переосмыслить свой API. Рассматривали ли вы следующее:

  • Нет установщиков. Ваш класс должен быть неизменным, чтобы он полностью инициализировался через конструктор и никогда не мог инициализироваться в недопустимом состоянии.

  • Если вы настаиваете на установщиках, то ваше свойство PriceText может быть доступно только для чтения, а остальные - для чтения / записи. По крайней мере, для этого вам не нужно проверять текст, переданный в это свойство.

  • Может быть, полностью удалить свойство PriceText, переопределить строковое представление вашего объекта в методе .ToString.

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

5 голосов
/ 29 мая 2010

Для строковой части было бы хорошо иметь статический метод, который анализирует строку и возвращает Price экземпляр.

, например

Price newPrice = Price.FromString("4/$3.99");
Console.WriteLine("{0} qty for {1}", newPrice.Quantity, newPrice.Price);

Здесь FromString - статический метод.
Это то же самое, что и Enum.Parse, если вы хотите увидеть пример.

5 голосов
/ 29 мая 2010

Как насчет того, чтобы сделать установщики в свойствах частными и добавить методы для изменения цен или даже без них, чтобы цены устанавливались только при создании экземпляра нового объекта.

2 голосов
/ 29 мая 2010

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

Кроме того, я считаю, что инициализаторы объектов сильно перегружены. После вызова конструктора объект должен быть полностью инициализирован и находиться в согласованном состоянии, удовлетворяющем всем инвариантам. Вы можете применить это, предоставив хорошо разработанный набор конструкторов, но обычно вы не можете применить это, предоставив конструктор по умолчанию и полагаясь на использование инициализаторов объектов. Потребитель может просто вызвать конструктор по умолчанию и больше ничего не делать.

var employee = new Employee { Id = 42, FirstName = "John", LastName = "Doe" };

На мой взгляд, это действительно плохой дизайн. Какова семантика следующего кода?

var employee = new Employee();

Это просто объект без каких-либо атрибутов. Бесполезный. Я считаю, что было бы намного лучше обеспечить, чтобы сотрудник имел хотя бы идентификатор, не предоставляя конструктор по умолчанию. Если каждый экземпляр сотрудника требует значения для других свойств, это зависит от фактического контекста, но если они имеют эти свойства, они, конечно, должны стать аргументами конструктора.

var employee = new Employee(42);
2 голосов
/ 29 мая 2010

Если вы просто хотите назначать значения непосредственно свойствам, как вы делаете, и имеют конструктор по умолчанию, зачем вообще беспокоиться о конструкторах?

Если оба параметра priceText и price / qty должны быть изменяемыми извне, то при каких условиях вы считаете инициализацию объекта завершенной:

AdPrice C = new AdPrice();
C.qty = 4;
C.priceText = "2/$1.99";
C.price = 3.99m

Выше приведен тот же код, который вы используете, только без «синтетического сахара». Чтобы предотвратить возникновение вашего примера, вы должны иметь возможность предотвратить вышесказанное.

Мое предложение было бы сделать свойство priceText частным. Для инициализации объекта с использованием строки потребуется использовать соответствующий конструктор.

...