谁应该创建业务对象?
Who should create business objects?
由于 business/domain 对象应该不知道它们的持久性,因此它们显然不能包含从数据库加载数据以初始化自身的代码。
另一方面,并非业务对象的所有属性都有 public setter 来帮助封装并避免将它们设置为无效值。
这意味着当从数据库中获取数据时,没有其他 class 可以完全初始化业务对象。
由于以上所有都是常见的最佳做法,因此必须有一些解决该问题的方法。 那么谁应该负责构建业务对象并用数据库中的数据填充它们?
注意:我想到的唯一选择是将 所有 属性添加到构造函数,我认为这非常不切实际。
由于术语的原因,这可能会变得复杂 - 同一事物通常可以有多个名称,或者一个模式可以是其他离散模式的合并。希望这个(非常简短的)答案对您有意义。
您通常会有一个数据层 (DAL) 或 "data service",其工作是与数据存储对话(或者它可能与存储库层或 ORM 对话)。
负责创建数据实体的层 ("business objects") 可能因您的设置而异。 ORM(如 Entity Framework)可以 return 你的数据实体,但你通常希望在它们进入你的应用程序的其余部分之前将它们更改为更不可知的东西,所以这将是DAL(或特定数据 "service")将它们从 EF 对象映射到常规数据实体。
或者,存储库层可以创建实体,因为它用于查询底层数据存储。
存储库应该能够根据数据层提供的信息创建业务对象。逻辑是,由于存储库应该以一种对用户隐藏实现的方式持久化您传入的对象,因此它必须能够序列化和反序列化该对象而无需任何额外信息。
如果您的对象具有私有数据,一个常见的模式是通过构造函数传入私有信息。
无需调用存储库即可创建新对象。通常通过 public 构造函数
例如:
public class Model {
public String Id {get;set;}
public String Colour { get; set; }
public Model(String id, String colour)
{
this.Id = id;
this.Colour = colour;
}
}
public interface IRepository
{
void Add(Model item);
Model Get(string id);
}
public class RepositorySql : IRepository
{
private string constr;
public RepositorySql(string connstr)
{
this.constr = connstr;
}
public void Add(Model item)
{
using (SqlConnection conn = new SqlConnection(constr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("insert into table (userid,colour) values (@userid,@colour)", conn))
{
cmd.Parameters.AddWithValue("@id", item.Id);
cmd.Parameters.AddWithValue("@colour", item.Colour);
cmd.ExecuteNonQuery();
}
}
}
public Model Get(string id)
{
using (SqlConnection conn = new SqlConnection(constr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("select * from table where id=@id)", conn))
{
cmd.Parameters.AddWithValue("@id", item.UserId);
SqlDataReader dr =cmd.ExecuteReader();
if (dr.Read())
{
Model i = new Model(dr["id"].ToString(), dr["colour"].ToString());
return i;
}
else
{
return null;
}
}
}
}
}
这个问题的解决方案很少。但两者都不是 "perfect".
- 使用反射来设置私有字段或属性。大多数 ORM 使用此解决方案。问题可能是当 ORM 设置 属性 时,它有一些逻辑,导致实体的不合逻辑状态(我被这个烧坏了很多次)。
- 抽象掉 "data persistence" 为 key/value 图。这样,实体可以持久化自身并且仍然独立于特定的持久化技术。但是您仍然需要明确地对此进行编码。
- 建造者模式。具有相同属性的单独 class,可以访问实体的私有字段,但仅用于构造。主要缺点是每个实体都需要有构建器。
- 创建一个 DTO,它反映实体的状态,而不是它的行为。然后实体可以 read/create 这个 DTO 来加载或持久化自己。缺点同上。
我个人认为您的问题没有任何好的答案,除非您可以设法自动执行上述解决方案之一。通过代码生成或通过语言支持。例如,"builder pattern" 的语言支持会有很大帮助。
由于 business/domain 对象应该不知道它们的持久性,因此它们显然不能包含从数据库加载数据以初始化自身的代码。
另一方面,并非业务对象的所有属性都有 public setter 来帮助封装并避免将它们设置为无效值。
这意味着当从数据库中获取数据时,没有其他 class 可以完全初始化业务对象。
由于以上所有都是常见的最佳做法,因此必须有一些解决该问题的方法。 那么谁应该负责构建业务对象并用数据库中的数据填充它们?
注意:我想到的唯一选择是将 所有 属性添加到构造函数,我认为这非常不切实际。
由于术语的原因,这可能会变得复杂 - 同一事物通常可以有多个名称,或者一个模式可以是其他离散模式的合并。希望这个(非常简短的)答案对您有意义。
您通常会有一个数据层 (DAL) 或 "data service",其工作是与数据存储对话(或者它可能与存储库层或 ORM 对话)。
负责创建数据实体的层 ("business objects") 可能因您的设置而异。 ORM(如 Entity Framework)可以 return 你的数据实体,但你通常希望在它们进入你的应用程序的其余部分之前将它们更改为更不可知的东西,所以这将是DAL(或特定数据 "service")将它们从 EF 对象映射到常规数据实体。
或者,存储库层可以创建实体,因为它用于查询底层数据存储。
存储库应该能够根据数据层提供的信息创建业务对象。逻辑是,由于存储库应该以一种对用户隐藏实现的方式持久化您传入的对象,因此它必须能够序列化和反序列化该对象而无需任何额外信息。
如果您的对象具有私有数据,一个常见的模式是通过构造函数传入私有信息。
无需调用存储库即可创建新对象。通常通过 public 构造函数
例如:
public class Model {
public String Id {get;set;}
public String Colour { get; set; }
public Model(String id, String colour)
{
this.Id = id;
this.Colour = colour;
}
}
public interface IRepository
{
void Add(Model item);
Model Get(string id);
}
public class RepositorySql : IRepository
{
private string constr;
public RepositorySql(string connstr)
{
this.constr = connstr;
}
public void Add(Model item)
{
using (SqlConnection conn = new SqlConnection(constr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("insert into table (userid,colour) values (@userid,@colour)", conn))
{
cmd.Parameters.AddWithValue("@id", item.Id);
cmd.Parameters.AddWithValue("@colour", item.Colour);
cmd.ExecuteNonQuery();
}
}
}
public Model Get(string id)
{
using (SqlConnection conn = new SqlConnection(constr))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand("select * from table where id=@id)", conn))
{
cmd.Parameters.AddWithValue("@id", item.UserId);
SqlDataReader dr =cmd.ExecuteReader();
if (dr.Read())
{
Model i = new Model(dr["id"].ToString(), dr["colour"].ToString());
return i;
}
else
{
return null;
}
}
}
}
}
这个问题的解决方案很少。但两者都不是 "perfect".
- 使用反射来设置私有字段或属性。大多数 ORM 使用此解决方案。问题可能是当 ORM 设置 属性 时,它有一些逻辑,导致实体的不合逻辑状态(我被这个烧坏了很多次)。
- 抽象掉 "data persistence" 为 key/value 图。这样,实体可以持久化自身并且仍然独立于特定的持久化技术。但是您仍然需要明确地对此进行编码。
- 建造者模式。具有相同属性的单独 class,可以访问实体的私有字段,但仅用于构造。主要缺点是每个实体都需要有构建器。
- 创建一个 DTO,它反映实体的状态,而不是它的行为。然后实体可以 read/create 这个 DTO 来加载或持久化自己。缺点同上。
我个人认为您的问题没有任何好的答案,除非您可以设法自动执行上述解决方案之一。通过代码生成或通过语言支持。例如,"builder pattern" 的语言支持会有很大帮助。