封装前和封装后的实体类设计
如何封装
封装前的例子
假设我们有一个用户实体类 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
对象时,对象的属性已经被适当地初始化,并且遵循了业务规则。这种做法有助于提高代码的质量和可维护性。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!