Цепочка перегруженных конструкторов

Я пытаюсь создать эффективный класс с минимальным дублированием кода.

Я определил это:

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 в качестве параметра запросит у БД имя и вызовет последний конструктор. Таким образом, все выполнение должно быть направлено на один из конструкторов, который на самом деле выполняет всю проверку и т.д. делает проверку.

Однако из-за ошибки компиляции я не могу использовать эту систему в строке Me.New(dt.Rows(0)("FirstName").ToString(),dt.Rows(0)("LastName").ToString()). Очевидно, эта строка должна быть первой строкой в ​​конструкторе. Но если у меня это первая строка, это нарушит процесс проверки, потому что проверка выдаст ошибку из-за отсутствия имени / фамилии. Мне нужно запросить БД, чтобы получить эту информацию.

Я знаю, что могу назначать здесь значения и вызывать проверку из этого конструктора, но это эффективно изолирует этот конструктор от последнего, таким образом дублируя код и немного добавляя к обслуживанию. К вашему сведению, в приведенном ниже примере у меня есть только 2 конструктора, но на самом деле у меня есть еще несколько. Если каждый будет выполнять свое задание, это просто добавит столько же к обслуживанию.

Итак, есть ли способ выполнить мою задачу, выполнив некоторый код и ЗАТЕМ вызывая перегруженный конструктор?

Спасибо за понимание

ОБНОВЛЕНИЕ 1:

Согласно комментарию the_lotus, я включаю определение dt. Есть обходной путь для этой проблемы. По сути, я бы взял проверку и присвоение из окончательного конструктора и поместил его в функцию. Все конструкторы будут вызывать эту функцию, что устраняет необходимость в цепочке конструкторов. Выглядит неплохо, но я хотел бы понять, почему для объединения конструкторов в цепочку я должен помещать вызовы конструкторов в первую строку.

Вот новый код:

Открытый класс 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

Конец класса

Об одном любопытном не упомянуть: онлайн-конвертеры кода (vb.net в C #) не имеют проблем с преобразованием вызовов связанных конструкторов НЕ в первой строке. Код C # возвращается как this.#ctor(dt.Rows(0)("FirstName").ToString(), dt.Rows(0)("LastName").ToString());. Однако, если я попытаюсь преобразовать обратно в VB.NET, это не удастся.


person George    schedule 11.05.2015    source источник
comment
Вы не показываете, откуда дт. У вас может быть частный метод инициализации или общий заводской метод.   -  person the_lotus    schedule 11.05.2015
comment
dt здесь не проблема. Вот почему я включил 'query DB to get firstName and lastName код ... Я стараюсь, чтобы код был коротким. Предположим, я определяю dt как DataTable и запрашиваю БД, чтобы получить значения. Также предположим, что я получил хотя бы одну строку данных со значениями. Проблема связана с Me.New, который компилятор хочет поместить в первую строку конструктора. Я дополню свой ответ dt Definition   -  person George    schedule 11.05.2015
comment
Да, вам нужно, чтобы вызов перегрузки конструктора следовал за вашим объявлением конструктора - это правило. А затем создайте свой объект оттуда, а не наоборот.   -  person T.S.    schedule 11.05.2015
comment
Это нарушит мою проверку, потому что в процедуре проверки у меня не может быть пустого / незаполненного значения. Я только что обновил свой ответ обходным путем, по сути, достигнув того, чего я хотел, без цепочки конструкторов. Я до сих пор не понимаю, какова цель правила «связанные вызовы конструктора должны быть в первой строке».   -  person George    schedule 11.05.2015
comment
Зачем нужна цепочка конструкторов? Поскольку ваш объект может иметь значения по умолчанию во многих свойствах, и у вас может быть много конструкторов, каждый из которых добавляет свойство. Внутри один конструктор может устанавливать 5 свойств, а другие 4 конструктора могут устанавливать только 1 свойство. например, Door() может устанавливать _material = wood, _locks=1 и _hinges=3 (по умолчанию). Затем Door(locks) вызовет Door() и затем установит _locks=locks, затем Door(locks, hinges) вызовет Door(locks) и затем установит _hinges=hinges   -  person T.S.    schedule 11.05.2015
comment
Недостаток того, что ctor обращается к базе данных для поиска начальных значений, заключается в том, что вы должны учитывать случаи, когда он не работает или запись не найдена. Поскольку нет возможности отменить создание (если вы не используете фабричный подход), теперь у вас есть недопустимый Foo. Чтобы разрешить Validate вызвать исключение, нужно закрыть дверь после того, как собака убежит. Все это можно предотвратить при создании нового Foo после защиты данных, которые ему требуются.   -  person Ňɏssa Pøngjǣrdenlarp    schedule 12.05.2015


Ответы (2)


То, что вы ищете, - это заводской метод

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.

person the_lotus    schedule 11.05.2015
comment
Personally, I wouldn't call the database inside a New. +1! Слишком занят или амбициозен - person Ňɏssa Pøngjǣrdenlarp; 11.05.2015
comment
Я пытался упростить логику ... кто-то, имеющий доступ к моему классу, просто должен был бы создать его экземпляр с необходимыми параметрами. Меньше шансов на ошибку, потому что кому-то не нужно создавать экземпляр, присваивать значения, вызывать проверку и т. Д. , всегда проверяйте, создается ли экземпляр класса с правильными значениями. Меньше шансов на ошибку времени выполнения, потому что кто-то создал экземпляр моего класса, но не присвоил правильные значения. - person George; 14.05.2015
comment
При этом меня смутил комментарий I wouldn't call the database inside a New. Это только из-за технических ограничений или плохой дизайн? Если да, то почему? По сути, решение состояло в том, чтобы переместить операции с БД из NEW в подпрограмму, которую вызывает NEW. То же самое в моей книге. Это плохой дизайн? - person George; 14.05.2015
comment
@George есть несколько хороших ответов здесь, и вы можете прочтите это - person the_lotus; 14.05.2015
comment
Спасибо the_lotus, очень интересная статья. - person George; 14.05.2015

Что мне не нравится, так это то, что вы обращаетесь к БД в конструкторе, а также проверяете в конструкторе. Я вижу в этом проблему дизайна. Ниже приведены 3 примера перегруженных конструкторов. Все трое работают. Вам может понадобиться №3. Инициируйте свой dt статическим (vb - общим) методом. Вы также можете заменить свои параметры 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;
    }
}
person T.S.    schedule 11.05.2015
comment
Этот пример не очень соответствует моему сценарию. Представьте, что у вас есть новый конструктор public Door(GUID account) в этом конструкторе, перед вызовом Door(int locks, int hinges) вам действительно нужно запросить базу данных, чтобы получить эти значения. Он не работает с VB.NET, потому что хочет видеть вызовы конструктора в первой строке кода. - person George; 11.05.2015
comment
@George, прежде всего, никогда не стоит вызывать DB в конструкторе. Это проблема вашего дизайна. Это то, что вам нужно понять. Но если вы хотите продвигать свой дизайн, это все еще возможно. Вы можете разделить логику загрузки и модель. Используйте фабричный метод предложенных the_lotus или статических объектов. Думаю, в моем посте рассказывается, что вы выбрали неправильный дизайн, и поэтому вы страдаете в простой ситуации. - person T.S.; 11.05.2015