假设我有一个带有几个属性的对象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构造函数中的代码是有意义的,但我无法找到一种方法来调用它并返回它给我的内容。通过谷歌搜索,我发现了有关使用: this()
语法的构造函数重载的信息,但所有这些示例似乎都是向后或与我正在尝试的内容不兼容。
所以我对构造函数的理解存在差距,但我似乎无法解决这个问题。我错过了什么?
答案 0 :(得分:10)
最简单的解决方案似乎是:构造另一个函数来完成你想要做的工作并让两个构造函数都调用该函数。
答案 1 :(得分:6)
我担心你没有得到的不是建设者,而是关于单一责任原则和松散耦合。
例如,您在上面显示的代码表示:
当然,这假设你没有使用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());
}
}
当然,还有很多机会在这里进一步推广代码重用。
答案 2 :(得分:3)
拥有许多不同的构造函数,这些构造函数都基于参数完成不同的操作,这通常会导致难以阅读的代码。更好的方法是在类上使用intent revealing names创建许多静态创建方法。那你只有一个构造函数。如果需要,您甚至可以将所有构造函数设为私有。客户端使用静态方法创建类的实例。
所以而不是:
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);
}
}
}
答案 3 :(得分: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
}
现在,您的默认构造函数将始终被调用,然后如果在DB中找到客户,则可以使用数据库中的值覆盖默认值。如果客户不在DB中,您可以抛出异常,或者只允许使用默认值构建客户。
答案 4 :(得分:1)
只需使用此构造函数语法
public Customer(int ID): this(DataAccess.GetCustomer(ID).Rows[0]) {}
但是如果你传递一个无效的id(一个不在数据库中),这个结构将抛出异常。
答案 5 :(得分:0)
如果您希望这种新方法能够选择是从检索到的数据行构建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;
}