Перегрузка конструкторов и повторное использование кода

Допустим, у меня есть объект Customer с парой свойств (ID, FirstName, LastName). У меня есть конструктор по умолчанию Customer(), но у меня также есть Customer(DataRow dr), поскольку я загружаю этот объект из базы данных, и это простой способ сделать это.

Я часто дохожу до того, что хочу настроить другой конструктор, Customer(int ID), на время, когда я хочу загрузить Customer, но я еще не совершил поездку в базу данных. Самый простой способ мне кажется таким:

Customer(int ID)
{
    DataTable dt = DataAccess.GetCustomer(ID);
    if (dt.Rows.Count > 0)
    {
        // pass control to the DataRow constructor at this point?
    }
    else
    {
        // pass control to the default constructor at this point?
    }   
}

Имеет смысл повторно использовать код, который уже находится в конструкторе DataRow, но я не могу придумать способ вызвать его и вернуть то, что он мне дает. Через Google я нашел информацию о перегрузке конструктора с синтаксисом : this(), но все эти примеры кажутся обратными или несовместимыми с тем, что я пытаюсь сделать.

Итак, в моем понимании конструкторов есть пробел, но я не могу с этим разобраться. Что мне не хватает?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
2 881
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Кажется, что самое простое решение: создать другую функцию, которая выполняет ту работу, которую вы хотите выполнить, и пусть оба конструктора вызывают эту функцию.

это решение, к которому я приходил пару раз, но я хотел убедиться, что нет синтаксиса, который я не понимал. Благодарю.

dnord 20.12.2008 18:10

Просто в сторону: если вы хотите установить поле readonly, вам нужно использовать обычный конструктор `: this (...)`, но в остальном обычный метод вполне подойдет.

Marc Gravell 20.12.2008 18:33

Если вы хотите, чтобы этот новый метод мог выбирать, создавать ли Customer из полученной строки данных или создавать неинициализированного клиента, а затем начинать устанавливать его данные (например, ID) из имеющихся данных, я бы рекомендовал вместо этого использовать factory. другого конструктора. Быстрый скетч, похожий на псевдокод, будет выглядеть так:

Customer ProvideCustomer(int ID)
{
    Customer result; // or initialize to null to signal more work to come
    DataTable dt = DataAccess.GetCustomer(ID);
    if (dt.Rows.Count > 0)
    {
        result = new Customer( dt.getappropriaterow ) // however you choose one
    }
    else
    {
        result = new Customer();
        result.ID = ID;          // whatever other initialization you need
    }
    return result;
}

Этот метод должен быть статическим.

Timothy Carter 20.12.2008 17:43

Если вы немного измените свои конструкторы, вы можете заставить это работать так, как вы этого хотите ... но нет, нет способа вызвать другой конструктор из тела конструктора. Но вот что можно сделать:

Измените конструктор, который принимает DataRows, чтобы взять DataTable и сначала вызвать конструктор по умолчанию:

Customer( DataTable dt ) : Customer()
{
    if ( dt != null && dt.Rows.Count > 0 )
    {
        // handle the row that was selected
    }
    else
    {
        throw Exception( "customer not in database" ); // or leave this line out to allow a default customer when they arent in the DB
    }
}

Затем измените конструктор идентификатора следующим образом:

Customer(int ID) : Customer(DataAccess.GetCustomer(ID))
{
    // no code
}

Итак, теперь ваш конструктор по умолчанию будет вызываться всегда, а затем, если клиент был найден в БД, вы можете перезаписать значения по умолчанию значениями из базы данных. Если клиента не было в базе данных, вы можете создать исключение или просто разрешить создание клиента только со значениями по умолчанию.

Меня беспокоит то, что вы не получаете не о конструкторах, а о принципе единой ответственности и свободном связывании.

