2014-09-25 5 views
3

Представьте себе класс, который можно, в принципе, создать, указав значение одного из двух свойств, оба из которых имеют один и тот же тип. Следующий код выполняет это, используя комбинацию named and optional parameters различать между двумя конструкторами:Использование Именованных и необязательных аргументов для различения конструкторов

class Program 
{ 
    static void Main() 
    {   
     //Note: these 2 ctors are distinguished only by the argument naming convention: 
     thing thingWithMass = new thing(mass: 10); 
     thing thingWithVolume = new thing(vol: 25); 
    } 

    class thing 
    { 
     int Density = 3; 
     int Mass; 
     int Vol; 
     public thing(int mass) 
     { 
      Mass = mass; 
      Vol = Mass/Density; 
     } 

     // Note the use of the optional variable to distinguish this ctor: 
     public thing(int vol, bool isVol=true) 
     { 
      Vol = vol; 
      Mass = Vol * Density; 
     } 
    } 
} 

Так (несколько неожиданно) этот код компилируется и работает отлично, но это плохой форме? Кажется, это немного похоже на обман, и мне интересно, существует ли скрытая опасность, которая мне не кажется очевидной? Он пахнет?

ПРИМЕЧАНИЕ: В данном конкретном случае, я понимаю, что я мог бы достичь по существу то же самое с одним конструктором, который выглядит следующим образом:

public thing(int mass=0, int vol=0) //plus a couple of if() statements in the body 

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

+1

Я бы сказал, что это немного пахнет. Любой, кто использует этот класс, должен был бы знать, как работать с этим обманом, или они могут закончиться неожиданными результатами. – JLRishe

+0

Я думаю, было бы лучше показать ваш реальный код, потому что прямо сейчас я чувствую, что спрашиваю, почему вы даже делаете класс (ссылочный тип btw), чтобы обернуть целочисленное значение. – Crono

+0

@Crono: мой класс намного сложнее этого; для этого примера я просто удалил все, что не было необходимо для демонстрации логики конструктора. – kmote

ответ

3

Если ваш класс имеет много конструкторов с очень разной логикой и конфликтующими типами аргументов, следует использовать статические фабричные методы:

public static Thing CreateFromMass(int mass) 
{ 
    return new Thing(mass, 0); 
} 

public static Thing CreateFromVol(int vol) 
{ 
    return new Thing(0, vol); 
} 

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

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

+0

Это хорошее предложение. Благодаря! – kmote

1

IMO это немного запах. Что делать, если потребитель звонит thing(10, false). Это имеет непредвиденные последствия для создания thing с неправильным значением.

я могу думать о двух возможных решений

1) Используйте завод, как описано Асария.

2) Создайте типы для массы и объема. Например,

class Mass 
{ 
    private readonly int _mass; 
    public Mass(int mass) { _mass = mass; } 
    public int Value { get { return _mass; } } 
} 

class Volume 
{ 
    private readonly int _volume; 
    public Mass(int volume) { _volume = volume; } 
    public int Value { get { return _volume; } } 
} 

Вы можете изменить свои подписи в

thing(Volume volume) 
thing(Mass mass) 

В ответ на ваш комментарий о простых арифметических операциях не работает со вторым подходом, вы можете определить неявные преобразования и из int для Mass и Volume.

abstract class Number 
{ 
    public static implicit operator int(Number number) 
    { 
     return number.Value; 
    } 

    public abstract int Value { get; set; } 
} 

internal class Mass : Number 
{ 
    public override int Value { get; set; } 
    public static implicit operator Mass(int val) { return new Mass(){ Value = val }; } 
} 

internal class Volume : Number 
{ 
    public static implicit operator Volume(int val) { return new Volume(){ Value = val }; } 
    public override int Value { get; set; } 
} 

var mass = new Mass { Value = 10 }; 
var volume = new Volume { Value = 20 }; 
int product = mass * volume; // should work 
mass = 10 * 20; // should also work 
+0

np, рад, что я мог бы помочь! –

+0

Еще одно большое предложение (и замечательный момент о непредвиденных последствиях). Спасибо! – kmote

+0

Хотя я все еще думаю, что ваше предложение полезно, у него есть некоторые последствия, с которыми мне неудобно. Например, простые уравнения, такие как 'mass = объем * плотность', должны быть преобразованы в' mass = new Mass (volume.value * density.value), что сделает более сложные уравнения гораздо труднее читать. Но спасибо, что помогли мне задуматься! – kmote