Pregunta

Me presenté esta estructura a un compañero de programador y que sentía que debería ser una clase mutable. Se sentían no es conveniente no tener referencias nulas y la capacidad de alterar el objeto según sea necesario. Realmente me gustaría saber si hay alguna otra razón para hacer esto una clase mutable.

[Serializable]
public struct PhoneNumber : IEquatable<PhoneNumber>
{
    private const int AreaCodeShift = 54;
    private const int CentralOfficeCodeShift = 44;
    private const int SubscriberNumberShift = 30;
    private const int CentralOfficeCodeMask = 0x000003FF;
    private const int SubscriberNumberMask = 0x00003FFF;
    private const int ExtensionMask = 0x3FFFFFFF;


    private readonly ulong value;


    public int AreaCode
    {
        get { return UnmaskAreaCode(value); }
    }

    public int CentralOfficeCode
    {
        get { return UnmaskCentralOfficeCode(value); }
    }

    public int SubscriberNumber
    {
        get { return UnmaskSubscriberNumber(value); }
    }

    public int Extension
    {
        get { return UnmaskExtension(value); }
    }


    public PhoneNumber(ulong value)
        : this(UnmaskAreaCode(value), UnmaskCentralOfficeCode(value), UnmaskSubscriberNumber(value), UnmaskExtension(value), true)
    {

    }

    public PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber)
        : this(areaCode, centralOfficeCode, subscriberNumber, 0, true)
    {

    }

    public PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, int extension)
        : this(areaCode, centralOfficeCode, subscriberNumber, extension, true)
    {

    }

    private PhoneNumber(int areaCode, int centralOfficeCode, int subscriberNumber, int extension, bool throwException)
    {
        value = 0;

        if (areaCode < 200 || areaCode > 989)
        {
            if (!throwException) return;
            throw new ArgumentOutOfRangeException("areaCode", areaCode, @"The area code portion must fall between 200 and 989.");
        }
        else if (centralOfficeCode < 200 || centralOfficeCode > 999)
        {
            if (!throwException) return;
            throw new ArgumentOutOfRangeException("centralOfficeCode", centralOfficeCode, @"The central office code portion must fall between 200 and 999.");
        }
        else if (subscriberNumber < 0 || subscriberNumber > 9999)
        {
            if (!throwException) return;
            throw new ArgumentOutOfRangeException("subscriberNumber", subscriberNumber, @"The subscriber number portion must fall between 0 and 9999.");
        }
        else if (extension < 0 || extension > 1073741824)
        {
            if (!throwException) return;
            throw new ArgumentOutOfRangeException("extension", extension, @"The extension portion must fall between 0 and 1073741824.");
        }
        else if (areaCode.ToString()[1] == '9')
        {
            if (!throwException) return;
            throw new ArgumentOutOfRangeException("areaCode", areaCode, @"The second digit of the area code cannot be greater than 8.");
        }
        else
        {
            value |= ((ulong)(uint)areaCode << AreaCodeShift);
            value |= ((ulong)(uint)centralOfficeCode << CentralOfficeCodeShift);
            value |= ((ulong)(uint)subscriberNumber << SubscriberNumberShift);
            value |= ((ulong)(uint)extension);
        }
    }


    public override bool Equals(object obj)
    {
        return obj != null && obj.GetType() == typeof(PhoneNumber) && Equals((PhoneNumber)obj);
    }

    public bool Equals(PhoneNumber other)
    {
        return this.value == other.value;
    }

    public override int GetHashCode()
    {
        return value.GetHashCode();
    }

    public override string ToString()
    {
        return ToString(PhoneNumberFormat.Separated);
    }

    public string ToString(PhoneNumberFormat format)
    {
        switch (format)
        {
            case PhoneNumberFormat.Plain:
                return string.Format(@"{0:D3}{1:D3}{2:D4}{3:#}", AreaCode, CentralOfficeCode, SubscriberNumber, Extension).Trim();
            case PhoneNumberFormat.Separated:
                return string.Format(@"{0:D3}-{1:D3}-{2:D4} {3:#}", AreaCode, CentralOfficeCode, SubscriberNumber, Extension).Trim();
            default:
                throw new ArgumentOutOfRangeException("format");
        }
    }

    public ulong ToUInt64()
    {
        return value;
    }


    public static PhoneNumber Parse(string value)
    {
        var result = default(PhoneNumber);
        if (!TryParse(value, out result))
        {
            throw new FormatException(string.Format(@"The string ""{0}"" could not be parsed as a phone number.", value));
        }
        return result;
    }

    public static bool TryParse(string value, out PhoneNumber result)
    {
        result = default(PhoneNumber);

        if (string.IsNullOrEmpty(value))
        {
            return false;
        }

        var index = 0;
        var numericPieces = new char[value.Length];

        foreach (var c in value)
        {
            if (char.IsNumber(c))
            {
                numericPieces[index++] = c;
            }
        }

        if (index < 9)
        {
            return false;
        }

        var numericString = new string(numericPieces);
        var areaCode = int.Parse(numericString.Substring(0, 3));
        var centralOfficeCode = int.Parse(numericString.Substring(3, 3));
        var subscriberNumber = int.Parse(numericString.Substring(6, 4));
        var extension = 0;

        if (numericString.Length > 10)
        {
            extension = int.Parse(numericString.Substring(10));
        }

        result = new PhoneNumber(
            areaCode,
            centralOfficeCode,
            subscriberNumber,
            extension,
            false
        );

        return result.value != 0;
    }

    public static bool operator ==(PhoneNumber left, PhoneNumber right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(PhoneNumber left, PhoneNumber right)
    {
        return !left.Equals(right);
    }

    private static int UnmaskAreaCode(ulong value)
    {
        return (int)(value >> AreaCodeShift);
    }

    private static int UnmaskCentralOfficeCode(ulong value)
    {
        return (int)((value >> CentralOfficeCodeShift) & CentralOfficeCodeMask);
    }

    private static int UnmaskSubscriberNumber(ulong value)
    {
        return (int)((value >> SubscriberNumberShift) & SubscriberNumberMask);
    }

    private static int UnmaskExtension(ulong value)
    {
        return (int)(value & ExtensionMask);
    }
}

