0

I am trying to create an efficient class with minimum code-duplication.

I have this defined:

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

The Constructor with firstName and lastName parameters is the end-point constructor that does validation. A constructor with userGUID as a parameter would query DB to obtain name and call the final constructor. This way all execution should be directed towards one of the constructors that actually does all validation etc etc. The idea behind it is that if I add new contructors, I only have to pull necessary data (firstname/lastname) and call the final constructor that does validation.

However, there is a compilation error preventing me from using this system on line Me.New(dt.Rows(0)("FirstName").ToString(),dt.Rows(0)("LastName").ToString()). Apparently this line has to be the first line in the constructor. But If I have this as first line, it will break the validation process because validation will throw an error due to no firstname/lastname. I have to query the DB in order to pull that info.

I am aware that I can assign values here and call validation from this constructor too, but this will effectively isolate this constructor from the final one, thus duplicating code and adding to maintenance a bit. FYI, in the example below I only have 2 constructors, but in reality i have several more. If each will do its own assignment it just adds up to maintenance that much.

So, is there a way to achieve my task by executing some code and THEN calling an overloaded constructor?

Thank you for any insight

UPDATE 1:

Per the_lotus comment, I am including dt definition. There is a workaround for this issue. Basically I would take the validation and assignment out of the final constructor and put it into a function. All constructors would call this function, thus eliminating the need to chain constructors. It doesn't look bad, but I would like to understand why in order to chain constructors I have to put constructor calls on the first line.

Here is new code:

Public Class Foo Private _firstName As String = "" 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

End Class

One curious not to mention: online code converters (vb.net to C#) have no issues converting chained constructor calls NOT on the first line. The C# code comes back as this.#ctor(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString()); However, If I try to convert back to VB.NET, it fails.

George
  • 2,165
  • 2
  • 22
  • 34
  • You're not showing where dt is from. You could have private Initialize method or have a shared factory method. – the_lotus May 11 '15 at 15:53
  • dt is not an issue here. That is why I included `'query DB to get firstName and lastName` code... I'm trying to keep the code short. Assume I define dt as DataTable and query the DB to get the values. Also assume I get at least one row of data with values. The issue is with Me.New, which compiler wants to put on the first line of the constructor. I will update my answer with dt Definition – George May 11 '15 at 15:57
  • Yes, you need to have constructor overload call follow your constructor declaration - this is the rule. And then, design your object from there, not other way around. – T.S. May 11 '15 at 16:09
  • That would break my validation, because in the validation routine I cannot have an empty/unpopulated value. I just updated my answer with a workaround essentially achieving what I wanted without constructor chaining. I still don't understand what is the purpose of the 'chained constructor calls must be on the first line' rule is. – George May 11 '15 at 16:14
  • Why constructor chaining? Because your object can have default values in many properties and you may have many constructors, each adding a property. Internally, one constructor may set 5 properties and other 4 constructors set only 1 property. for example `Door()` may set `_material = wood`, `_locks=1` and `_hinges=3` (default). Then `Door(locks)` will call `Door()` and then set `_locks=locks`, then `Door(locks, hinges)` will call `Door(locks)` and then set `_hinges=hinges` – T.S. May 11 '15 at 16:24
  • The flaw in having the ctor hit a database to find the initial values is that you have to allow for cases when it fails or no record is not found. Since there is no way to cancel creation (unless you use a factory approach), you now have an invalid `Foo`. To let `Validate` throw an exception is closing the door after the dog has run off. All of which can be prevented creating a new Foo *after* you have secured the data it *requires*. – Ňɏssa Pøngjǣrdenlarp May 12 '15 at 01:13

2 Answers2

3

What you are looking is a factory method

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

Or an initialization function

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

Personally, I wouldn't call the database inside a New.

the_lotus
  • 12,668
  • 3
  • 36
  • 53
  • 1
    `Personally, I wouldn't call the database inside a New.` +1! Too "busy" or ambitious – Ňɏssa Pøngjǣrdenlarp May 11 '15 at 17:53
  • I was trying to make the logic simple... someone accessing my class would simply have to instantiate it with necessary parameters. Less chance of mistake, because that someone doesnt have to instantiate, assign values, call validate, etc etc. From OOP perspective, it makes sense to force the user of your class to do exactly what you want - instantiate with proper parameters, always validate, always make sure the class is instantiated with proper values. Less chance of a runtime error because someone instantiated my class but didnt assign proper values. – George May 14 '15 at 14:11
  • That being said, i got confused by the `I wouldn't call the database inside a New` comment. Is it only because of technical limitations or it a bad design? If it is, why? Essentially the solution was to move DB operations from NEW to a sub that NEW calls. Same thing in my book. Is it a bad design? – George May 14 '15 at 14:13
  • 1
    @George there's some good answers [here](http://stackoverflow.com/questions/531282/how-much-code-should-one-put-in-a-constructor) and you can read [this](http://en.wikipedia.org/wiki/Factory_method_pattern) – the_lotus May 14 '15 at 14:30
  • Thank you the_lotus, very interesting article. – George May 14 '15 at 15:35
0

What I don't like is the fact that you accessing DB on constructor, and also that you validate in constructor. I see this as design issue. Below there are 3 examples of overloaded constructors. All three work. You may need #3. Init your dt in a static (vb - shared) method. You can also substitute your fname/lname parameters with one parameter that contains both. And that will work with #3 for you

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

Why constructor chaining? Because your object can have default values in many properties and you may have many constructors, each adding a property. Internally, one constructor may set 5 properties and other 4 constructors set only 1 property.

For example:

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;
    }
}
T.S.
  • 18,195
  • 11
  • 58
  • 78
  • This example is not very accurate to the scenario I was having. Pretend you have a new constructor `public Door(GUID account)` , in this constructor, before calling `Door(int locks, int hinges)` you actually need to query the database to get those values. It does not work with VB.NET because it wants to see constructor calls on the first line of code. – George May 11 '15 at 16:45
  • @George first of all, it is never a good idea to call DB on constructor. This is your design problem. This is what you need to understand. But if you want to push your design through, it is still possible. You can separate load logic and model. Use factory method "the_lotus" offered or static objects. I guess, my post is telling you that you picked the wrong design and this is why you suffering in simple situation – T.S. May 11 '15 at 18:12