csharp:第七章封装与访问修饰符
目录
第七章 封装与访问修饰符
7.1 封装的概念
什么是封装
封装是面向对象编程的三大特性之一,指的是将数据(字段)和操作数据的方法(属性、方法)绑定在一起,并隐藏内部实现细节,只暴露必要的接口。
封装的好处
| 好处 | 说明 |
| —— | —— |
| 数据保护 | 防止外部直接修改内部数据 |
| 简化使用 | 用户只需了解公开接口 |
| 易于维护 | 内部实现可以修改而不影响外部 |
| 提高安全性 | 控制数据的访问和修改方式 |
// 未封装的例子(不推荐) public class BankAccountBad { public decimal Balance; // 任何人都可以直接修改 } // 封装的例子(推荐) public class BankAccountGood { private decimal balance; // 私有字段 public decimal Balance // 公开属性 { get { return balance; } private set { balance = value; } } public void Deposit(decimal amount) { if (amount > 0) balance += amount; } public bool Withdraw(decimal amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; } }
7.2 访问修饰符详解
五种访问修饰符
| 修饰符 | 访问范围 | 使用场景 |
| ——– | ———- | ———- |
| public | 任何地方 | 公开的API接口 |
| private | 仅在类内部 | 内部实现细节 |
| protected | 类内部和派生类 | 需要子类访问的成员 |
| internal | 同一程序集 | 程序集内部使用 |
| protected internal | 同一程序集或派生类 | 组合场景 |
| private protected(C# 7.2+) | 同一程序集的派生类 | 更严格的protected |
public
public class PublicDemo { public string PublicField = "公开字段"; public void PublicMethod() { Console.WriteLine("公开方法"); } } // 任何地方都可以访问 var demo = new PublicDemo(); Console.WriteLine(demo.PublicField); demo.PublicMethod();
private
public class PrivateDemo { private string privateField = "私有字段"; private void PrivateMethod() { Console.WriteLine("私有方法"); } public void PublicMethod() { // 类内部可以访问私有成员 Console.WriteLine(privateField); PrivateMethod(); } } var demo = new PrivateDemo(); // Console.WriteLine(demo.privateField); // 编译错误 // demo.PrivateMethod(); // 编译错误 demo.PublicMethod(); // 通过公开方法间接访问
protected
public class BaseClass { protected string protectedField = "受保护字段"; protected void ProtectedMethod() { Console.WriteLine("受保护方法"); } } public class DerivedClass : BaseClass { public void AccessProtected() { // 派生类可以访问protected成员 Console.WriteLine(protectedField); ProtectedMethod(); } } var baseObj = new BaseClass(); // baseObj.protectedField; // 编译错误 var derived = new DerivedClass(); // derived.protectedField; // 编译错误 derived.AccessProtected(); // 通过派生类方法访问
internal
// 在同一个程序集(项目)内可访问 internal class InternalClass { internal string InternalField = "内部字段"; } // 默认访问修饰符 class DefaultClass // 等同于internal class { string DefaultField; // 等同于private string }
7.3 属性与封装的深度应用
计算属性
public class Rectangle { private double width; private double height; public double Width { get => width; set { if (value <= 0) throw new ArgumentException("宽度必须大于0"); width = value; } } public double Height { get => height; set { if (value <= 0) throw new ArgumentException("高度必须大于0"); height = value; } } // 计算属性 public double Area => Width * Height; public double Perimeter => 2 * (Width + Height); public bool IsSquare => Width == Height; public double Diagonal => Math.Sqrt(Width * Width + Height * Height); }
延迟初始化
public class LazyInitializationDemo { private ExpensiveObject expensiveObject; public ExpensiveObject ExpensiveObject { get { if (expensiveObject == null) { expensiveObject = new ExpensiveObject(); Console.WriteLine("ExpensiveObject已创建"); } return expensiveObject; } } } // 使用Lazy<T>(更优雅的方式) public class LazyDemo { private Lazy<ExpensiveObject> lazyObject = new Lazy<ExpensiveObject>(() => new ExpensiveObject()); public ExpensiveObject ExpensiveObject => lazyObject.Value; }
7.4 嵌套类型
嵌套类
public class OuterClass { private string outerField = "外部类字段"; // 嵌套类 public class NestedClass { private string nestedField = "嵌套类字段"; public void AccessOuter(OuterClass outer) { // 嵌套类可以访问外部类的私有成员 Console.WriteLine(outer.outerField); } } private class PrivateNestedClass { // 只有外部类可以访问 } } // 使用嵌套类 var nested = new OuterClass.NestedClass();
7.5 不可变对象设计
只读集合
public class ImmutablePerson { // 只读字段 private readonly string name; private readonly DateTime birthDate; public string Name => name; public DateTime BirthDate => birthDate; // 计算属性 public int Age => DateTime.Now.Year - birthDate.Year; public ImmutablePerson(string name, DateTime birthDate) { this.name = name ?? throw new ArgumentNullException(nameof(name)); this.birthDate = birthDate; } // 创建修改后的副本 public ImmutablePerson WithName(string newName) { return new ImmutablePerson(newName, this.birthDate); } }
使用init-only属性(C# 9+)
public class PersonRecord { public string Name { get; init; } public int Age { get; init; } public DateTime CreatedAt { get; init; } = DateTime.Now; } // 只能在初始化时设置 var person = new PersonRecord { Name = "张三", Age = 30 }; // person.Name = "李四"; // 编译错误
7.6 设计模式与封装
单例模式
public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance => lazy.Value; private Singleton() { // 私有构造函数 } public void DoSomething() { Console.WriteLine("Doing something..."); } } // 使用 Singleton.Instance.DoSomething(); // new Singleton(); // 编译错误
工厂模式
// 产品接口 public interface IProduct { string Name { get; } void Use(); } // 具体产品 public class ProductA : IProduct { public string Name => "产品A"; public void Use() => Console.WriteLine("使用产品A"); } public class ProductB : IProduct { public string Name => "产品B"; public void Use() => Console.WriteLine("使用产品B"); } // 工厂类 public static class ProductFactory { public static IProduct CreateProduct(string type) { return type switch { "A" => new ProductA(), "B" => new ProductB(), _ => throw new ArgumentException("未知产品类型") }; } }
7.7 实战:完整的封装示例
public class SecureBankAccount { // 私有字段 private readonly string accountNumber; private decimal balance; private readonly List<Transaction> transactions; private static readonly decimal MinimumBalance = 0; private static readonly decimal MaximumWithdrawalPerDay = 10000; private decimal todayWithdrawn; private DateTime lastWithdrawalDate; // 公开属性 public string AccountNumber => accountNumber; public decimal Balance { get => balance; private set { if (value < MinimumBalance) throw new InvalidOperationException("余额不能低于最低限额"); balance = value; } } public string OwnerName { get; set; } public DateTime OpenDate { get; } public bool IsActive { get; private set; } public IReadOnlyList<Transaction> Transactions => transactions.AsReadOnly(); // 构造函数 public SecureBankAccount(string ownerName, decimal initialBalance = 0) { if (string.IsNullOrWhiteSpace(ownerName)) throw new ArgumentException("户主姓名不能为空"); accountNumber = GenerateAccountNumber(); OwnerName = ownerName; balance = initialBalance; transactions = new List<Transaction>(); OpenDate = DateTime.Now; IsActive = true; todayWithdrawn = 0; lastWithdrawalDate = DateTime.Now; if (initialBalance > 0) { RecordTransaction(TransactionType.Deposit, initialBalance, "开户存款"); } } // 公开方法 public void Deposit(decimal amount, string description = "") { ValidateActive(); ValidateAmount(amount); Balance += amount; RecordTransaction(TransactionType.Deposit, amount, description); } public bool Withdraw(decimal amount, string description = "") { ValidateActive(); ValidateAmount(amount); if (!CanWithdraw(amount)) return false; Balance -= amount; UpdateWithdrawalTracking(amount); RecordTransaction(TransactionType.Withdrawal, amount, description); return true; } public bool Transfer(SecureBankAccount target, decimal amount, string description = "") { if (target == null) throw new ArgumentNullException(nameof(target)); if (target == this) throw new InvalidOperationException("不能转账给自己"); if (Withdraw(amount, $"转账给 {target.OwnerName}: {description}")) { target.Deposit(amount, $"来自 {OwnerName} 的转账: {description}"); return true; } return false; } public void Close() { if (Balance > 0) throw new InvalidOperationException("账户有余额,无法关闭"); IsActive = false; RecordTransaction(TransactionType.System, 0, "账户关闭"); } public string GetStatement(DateTime? startDate = null, DateTime? endDate = null) { var sb = new StringBuilder(); sb.AppendLine("========== 账户对账单 =========="); sb.AppendLine($"账户号: {AccountNumber}"); sb.AppendLine($"户主: {OwnerName}"); sb.AppendLine($"当前余额: {Balance:C}"); sb.AppendLine($"开户日期: {OpenDate:yyyy-MM-dd}"); sb.AppendLine($"状态: {(IsActive ? "正常" : "已关闭")}"); sb.AppendLine(); var filtered = transactions.AsEnumerable(); if (startDate.HasValue) filtered = filtered.Where(t => t.Date >= startDate.Value); if (endDate.HasValue) filtered = filtered.Where(t => t.Date <= endDate.Value); sb.AppendLine("交易记录:"); foreach (var transaction in filtered.OrderByDescending(t => t.Date)) { sb.AppendLine(transaction.ToString()); } return sb.ToString(); } // 私有方法 private void ValidateActive() { if (!IsActive) throw new InvalidOperationException("账户已关闭"); } private static void ValidateAmount(decimal amount) { if (amount <= 0) throw new ArgumentException("金额必须大于0"); } private bool CanWithdraw(decimal amount) { if (amount > Balance) { Console.WriteLine("余额不足"); return false; } ResetDailyLimitIfNeeded(); if (todayWithdrawn + amount > MaximumWithdrawalPerDay) { Console.WriteLine("超出每日取款限额"); return false; } return true; } private void UpdateWithdrawalTracking(decimal amount) { todayWithdrawn += amount; lastWithdrawalDate = DateTime.Now; } private void ResetDailyLimitIfNeeded() { if (lastWithdrawalDate.Date != DateTime.Now.Date) { todayWithdrawn = 0; } } private void RecordTransaction(TransactionType type, decimal amount, string description) { transactions.Add(new Transaction { Date = DateTime.Now, Type = type, Amount = amount, BalanceAfter = Balance, Description = description }); } private static string GenerateAccountNumber() { return $"SA{DateTime.Now:yyyyMMdd}{Guid.NewGuid().ToString("N")[..8].ToUpper()}"; } } // 交易记录类 public class Transaction { public DateTime Date { get; set; } public TransactionType Type { get; set; } public decimal Amount { get; set; } public decimal BalanceAfter { get; set; } public string Description { get; set; } public override string ToString() { return $"[{DateTime:yyyy-MM-dd HH:mm:ss}] {Type,-10} {Amount,12:C} | 余额: {BalanceAfter,12:C} | {Description}"; } } public enum TransactionType { Deposit, Withdrawal, Transfer, System }
练习题
基础练习
1. 访问修饰符测试:创建一个类,包含所有类型的成员(public、private、protected、internal),编写代码测试每种修饰符的访问范围。
2. 封装练习:将以下未封装的类改写为封装良好的类:
<code csharp>
public class Student
{
public string Name;
public int Age;
public int[] Scores;
}
</code>
3. 属性验证:创建Temperature类,封装摄氏温度值:
- 设置时自动验证(-273.15到1000度之间)
- 提供华氏温度的读取属性
- 提供开尔文温度的读取属性
进阶练习
4. 安全集合:创建一个只读包装类ReadOnlyList<T>,封装List<T>,提供以下特性:
- 外部只能读取,不能修改
- 内部可以通过特定方法修改
- 支持索引访问
- 支持枚举
5. 配置类设计:设计一个AppConfig类,封装应用程序配置:
- 从配置文件读取
- 提供类型安全的访问
- 支持默认值
- 配置变更通知
6. 密码管理器:设计一个安全的PasswordManager类:
- 密码加密存储
- 密码强度验证
- 历史密码记录
- 密码过期检查
挑战练习
7. 访问控制框架:实现一个基于角色的访问控制系统:
- 用户、角色、权限的定义
- 资源级的访问控制
- 支持权限继承
- 访问日志记录
8. API客户端封装:封装一个HTTP API客户端:
- 隐藏HTTP细节
- 提供强类型接口
- 自动重试机制
- 响应缓存
- 错误处理
本章小结
本章学习了C#的封装与访问修饰符:
- 封装的概念和好处
- 五种访问修饰符的详细用法
- 属性与封装的深度应用
- 不可变对象设计
- 常见设计模式(单例、工厂)
- 完整的封装实战示例
良好的封装是编写可维护、安全代码的基础。
csharp/第七章封装与访问修饰符.txt · 最后更改: 由 127.0.0.1
