====== 第七章 封装与访问修饰符 ====== ===== 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(更优雅的方式) public class LazyDemo { private Lazy lazyObject = new Lazy(() => 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 lazy = new Lazy(() => 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 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 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(); 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. **封装练习**:将以下未封装的类改写为封装良好的类: public class Student { public string Name; public int Age; public int[] Scores; } 3. **属性验证**:创建Temperature类,封装摄氏温度值: - 设置时自动验证(-273.15到1000度之间) - 提供华氏温度的读取属性 - 提供开尔文温度的读取属性 === 进阶练习 === 4. **安全集合**:创建一个只读包装类ReadOnlyList,封装List,提供以下特性: - 外部只能读取,不能修改 - 内部可以通过特定方法修改 - 支持索引访问 - 支持枚举 5. **配置类设计**:设计一个AppConfig类,封装应用程序配置: - 从配置文件读取 - 提供类型安全的访问 - 支持默认值 - 配置变更通知 6. **密码管理器**:设计一个安全的PasswordManager类: - 密码加密存储 - 密码强度验证 - 历史密码记录 - 密码过期检查 === 挑战练习 === 7. **访问控制框架**:实现一个基于角色的访问控制系统: - 用户、角色、权限的定义 - 资源级的访问控制 - 支持权限继承 - 访问日志记录 8. **API客户端封装**:封装一个HTTP API客户端: - 隐藏HTTP细节 - 提供强类型接口 - 自动重试机制 - 响应缓存 - 错误处理 ===== 本章小结 ===== 本章学习了C#的封装与访问修饰符: - 封装的概念和好处 - 五种访问修饰符的详细用法 - 属性与封装的深度应用 - 不可变对象设计 - 常见设计模式(单例、工厂) - 完整的封装实战示例 良好的封装是编写可维护、安全代码的基础。