Например, код, который вы показываете выше, означает:

  • ваша модель домена содержит код доступа к данным
  • вы не используете повторно какой-либо код из базового класса, который может быть введен с логикой доступа к данным в первую очередь
  • Ваш объект домена знает о структурах данных, отличных от себя или своих членов, таких как DataTable или DataRow, что связывает его с этими структурами данных и затрудняет использование других структур данных.

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

Я предпочитаю, чтобы объект домена содержал только логику, связанную с хранением подлинных данных клиента и манипулированием ими, и ничего больше. Таким образом, мой конструктор для него будет:

class Customer
{
    public Customer(int id, string firstName, string LastName)
    {
        Id = id;
        FirstName = firstName;
        LastName = lastName;
    }

    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

Обновлено: Тем не менее, это основная причина, по которой некоторые люди предпочитают ORM, которые позволяют использовать POCO, например NHibernate: нет необходимости помещать туда логику загрузки данных.

Если бы это было сделано, например, в NHibernate, вам понадобился бы базовый класс DomainObject:

public class Customer : DomainObject

Что, в свою очередь, может быть использовано реализацией IRepository NHibernate:

public class Repository<T> : IRepository where T : DomainObject

Этот объект Repository будет содержать весь код, необходимый для операций CRUD.

Если вы хотите придерживаться ADO.NET, одним из возможных решений является создание объектов диспетчера DAL для всей загрузки:

public class CustomerManager
{
    public IList<Customer> LoadCustomers()
    {
        //load all customers here
        foreach (DataRow dr in dt.Table[0])
        {
             yield return new Customer((int) dr["Id"], dr["FirstName"].ToString(), dr["LastName"].ToString());
        }
    }

    public Customer LoadCustomerByID(int id)
    {
        //load one customer here
        return new Customer((int) dr["Id"], dr["FirstName"].ToString(), dr["LastName"].ToString());
    }
}

Конечно, здесь есть гораздо больше возможностей для дальнейшего продвижения повторного использования кода.

Вы, конечно, правы - класс должен быть просто структурой данных. но я задаю более важный вопрос о заполнении этих данных.

dnord 20.12.2008 18:15

Я отвечу на это в теле ответа, днорд, мне здесь не хватает места :)

Jon Limjap 20.12.2008 18:27

Наличие ряда различных конструкторов, которые делают совершенно разные вещи на основе аргументов, часто затрудняет чтение кода. Лучший способ сделать это - создать несколько статических методов создания с намерение раскрыть имена в ваших классах. Тогда у вас есть только один конструктор. Вы даже можете сделать все конструкторы закрытыми, если хотите. Клиенты используют статические методы для создания экземпляров вашего класса.

Так что вместо:

Customer c = new Customer(13, "George", "Bush");
Customer c2 = new Customer(12);
Customer c3 = new Customer(GetDataRow(11));

Ты получаешь:

Customer c = new Customer(13, "George", "Bush");
Customer c2 = Customer.LoadFromDatabaseId(12);
Customer c3 = Customer.MapFromDataRow(GetDataRow(11));

Тогда ваш класс клиента будет выглядеть так:

class Customer
{
    public Customer(int id, string firstName, string lastName)
    {
        //...
    }

    static public Customer MapFromDataRow(DataRow dr)
    {
        return new Customer(
            dr["ID"],
            dr["FirstName"],
            dr["LastName"]);
    }

    static public Customer LoadFromDatabaseId(int id)
    {
        DataTable dt = DataAccess.GetCustomer(ID);
        if (dt.Rows.Count > 0)    
        {
            return MapFromDataRow(dt.Rows[0]);
        }
        else    
        {        
            throw new CustomerNotFoundException(id);                
        } 
    }
}

Просто используйте синтаксис конструктора this

 public Customer(int ID): this(DataAccess.GetCustomer(ID).Rows[0]) {}

Но эта конструкция вызовет исключение, если вы передадите ей недопустимый идентификатор (его нет в базе данных).

Другие вопросы по теме