====== 第七章 封装与访问修饰符 ======
===== 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#的封装与访问修饰符:
- 封装的概念和好处
- 五种访问修饰符的详细用法
- 属性与封装的深度应用
- 不可变对象设计
- 常见设计模式(单例、工厂)
- 完整的封装实战示例
良好的封装是编写可维护、安全代码的基础。