public enum PhoneNumberFormat
{
    Plain,
    Separated
}
¿Fue útil?

Solución

Un programa que manipula un número de teléfono es un modelo de un proceso.

Por lo tanto, hacer las cosas que son inmutables en el proceso inmutable en el código. Hacer las cosas que son mutables en el proceso mutable en el código.

Por ejemplo, un proceso probablemente incluye a una persona. Una persona tiene un nombre. Una persona puede cambiar su nombre al tiempo que conserva su identidad. Por lo tanto, el nombre de un objeto persona debe ser mutable.

Una persona tiene un número de teléfono. Una persona puede cambiar su número de teléfono sin perder su identidad. Por lo tanto, el número de teléfono de una persona debe ser mutable.

Un número de teléfono tiene un código de área. Un número de teléfono no puede cambiar su código de área y conservar su identidad; que cambiar el código de área, que ahora tiene un número de teléfono diferente. Por lo tanto, el código de área de un número de teléfono debe ser inmutable.

Otros consejos

Creo que es bien para mantenerlo como una estructura inmutable - pero yo personalmente sólo tiene que utilizar las variables independientes para cada uno de los campos lógicos a menos que vas a tener enorme número de éstos en la memoria en un momento. Si usted se pega con el tipo más adecuado (por ejemplo, para ushort 3-4 dígitos), entonces no debería ser que caros -. Y el código será un diablos de mucho más clara

Estoy de acuerdo que este debe ser un tipo inmutable. Pero por qué esta estructura debe implementar una interfaz ICloneable y IEquatable? Es un tipo de valor.

En lo personal, siento que dejar esto como una estructura inmutable es una cosa muy buena. Yo no recomiendo cambiar a una clase mutable.

La mayoría de las veces, en mi experiencia, las personas que desean evitar estructuras inmutables están haciendo esto desde la pereza. estructuras inmutables le obligan a volver a crear los parámetros completos estructura voluntad, sino buenos sobrecargas constructor puede ayudar enormemente aquí. (Por ejemplo, mira esto Fuente constructor - aunque es una clase, que implementa un "clon de todo, pero esta variable" patrón que se puede duplicar para sus campos comunes que necesitan cambiar.)

Creación de clases mutables introduce otros problemas y gastos generales que iba a evitar a menos que sea necesario.

Tal vez su compañero de trabajo podría ser satisfecha por un conjunto de métodos para permitir que los campos individuales para ser fácilmente "transformados" (que resulta en una nueva instancia con los los mismos valores que el primer ejemplo, excepto para el nuevo campo).

public PhoneNumber ApplyAreaCode(int areaCode)
{
  return new PhoneNumber(
    areaCode, 
    centralOfficeCode, 
    subscriberNumber, 
    extension);
}

Además, usted podría tener un caso especial de un número de teléfono "indefinido":

public static PhoneNumber Empty
{ get {return default(PhoneNumber); } }

public bool IsEmpty
{ get { return this.Equals(Empty); } }

La propiedad "vacío" da una sintaxis más natural que sea "default (Fax) o una nueva Fax ()" y permite por el equivalente de un cheque nulo, ya sea con "foo == PhoneNumber.Empty" o foo.IsEmpty.

Además ... En su TryParse no quiere usted decir a

return result.value != 0;

Anulabilidad se puede manejar fácilmente a través de Fax?

Los titulares de los datos que van a ser trozos alterables deben ser estructuras, en lugar de clases. Si bien se puede debatir si las estructuras deben ser trozos alterable, clases mutables hacer titulares de los datos pésimos .

El problema es que cada clase de objeto contiene efectivamente dos tipos de información:

  1. El contenido de la totalidad de sus campos
  2. El paradero de todas las referencias que existen para que

Si un objeto de clase es inmutable, por lo general, no importará lo que existe referencias a ella. Cuando un objeto de clase de retención de datos es mutable, sin embargo, todas las referencias a ella son efectivamente "unidos" entre sí; cualquier mutación realizada en uno de ellos será efectivamente llevarse a cabo en todos.

Si PhoneNumber eran una estructura mutable, se podría cambiar los campos de un lugar de almacenamiento del tipo PhoneNumber sin afectar a los campos en cualquier otro lugar de almacenamiento de ese tipo. Si uno fuera a decir var temp = Customers("Fred").MainPhoneNumber; temp.Extension = "x431"; Customers("Fred").MainPhoneNumber = temp; que cambiaría la extensión de Fred sin afectar de cualquier otra persona. Por el contrario, si PhoneNumber eran una clase mutable, el código anterior establece la extensión para todos cuya MainPhoneNumber mantiene una referencia al mismo objeto, pero no afecta a la extensión de cualquier persona cuya MainPhoneNumber celebrada datos idénticos, pero no era el mismo objeto. Repulsivo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top