2010-02-02 12 views
5

Sobrecarga constructores y métodos parece desordenado, es decir, simplemente diferenciándolos por el orden y el número de parámetros. ¿No hay una forma, tal vez con genéricos, de hacer esto limpiamente para que, incluso si solo tiene un parámetro (por ejemplo, código de cadena/estado de cadena), todavía podría diferenciarlos?¿Cuál es la forma más elegante de sobrecargar un constructor/método?

using System; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TheForm tf1 = new TheForm("online", DateTime.Now); 
      TheForm tf2 = new TheForm(DateTime.Now, "form1"); 
     } 
    } 

    public class TheForm 
    { 
     public TheForm(string status, DateTime startTime) 
     { 
      //... 
     } 

     public TheForm(DateTime startTime, string idCode) 
     { 
      //... 
     } 
    } 
} 

Respuesta

13

Si necesita que muchas sobrecargas, tal vez sus tipos están manejando demasiado (ver Single Responsibility Principle). Personalmente, rara vez necesito más de uno o algunos constructores.

+2

+1 Buen punto. Definitivamente debería ser su primera prioridad hacer que la API sea menos ambigua. –

+3

-1 Ni siquiera trataste de responder la pregunta. – Trap

+1

@Trap: lamento que no hayas gustado mi respuesta, pero no veo cómo esto no intenta responder a la pregunta. Si sigues el SRP, generalmente no necesitas muchas sobrecargas. Además, no quería cubrir las mismas sugerencias que otros pósters. –

5

La única forma en que se me ocurre diferenciar la construcción con un solo parámetro de un tipo dado es usar un método de fábrica que no sea instancia, ya sea en el tipo mismo o en una clase de fábrica.

p. Ej. (Del tipo en sí)

(no probado)

public class TheForm 
{ 
    public static TheForm CreateWithId(string idCode) 
    { 
    } 

    public static TheForm CreateWithStatus(string status) 
    { 
    } 
} 
+0

bien, parece que habría algo en el lenguaje para manejar esto en caso de que realmente desee (o necesite) que el constructor se llame "Crear" –

+0

no estoy seguro de lo que quiere decir ... los métodos anteriores no son constructores, son métodos estáticos que resultan devolver una nueva instancia del tipo –

+1

. Quise decir que parece que debería haber algo en el lenguaje que le permita tener constructores, por ejemplo de esta manera: Create (string idCode) y Create (string status) para que NO TENGA que hacer métodos estáticos, tal vez genéricos o anotaciones o atributos o metadatos de algún tipo. –

8

Se podría considerar la posibilidad de un constructor Fluido para la clase de lugar, aunque es más trabajo. Esto le permitiría escribir algo como esto:

var form = new TheFormBuilder().WithStatus("foo").WithStartTime(dt).Build(); 

Es más explícito, pero no necesariamente mejor. Definitivamente es más trabajo.

En C# 4, se puede escribir opcionalmente los nombres de los parámetros cuando se invoca el constructor:

var form = new TheForm(status: "Foo", startTime: dt); 
1

Constructor Forwarding!

1

Utilice las clases de inicialización de ayuda para comunicar la semántica de sus sobrecargas.

Así, por ejemplo, definir

public class TheForm 
{ 
    public class TheForm(ById initializer) 
    { 
     //... 
    } 

    public class TheForm(ByStatus initializer) 
    { 
     //... 
    } 

    // ... 

    public class ById 
    { 
     public ById(DateTime startTime, string idCode) 
     // ... 
    } 

    public class ByStatus 
    { 
     public ByStatus(string status, DateTime startTime) 
     // ... 
    } 
} 

sin embargo, prefieren el uso de clases que son generalmente más útil si se puede, no sólo para initalialization. Es posible que desee factorizar sus clases de una manera diferente en su lugar. Siento la posibilidad de un olor a código: ¿su clase TheForm contiene demasiada lógica comercial? ¿Podría dividir un controlador MVC, por ejemplo?

0

¿No es aquí donde entra la herencia? Simplemente tenga TheForm como clase base y luego clases secundarias TheFormWithID y TheFormWithStatus. Haga que sus constructores tomen el Id. De cadena y el Estado de cadena, respectivamente, devolviendo el valor de Fecha y hora a la clase base.

No tengo ninguna herramienta de codificación delante de mí, así que disculpe la sintaxis. Estoy seguro de que lo resolverás.

