封装前和封装后的实体类设计
如何封装
封装前的例子
假设我们有一个用户实体类 User
,在封装前,可能看起来像这样:
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public List<Role> Roles { get; set; }
public User()
{
Roles = new List<Role>();
}
}
在这个例子中,所有的属性都可以被外部代码直接修改,没有任何限制或验证。
封装后的例子
在进行了适当的封装后,User
类可能会变成如下所示:
public class User : Entity<int>
{
public string UserName { get; protected set; }
public string Email { get; protected set; }
public List<Role> Roles { get; protected set; }
protected User()
{
// 无参数构造函数,一般不对外可见
Roles = new List<Role>();
}
public User(int id, string userName, string email)
: this(id)
{
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
Email = email ?? throw new ArgumentNullException(nameof(email));
}
public User(int id)
{
Id = id;
Roles = new List<Role>();
}
public void SetUserName(string userName)
{
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
}
public void SetEmail(string email)
{
Email = email ?? throw new ArgumentNullException(nameof(email));
}
public void AddRole(Role role)
{
if (role == null)
throw new ArgumentNullException(nameof(role));
if (!Roles.Any(r => r.Id == role.Id))
Roles.Add(role);
}
public void RemoveRole(Role role)
{
if (role == null)
throw new ArgumentNullException(nameof(role));
Roles.RemoveAll(r => r.Id == role.Id);
}
}
在这个封装后的例子中,我们做了以下改动:
-
构造函数:
- 无参数构造函数被设为
protected
,不允许外部直接创建实例。 - 有参数构造函数检查参数有效性,并初始化属性。
- 初始化了
Roles
列表。
- 无参数构造函数被设为
-
属性访问器:
UserName
和Email
的 setter 被设为protected
,不允许外部直接修改。- 提供了
SetUserName
和SetEmail
方法,用来安全地设置这些属性,并进行有效性检查。
-
集合操作:
Roles
的 setter 被设为protected
,不允许外部直接修改。- 提供了
AddRole
和RemoveRole
方法来安全地添加和删除角色。
通过这种方式,我们可以更好地控制实体的状态,并确保实体的一致性和有效性。
构造函数
protected User()
{
// 无参数构造函数,一般不对外可见
Roles = new List<Role>();
}
- 这是一个无参数的构造函数,它被声明为
protected
,意味着只有该类本身或其派生类可以调用它。 - 在构造函数体内,初始化了一个
Roles
列表。这是一个包含Role
类型对象的列表,用于存储用户的各个角色。 - 通常情况下,无参数构造函数并不推荐对外开放,因为这可能导致实体对象在未完全初始化的情况下就被创建。这里的目的是为了让派生类可以使用它,或者在某些框架中需要无参数构造函数的情况。
public User(int id, string userName, string email)
: this(id)
{
UserName = userName ?? throw new ArgumentNullException(nameof(userName));
Email = email ?? throw new ArgumentNullException(nameof(email));
}
- 这是一个带有三个参数的构造函数:
int id
、string userName
和string email
。 - 使用
: this(id)
表示这个构造函数会先调用另一个带有int id
参数的构造函数来初始化部分成员变量。 - 在构造函数体内,通过
??
运算符检查userName
和email
是否为null
。如果是null
,则抛出ArgumentNullException
,并指明哪个参数未被赋值。 - 如果参数不是
null
,则设置UserName
和Email
属性。
public User(int id)
{
Id = id;
Roles = new List<Role>();
}
- 这是一个带有一个参数
int id
的构造函数。 - 在构造函数体内,初始化了
Id
属性,并创建一个新的Roles
列表。
总结
这些构造函数的设计原则如下:
- 无参数构造函数 (
protected User()
): 一般用于派生类或某些框架的需求,在这里主要是为了派生类能够继承此类时调用。 - 带有必要参数的构造函数 (
public User(int id, string userName, string email)
): 这个构造函数用于创建一个完整的User
对象,其中包含了必需的参数。它还检查了参数的有效性,确保不会创建无效的用户对象。 - 带有部分必要参数的构造函数 (
public User(int id)
): 这个构造函数用于创建一个具有唯一标识符id
的User
对象,但是不包含所有必需的信息。通常用于某些特定场景下的初始化。
通过这样的构造函数设计,我们可以确保在创建 User
对象时,对象的属性已经被适当地初始化,并且遵循了业务规则。这种做法有助于提高代码的质量和可维护性。