开发者

What is the most elegant way to overload a constructor/method?

Overloading constructors and methods seems messy, i.e. simply differentiating them by the order and number of parameters. Isn't there a way, perhaps with generics, to do this cleanly so that, even if you just have one parameter (e.g. string idCode / string status) you could still differentiate them?

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            TheForm tf1 = new TheForm("online", Date开发者_运维问答Time.Now);
            TheForm tf2 = new TheForm(DateTime.Now, "form1");
        }
    }

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

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


If you need that many overloads, perhaps your types are handling too much (see Single Responsibility Principle). Personally I rarely need more than one or a few constructors.


You could consider having a Fluent Builder for the class instead, although it's more work. This would allow you to write something like this:

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

It's more explicit, but not necessary better. It's definitely more work.

In C# 4, you can optionally write the parameter names when you invoke the constructor:

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


The new object initialization feature of .NET 3.0 is more flexible than an overloaded constructor. Here is a simple example:

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

As it is written now, we can do the following in code:

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

I would only add an overloaded constructor to the class Item if I want to add functionality during property initialization. For example:

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

Note: If this was a child class, I could have chained to a parent constructor with the “base” keyword.

If I am writing a “configuration” type of class, I use Fluent Methods in place of Overloaded constructors.

For example, if I added these methods to the Item class:

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

I could write code like this:

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


The only way I can think of to differentiate the construction with a single parameter of a given type is to use a non-instance factory method, either on the type itself or in a factory class.

e.g. (on the type itself)

(untested)

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

    public static TheForm CreateWithStatus(string status)
    {
    }
} 


Before Fluent builders we sometimes managed to get around with parameter objects or setup objects:

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


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


Constructor Forwarding!


Use helper initialization classes to communicate the semantics of your overloads.

So, for instance, define

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)
        // ...
    }
}

However, prefer using classes which are more generally usable if you can, not just for initalialization. You may want to factor your classes in a different way instead. I sense the possibility of a code smell: does your TheForm class contain too much business logic? Might you want to split out an MVC Controller, for instance?


In C# (like in many other programming languages) in this case you should use Factory Methods. Something like this:

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

or fiction parameters:

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

Or you can use Eiffel ;):

class THE_FORM create
   make_from_id, make_from_status
feature
  ...
end


We use properties instead of overloading constructors, it's quite clean and easy to implement:

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

and then fill just the properties you need at instantiation time (and/or later)

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

The benefit with this is it works with .Net 3.5 and makes it really clear what is being set. (as opposed to var obj = new x("abc", 123, true, false, ... etc) where you have to guess the meaning of each value, which can get really hairy when there are many overloads)


Here's an example:

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


Isn't this where inheritence comes in? Just have TheForm as a base class and then TheFormWithID and TheFormWithStatus child classes. Have their constructors take string ID and string Status respectively passing back the DateTime value to the base class.

I haven't got any coding tools infront of me so please excuse the syntax. I'm sure that you'll figure it out.

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)
        {
           //...
        }
    }
}

Or have TheForm as an abstract class.


Whether you're talking about constructors or not, overloading's pretty limited, and when you start to run up against its limits, that's a hint that it's not the right tool for the job.

It's worth looking at a well-designed API that uses overloading to get a sense of what kind of job the tool is good for. XmlReader.Create is a good example: It supports twelve different overloads. Twelve! And yet, it's actually completely sensible: when you look at them all, they boil down to what would, in Python, be a single calling signature with optional parameters:

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

input, to this method, can be a string containing a URL or filename, a TextReader, or a Stream. But irrespective of its data type, it's still fundamentally the same thing: the source of the data that the XmlReader is going to read.

Now let's look at your case. Forget about data types for a moment. There's clearly some functional difference between a status and an idCode in your application. Your form is going to behave one way if it's given a status and another if it's given an idCode. The API that you're proposing conceals this functional difference. It should be illuminating it.

I would first consider the simplest possible approach, which uses no overloads at all:

TheForm(string idCode, string status)

Make your constructor throw an exception if both values are provided (or if both are null). Note that they're mutually exclusive in the documentation. Call it a day.

My second choice would be:

enum FormType
{
   IdCode,
   Status
};

TheForm(FormType type, string data)

This is less concise, but it has the very great merit of making the fact that this method supports multiple mutually-exclusive modes explicit.

I called that enum FormType because it seemed like a sensible name, given what I know so far, and the fact that this method's a constructor. But whenever you contemplate creating an enum to determine the type of an instance, you should at least consider the possibility that you should really be creating a type to determine the type of an instance:

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

The functional difference between idCode and status probably relates to a functional difference between a form instantiated with idCode and a form instantiated with status. And that strongly suggests that they should be subclasses.

In all of this analysis, I've never once considered the possibility of doing what you actually asked for, which is to provide multiple overloads. I don't think overloading is the right tool for this job. If idCode were an int and status were a string I still wouldn't think that overloading were the right tool for this job, though I probably wouldn't have ended up noticing it until I had a lot of code I needed to refactor.


I am not getting what "messy" you found in multiple constructors. I felt the static methods for returning an instance of the object also a probable alternate.

But, if somebody want to have the fancy of a single constructor and still have different implementations, we can think of passing an object derived from some interface as the input to the constructor and might check the type of the input to create an instance. This is kind of an abstract factory in this case.

In one place we have a class like the following:

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
    {
        //.....
    }
}


I personally dont like the idea of other classes being able to set my properties

so this allows my properties to be protected or private, but still have alot of the functionality described by other answers:

public class FooSettings
{
    public bool Prop1 { get; set; }
    public bool Prop2 { get; set; }

    public TimeSpan Prop3 { get; set; }

    public FooSettings()
    {
        this.Prop1 = false;
        this.Prop2 = false;

        this.Prop3 = new TimeSpan().ExtensionMethod(CustomEnum.Never);
    }

    public FooSettings BoolSettings
    (bool incomingFileCacheSetting, bool incomingRuntimeCacheSetting)
    {
        this.Prop1 = incomingFileCacheSetting;
        this.Prop2 = incomingRuntimeCacheSetting;
        return this;
    }

    public FooSettings Prop3Setting
    (TimeSpan incomingCustomInterval)
    {
        this.Prop3 = incomingCustomInterval;
        return this;
    }

    public FooSettings Prop3Setting
    (CustomEnum incomingPresetInterval)
    {
        return this.Prop3Setting(new TimeSpan().ExtensionMethod(CustomEnum.incomingPresetInterval));
    }
}


public class Foo
{
    public bool Prop1 { get; private set; }
    public bool Prop2 { get; private set; }

    public TimeSpan Prop3 { get; private set; }

    public CallTracker
    (
        FooSettings incomingSettings
    )
    {
        // implement conditional logic that handles incomingSettings
    }
}

could then be consumed as:

FooSettings newFooSettings = new FooSettings {Prop1 = false, Prop2 = true}
newFooSettings.Prop3Setting(new TimeSpan(3,0,0));

Foo newFoo = new Foo(newFooSettings)

or

FooSettings newFooSettings = new FooSettings()
    .BoolSettings(false, true)
    .Prop3Setting(CustomEnum.Never)

Foo newFoo = new Foo(newFooSettings)

obviously a bit overkill for a simple class, but it gives alot of control over the types of data that can be funneled down to a single property, IE: TimeSpan can be parsed from a custom enum type using an extension method

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