簡體   English   中英

重載構造函數/方法的最優雅的方法是什么?

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

重載構造函數和方法似乎很麻煩,即簡單地通過參數的順序和數量來區分它們。 是不是有一種方法,也許是泛型,可以干凈利落地做到這一點,即使您只有一個參數(例如字符串 idCode / 字符串狀態),您仍然可以區分它們?

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

如果您需要那么多重載,可能您的類型處理的太多了(請參閱單一職責原則)。 我個人很少需要一個或幾個以上的構造函數。

您可以考慮為該類使用Fluent Builder ,盡管這需要更多的工作。 這將允許您編寫如下內容:

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

它更明確,但沒有必要更好。 這肯定是更多的工作。

在 C# 4 中,您可以選擇在調用構造函數時編寫參數名稱:

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

.NET 3.0 的新對象初始化功能比重載構造函數更靈活。 這是一個簡單的例子:

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

正如現在所寫,我們可以在代碼中執行以下操作:

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

如果我想在屬性初始化期間添加功能,我只會向類 Item 添加一個重載的構造函數。 例如:

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

注意:如果這是一個子類,我可以使用“base”關鍵字鏈接到父構造函數。

如果我正在編寫“配置”類型的類,我會使用 Fluent 方法代替重載構造函數。

例如,如果我將這些方法添加到 Item 類:

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

我可以寫這樣的代碼:

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

我能想到用給定類型的單個參數來區分構造的唯一方法是在類型本身或工廠類中使用非實例工廠方法。

例如(在類型本身上)

(未經測試)

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

    public static TheForm CreateWithStatus(string status)
    {
    }
} 

在 Fluent 構建器之前,我們有時會設法繞過參數對象或設置對象:

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


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

構造器轉發!

使用輔助初始化類來傳達重載的語義。

因此,例如,定義

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

但是,如果可以的話,更喜歡使用更普遍可用的類,而不僅僅是用於初始化。 您可能希望以不同的方式分解您的類。 我感覺到了代碼異味的可能性:您的 TheForm 類是否包含過多的業務邏輯? 例如,您是否想拆分一個 MVC 控制器?

在 C# 中(就像在許多其他編程語言中一樣)在這種情況下,您應該使用Factory Methods 像這樣的東西:

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

或小說參數:

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

或者你可以使用 Eiffel ;):

class THE_FORM create
   make_from_id, make_from_status
feature
  ...
end

我們使用屬性而不是重載構造函數,它非常干凈且易於實現:

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

然后在實例化時(和/或以后)僅填充您需要的屬性

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

這樣做的好處是它可以與 .Net 3.5 一起使用,並使設置的內容非常清楚。 (與var obj = new x("abc", 123, true, false, ... etc)相反,您必須猜測每個值的含義,當有很多重載時,這會變得非常麻煩)

下面是一個例子:

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

這不就是繼承的用武之地嗎? 只需將 TheForm 作為基類,然后將 TheFormWithID 和 TheFormWithStatus 作為子類。 讓它們的構造函數分別獲取字符串 ID 和字符串狀態,將 DateTime 值傳回給基類。

我面前沒有任何編碼工具,所以請原諒語法。 我相信你會弄明白的。

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

或者將 TheForm 作為抽象類。

我沒有得到您在多個構造函數中發現的“混亂”。 我覺得返回對象實例的靜態方法也是一個可能的替代方法。

但是,如果有人想要單個構造函數的幻想並且仍然有不同的實現,我們可以考慮將從某個接口派生的對象作為輸入傳遞給構造函數,並可能檢查輸入的類型以創建實例。 在這種情況下,這是一種抽象工廠。

在一個地方,我們有一個如下所示的類:

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

無論您是否在談論構造函數,重載都非常有限,當您開始遇到它的極限時,這暗示它不是適合這項工作的工具。

值得看看設計良好的 API,它使用重載來了解該工具適合什么樣的工作。 XmlReader.Create就是一個很好的例子:它支持十二種不同的重載。 十二! 然而,它實際上是完全合理的:當您查看所有這些時,它們歸結為在 Python 中將是帶有可選參數的單個調用簽名:

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

對於此方法, input可以是包含 URL 或文件名的字符串、 TextReaderStream 但是不管它的數據類型如何,它本質上仍然是一樣的: XmlReader將要讀取的數據源。

現在讓我們看看你的情況。 暫時忘記數據類型。 應用程序中的statusidCode之間顯然存在一些功能差異。 如果給定status ,您的表單將采取一種方式,如果給定idCode另一種idCode 您提議的 API隱藏了這種功能差異。 它應該照亮它。

我會首先考慮最簡單的方法,它根本不使用重載:

TheForm(string idCode, string status)

如果提供了兩個值(或者兩者都為空),則使您的構造函數拋出異常。 請注意,它們在文檔中是相互排斥的。 今天就這樣吧。

我的第二個選擇是:

enum FormType
{
   IdCode,
   Status
};

TheForm(FormType type, string data)

這不太簡潔,但它有一個非常大的優點,即該方法支持多種互斥模式的事實明確。

我稱這個 enum FormType是因為它看起來是一個合理的名字,考慮到我目前所知道的,以及這個方法是一個構造函數的事實。 但是每當您考慮創建一個枚舉來確定實例的類型時,您至少應該考慮您應該真正創建一個類型來確定實例類型的可能性:

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

idCodestatus之間的功能差異可能與使用idCode實例化的idCode和使用status實例化的表單之間的功能差異有關。 這強烈表明它們應該是子類。

在所有這些分析中,我從未考慮過做您實際要求的事情的可能性,即提供多個重載。 我不認為重載是這項工作的正確工具。 如果idCode是一個intstatus是一個string仍然不會認為重載是適合這項工作的工具,盡管在我有很多需要重構的代碼之前我可能不會注意到它。

我個人不喜歡其他類能夠設置我的屬性的想法

所以這允許我的屬性受到保護或私有,但仍然具有其他答案描述的許多功能:

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

然后可以作為:

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

Foo newFoo = new Foo(newFooSettings)

或者

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

Foo newFoo = new Foo(newFooSettings)

對於一個簡單的類來說顯然有點矯枉過正,但它可以控制可以歸結為單個屬性的數據類型,IE:TimeSpan 可以使用擴展方法從自定義枚舉類型中解析

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM