5

假设我有一个Customer具有几个属性 ( ID, FirstName, LastName) 的对象。我有默认构造函数Customer(),但我也有一个Customer(DataRow dr),因为我从数据库加载这个对象,这是一种简单的方法。

我经常遇到想要设置另一个构造函数的情况,Customer(int ID)有时我想加载 aCustomer但我还没有访问数据库。对我来说最简单的方法似乎是这样的:

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 构造函数中的代码是有意义的,但是我想不出一种方法来调用它并返回它给我的东西。通过谷歌搜索,我找到了有关使用: this()语法重载构造函数的信息,但所有这些示例似乎都与我正在尝试做的事情背道而驰或不兼容。

所以我对构造函数的理解存在差距,但我似乎无法理清。我错过了什么?

4

6 回答 6

11

最简单的解决方案似乎是:构造另一个函数来完成您想做的工作,并让两个构造函数都调用该函数。

于 2008-12-20T14:06:13.700 回答
6

我担心你没有得到的不是构造函数,而是单一责任原则和松散耦合。

例如,您在上面显示的代码意味着:

  • 您的域模型包含数据访问代码
  • 您没有重用基类中的任何代码,这些代码可能首先注入了数据访问逻辑
  • 您的域对象知道除自身或其成员之外的数据结构,如 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; }
}

更新:话虽这么说,这是一些人更喜欢允许 POCO 的 ORM 的主要原因,例如 NHibernate:不需要将数据加载逻辑放在那里。

例如,如果这是在 NHibernate 中完成的,您将需要一个 DomainObject 基类:

public class Customer : DomainObject

NHibernate 的 IRepository 的实现又可以使用它:

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());
    }
}

当然,这里还有更多的机会可以进一步促进代码重用。

于 2008-12-20T14:43:52.347 回答
3

拥有许多不同的构造函数,它们都根据参数做完全不同的事情,这通常会使代码难以阅读。一个更好的方法是创建一些静态创建方法,目的是在你的类上显示名称。那么你只有一个构造函数。如果需要,您甚至可以将所有构造函数设为私有。客户端使用静态方法来创建您的类的实例。

所以而不是:

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);                
        } 
    }
}
于 2008-12-20T15:24:47.147 回答
1

如果你稍微修改你的构造函数,你可以让它按照你想要的方式工作......但是不,没有办法从构造函数的主体内部调用另一个构造函数。但这是你可以做的:

更改采用 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
}

因此,现在将始终调用您的默认构造函数,然后如果在数据库中找到客户,您可以使用数据库中的值覆盖默认值。如果客户不在数据库中,您可以抛出异常,或者只允许使用默认值构造客户。

于 2008-12-20T14:30:04.090 回答
1

只需使用 this 构造函数语法

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

但是如果你传递一个无效的 id(一个不在数据库中),这个构造会抛出一个异常。

于 2008-12-20T16:53:54.563 回答
0

如果您希望这种新方法能够选择是从检索到的数据行构造客户还是构造未初始化的客户,然后从手头的数据开始设置其数据(例如 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;
}
于 2008-12-20T14:24:58.267 回答