【问题标题】:Chaining overloaded constructors链接重载的构造函数
【发布时间】:2015-05-11 15:24:11
【问题描述】:

我正在尝试创建一个代码重复最少的高效类。

我已经定义了:

Public Class Foo
    Private _firstName as string = ""
    Private _lastName as string = ""

    Public Sub New(ByVal userGUID As Guid)
        'query DB to get firstName and lastName
        Me.New(dt.Rows(0)("FirstName").ToString(),dt.Rows(0)("LastName").ToString())
    End Sub

    Public Sub New(ByVal firstName As String, ByVal lastName As String)
        _firstName = firstName.toUpper()
        _lastName = lastName.toUpper()
        Validate()
    End Sub

    Private Sub Validate()
        ' Throw error if something is wrong
    End Sub
End Class

具有 firstName 和 lastName 参数的构造函数是进行验证的端点构造函数。以 userGUID 作为参数的构造函数将查询 DB 以获取名称并调用最终构造函数。这样,所有执行都应该针对实际执行所有验证等的构造函数之一。其背后的想法是,如果我添加新的构造函数,我只需要提取必要的数据(名字/姓氏)并调用最终的构造函数进行验证。

但是,有一个编译错误阻止我在Me.New(dt.Rows(0)("FirstName").ToString(),dt.Rows(0)("LastName").ToString()) 线上使用这个系统。显然,这一行必须是构造函数中的第一行。但是如果我将其作为第一行,它将破坏验证过程,因为验证会由于没有名字/姓氏而引发错误。我必须查询数据库才能提取该信息。

我知道我可以在这里分配值并从这个构造函数调用验证,但这会有效地将这个构造函数与最后一个构造函数隔离,从而复制代码并增加一些维护。仅供参考,在下面的示例中,我只有 2 个构造函数,但实际上我还有更多。如果每个人都做自己的任务,那只会增加维护费用。

那么,有没有办法通过执行一些代码然后调用重载的构造函数来完成我的任务?

感谢您的任何见解

更新 1:

根据 the_lotus 评论,我包括 dt 定义。这个问题有一个解决方法。基本上我会从最终的构造函数中取出验证和赋值,并将其放入一个函数中。所有的构造函数都会调用这个函数,从而消除了链接构造函数的需要。它看起来不错,但我想了解为什么为了链接构造函数,我必须将构造函数调用放在第一行。

这是新代码:

公开课 Foo 私有 _firstName 作为字符串 = "" Private _lastName As String = ""

Public Sub New(ByVal userGUID As Guid)
    Dim dt As New DataTable
    ' query DB to get firstName and lastName
    ' Assume I populate dt with at least one DataRow
    AssignAndValidate(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString())
    'Me.New(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString())
End Sub

Public Sub New(ByVal firstName As String, ByVal lastName As String)
    AssignAndValidate(firstName, lastName)
End Sub

Private Sub Validate()
    ' Throw error if something is wrong
End Sub

Private Sub AssignAndValidate(ByVal firstName As String, ByVal lastName As String)
    _firstName = firstName.ToUpper()
    _lastName = lastName.ToUpper()
    Validate()
End Sub

结束类

