перегрузка конструкторов и повторное использование кода - PullRequest
4 голосов
/ 20 декабря 2008

Допустим, у меня есть объект 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, но я не могу найти способ вызвать это и вернуть то, что он мне дает. Благодаря Googling я нашел информацию о перегрузке конструктора с помощью синтаксиса : this(), но все эти примеры кажутся отсталыми или несовместимыми с тем, что я пытаюсь сделать.

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

Ответы [ 6 ]

10 голосов
/ 20 декабря 2008

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

6 голосов
/ 20 декабря 2008

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

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

  • модель вашего домена содержит код доступа к данным
  • вы не используете повторно какой-либо код из базового класса, который может быть в первую очередь введен логикой доступа к данным
  • объект вашего домена знает о структурах данных, отличных от себя или его членов, таких как 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());
    }
}

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

3 голосов
/ 20 декабря 2008

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

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

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);                
        } 
    }
}
1 голос
/ 20 декабря 2008

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

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

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

1 голос
/ 20 декабря 2008

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

Измените ваш конструктор, который принимает 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
    }
}

Затем измените ваш конструктор ID таким образом:

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

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

0 голосов
/ 20 декабря 2008

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

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...