using System; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TheForm tf1 = new TheFormWithStatus(DateTime.Now, "online"); 
      TheForm tf2 = new TheFormWithID(DateTime.Now, "form1"); 
     } 
    } 

    public class TheForm 
    { 
     public TheForm(DateTime startTime) 
     { 
      //... 
     } 

    } 

    public class TheFormWithID : TheForm 
    { 
     public TheFormWithID (DateTime startTime, string idCode) : TheForm (startTime) 
     { 
      //... 
     } 
    } 

    public class TheFormWithStatus : TheForm 
    { 
     public TheFormWithStatus (DateTime startTime, string status) : TheForm (startTime) 
     { 
      //... 
     } 
    } 
} 

O tenga TheForm como clase abstracta.

2

Antes de constructores Fluido a veces nos las arreglamos para conseguir un poco con objetos u objetos de parámetros de configuración:

public class FormSetup { 
    public string Status ... 
    public string Id ... 
} 


var frm = new MyForm(new FormSetup { Status = "Bla", ... }); 
1

En C# (como en muchos otros lenguajes de programación) en este caso se debe utilizar Factory Methods. Algo como esto:

class TheForm 
{ 
    public static TheForm CreateFromId(string idCode); 
    public static TheForm CreateFromStatus(string status); 
} 

o ficción parámetros:

class TheForm 
{ 
    public TheForm(string idCode, int); 
    public TheForm(string status); 
} 

o puede utilizar Eiffel;):

class THE_FORM create 
    make_from_id, make_from_status 
feature 
    ... 
end 
1

Utilizamos propiedades en lugar de constructores sobrecarga, es muy limpio y fácil para implementar:

public class x { 
    public string prop1 {get;set;} 
    public DateTime prop2 {get;set;} 
    ... 
} 

y luego llenar sólo las propiedades que necesita en el momento de instancias (y/o posterior)

var obj = new x() { 
    prop1 = "abc", 
    prop2 = 123 
}; 

La ventaja de esto es que funciona con .Net 3.5 y hace que sea muy claro lo que se está estableciendo. (a diferencia de var obj = new x("abc", 123, true, false, ... etc) donde tienes que adivinar el significado de cada valor, que puede ponerse realmente peludo cuando hay muchas sobrecargas)

0

No estoy obteniendo el "desorden" que encontraste en múltiples constructores. Sentí que los métodos estáticos para devolver una instancia del objeto también son una alternativa probable.

Pero, si alguien quiere tener la fantasía de un solo constructor y todavía tiene implementaciones diferentes, podemos pensar en pasar un objeto derivado de alguna interfaz como la entrada al constructor y puede verificar el tipo de entrada para crear una instancia. Esto es una especie de fábrica abstracta en este caso.

En un lugar en el que tienen una clase como la siguiente:

using System; 

namespace MyApplication 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      base1 t1 = new type1(); 
      class1 c1 = new class1(t1); 
      base1 t2 = new type2(); 
      class1 c2 = new class1(t2); 
      //..... 
     } 
    } 

    public class class1 
    { 
     public class1(base1 mytype) 
     { 
      switch(mytype.type) 
       case mytype.types.type1 
        return createObjectOftype1(); 
       case mytype.types.type2 
        return createObjectOftype2(); 
       case mytype.types.type3 
        return createObjectOftype3(); 
     } 

     public class1 createObjectOftype1() 
     { 
      //.... 
     } 

     public class1 createObjectOftype2() 
     { 
      //... 
     } 

     public class1 createObjectOftype2() 
     { 
      //... 
     } 

    } 


    public class base1 
    { 
     publlic Enum Types {0 "type1",.... 
    } 

    public class type1:base1 
    { 
     //..... 
    } 

    public class type2:base1 
    { 
     //..... 
    } 
} 
5

La nueva función de inicialización de objetos de .NET 3.0 es más flexible que un constructor sobrecargado. Aquí está un ejemplo sencillo:

public class Item 
{ 
    public string Name {get; set;} 
    public int Index {get; set;} 
    public string Caption {get; set;} 
} 

Como está escrito ahora, podemos hacer lo siguiente código:

var x = new item {Name=”FooBar”}; 
var x = new item {Name=”FooBar”, Index=”1”, Caption=”Foo Bar”}; 

Sólo añadiría un constructor sobrecargado con el artículo clase si quiero añadir funcionalidad durante la inicialización de la propiedad. Por ejemplo:

public class Item 
{ 
    public Item() {} 

    public Item(string name) 
    { 
     Name = name; 
     Caption = name; //defaulting name to caption 
    } 

    public Item(string name, int index) : this(name) 
    { 
     Index = index; 
    } 

    public Item(string name, int index, string caption) : this(name, int) 
    { 
     Caption = caption; 
    } 

    public string Name {get; set;} 
    public int Index {get; set;} 
    public string Caption {get; set;} 
} 