更奇怪的是:在线代码转换器(vb.net 到 C#)在转换链式构造函数调用时没有问题,不在第一行。 C# 代码返回为this.#ctor(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString()); 但是,如果我尝试转换回 VB.NET,它会失败。

【问题讨论】:

  • 您没有显示 dt 的来源。您可以拥有私有 Initialize 方法或共享工厂方法。
  • dt 在这里不是问题。这就是为什么我包含'query DB to get firstName and lastName 代码...我试图保持代码简短。假设我将 dt 定义为 DataTable 并查询数据库以获取值。还假设我得到至少一行带有值的数据。问题在于 Me.New,编译器希望将它放在构造函数的第一行。我将用 dt 定义更新我的答案
  • 是的,你需要在你的构造函数声明之后有构造函数重载调用——这是规则。然后,从那里设计您的对象,而不是其他方式。
  • 这会破坏我的验证,因为在验证例程中我不能有一个空/未填充的值。我刚刚用一种解决方法更新了我的答案,基本上在没有构造函数链接的情况下实现了我想要的。我仍然不明白'链式构造函数调用必须在第一行'规则的目的是什么。
  • 为什么要使用构造函数链接?因为您的对象可以在许多属性中具有默认值,并且您可能有许多构造函数,每个构造函数都添加一个属性。在内部,一个构造函数可以设置 5 个属性,而其他 4 个构造函数只能设置 1 个属性。例如Door() 可以设置_material = wood_locks=1_hinges=3(默认)。然后Door(locks)会调用Door()然后设置_locks=locks,然后Door(locks, hinges)会调用Door(locks)然后设置_hinges=hinges

标签: vb.net constructor-overloading constructor-chaining


【解决方案1】:

你正在寻找的是一个工厂方法

Public Class Foo 

    Public Shared Function GetFooFromGuid(ByVal userGUID As Guid) As Foo

        ' Query db

        return New Foo(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString())
    End Function

End Class

或者一个初始化函数

Public Class Foo 

    Public Sub New(ByVal userGUID As Guid)
        ' query DB to get firstName and lastName
        Initialize(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString())
    End Sub

    Public Sub New(ByVal firstName As String, ByVal lastName As String)
        Initialize(firstName, lastName)
    End Sub

    Private Sub Initialize(ByVal firstName As String, ByVal lastName As String)
    End Sub

End Class

就个人而言,我不会在 New 中调用数据库。

【讨论】:

  • Personally, I wouldn't call the database inside a New. +1!太“忙”或雄心勃勃
  • 我试图让逻辑变得简单......访问我的类的人只需使用必要的参数对其进行实例化。出错的机会更少,因为某人不必实例化、赋值、调用验证等。从 OOP 的角度来看,强制类的用户完全按照您的意愿进行操作是有意义的 - 使用适当的参数进行实例化,始终验证,请始终确保使用正确的值实例化该类。运行时错误的可能性较小,因为有人实例化了我的类但没有分配正确的值。
  • 话虽如此,我对I wouldn't call the database inside a New 的评论感到困惑。仅仅是因为技术限制还是设计不好?如果是,为什么?本质上,解决方案是将数据库操作从 NEW 移动到 NEW 调用的 sub。我的书中也有同样的事情。这是一个糟糕的设计吗?
  • @George 有一些很好的答案here 你可以阅读this
  • 谢谢the_lotus,非常有趣的文章。
【解决方案2】:

我不喜欢的是您在构造函数中访问数据库,以及在构造函数中进行验证。我认为这是设计问题。下面是 3 个重载构造函数的示例。所有三个工作。你可能需要#3。以静态(vb - 共享)方法初始化您的 dt。您还可以将 fname/lname 参数替换为包含两者的参数。这将适用于您的#3

public class A
{
    public A() : this ("xxx")
    {

    }
    public A(string x)
    {

    }
}

public class A
{
    public A() 
    {

    }
    public A(string x): this ()
    {

    }
}

public class A
{
    public A() : this(GetXxx())
    {

    }
    public A(string x)
    {

    }

    private static string GetXxx()
    {
        return "xxx";
    }
}

为什么是构造函数链?因为您的对象可以在许多属性中具有默认值,并且您可能有许多构造函数,每个构造函数都添加一个属性。在内部,一个构造函数可以设置 5 个属性,而其他 4 个构造函数只设置 1 个属性。

例如:

public class Door
{
    private string _material = "wood";
    private int _locks = 1;
    private int _hinges = 3;

    public Door()
    {

    }
    public Door(int locks) : this()
    {
        _locks = locks;
    }
    public Door(int locks, int hinges) : this(locks)
    {
        _hinges = hinges;
    }
}

【讨论】:

  • 这个例子对我的场景不是很准确。假设您有一个新的构造函数 public Door(GUID account) ,在此构造函数中,在调用 Door(int locks, int hinges) 之前,您实际上需要查询数据库以获取这些值。它不适用于 VB.NET,因为它希望在第一行代码中看到构造函数调用。
  • @George 首先,在构造函数上调用 DB 绝不是一个好主意。这是你的设计问题。这是你需要了解的。但是,如果您想推动您的设计通过,它仍然是可能的。您可以分离加载逻辑和模型。使用提供的工厂方法“the_lotus”或静态对象。我想,我的帖子是在告诉您,您选择了错误的设计,这就是您在简单情况下受苦的原因
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-26
  • 1970-01-01
  • 2015-07-24
  • 1970-01-01
相关资源
最近更新 更多