Nota: Si esto era una clase hija, podría haber encadenado a un constructor padre con la palabra clave “base”.

Si estoy escribiendo un tipo de clase de "configuración", utilizo métodos fluidos en lugar de constructores sobrecargados.

Por ejemplo, si he añadido estos métodos a la clase de artículo:

public Item WithName(string name) 
{ 
    Name = name; 
    return this; 
} 
public Item WithIndex(int index) 
{ 
    Index = index; 
    return this; 
} 
public Item WithCaption(string caption) 
{ 
    Caption = caption; 
    return this; 
} 

podría escribir código como este:

var x = new Item().WithName(“FooBar”).WithIndex(“99”).WithCaption(“Foo Bar”); 
+0

+1 Usa las funciones de lenguaje integradas (encadenamiento de constructores e inicialización de objetos) en lugar de hacerte más trabajo reinventando la rueda. Editar: Debo mencionar que la inicialización de objetos es * no * una característica .NET 3.5. Más bien, es una característica de C# 3.0 – alimbada

+0

alimbada, gracias por el encabezado de la función 3.0. – fremis

+0

No hay problema. Mucha gente no se da cuenta de que la versión .NET no está sincronizada con la versión del lenguaje C# y tampoco está sincronizada con la versión CLR. Es una receta para la confusión masiva. :-) – alimbada

0

Ya sea que esté hablando de constructores o no, de la sobrecarga bastante limitado, y cuando comienzas a correr contra sus límites, eso es una pista de que no es la herramienta adecuada para el trabajo.

Vale la pena mirar una API bien diseñada que utiliza la sobrecarga para tener una idea de para qué tipo de trabajo es buena la herramienta. XmlReader.Create es un buen ejemplo: admite doce sobrecargas diferentes. ¡Doce! Y, sin embargo, es en realidad completamente sensata: cuando se mira a todos ellos, que se reducen a lo que, en Python, ser una sola firma vocación con parámetros opcionales:

XmlReader.Create(input [, settings [, parser_context]]) 

input, con este método, puede ser una cadena que contiene una URL o nombre de archivo, TextReader o Stream. Pero independientemente de su tipo de datos, sigue siendo fundamentalmente lo mismo: la fuente de los datos que el XmlReader va a leer.

Ahora veamos su caso. Olvídate de los tipos de datos por un momento. Claramente hay alguna diferencia funcional entre un status y un idCode en su aplicación. Su formulario se comportará de una manera si se le da un status y otro si se le da un idCode. La API que está proponiendo oculta esta diferencia funcional. Debe ser que ilumina.

yo consideraría primer lugar del enfoque más simple posible, que no usa sobrecargas en absoluto:

TheForm(string idCode, string status) 

Haga su constructor una excepción si se proporcionan ambos valores (o si ambos son nulos). Tenga en cuenta que son mutuamente excluyentes en la documentación. Terminar.

Mi segunda opción sería:

enum FormType 
{ 
    IdCode, 
    Status 
}; 

TheForm(FormType type, string data) 

Esto es menos concisa, pero tiene el gran mérito de hacer que el hecho de que este método es compatible con múltiples modos mutuamente excluyentes explícita.

Llamé a esa enumeración FormType porque me pareció un nombre sensato, dado lo que sé hasta ahora, y el hecho de que este método es un constructor. Pero cada vez que usted contempla la creación de una enumeración para determinar el tipo de una instancia, al menos debe tener en cuenta la posibilidad de que realmente debería ser la creación de un tipo para determinar el tipo de una instancia:

class TheFormWhatUsesIdCode : TheForm {...} 
class TheFormWhatUsesStatus : TheForm {...} 

La diferencia funcional entre idCode y status probablemente se relaciona con una diferencia funcional entre un formulario instanciado con idCode y un formulario instanciado con status. Y eso sugiere fuertemente que deberían ser subclases.

En todo este análisis, nunca he considerado la posibilidad de hacer lo que realmente pediste, que es proporcionar múltiples sobrecargas. No creo que la sobrecarga sea la herramienta adecuada para este trabajo. Si idCode eran una int y status eran una string I todavía no pensaría que una sobrecarga eran la herramienta adecuada para este trabajo, aunque probablemente no habría terminado darse cuenta de ello hasta que tuve un montón de código que necesitaba para refactorizar .

1

He aquí un ejemplo:

Timespan.FromMilliseconds(double) 
Timespan.FromSeconds(double) 
Timespan.FromMinutes(double) 
Timespan.FromHours(double) 
Timespan.FromDays(double) 
Cuestiones relacionadas