目录

第七章 封装与访问修饰符

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类,封装摄氏温度值:

  1. 设置时自动验证(-273.15到1000度之间)
  2. 提供华氏温度的读取属性
  3. 提供开尔文温度的读取属性

进阶练习

4. 安全集合:创建一个只读包装类ReadOnlyList<T>,封装List<T>,提供以下特性:

  1. 外部只能读取,不能修改
  2. 内部可以通过特定方法修改
  3. 支持索引访问
  4. 支持枚举

5. 配置类设计:设计一个AppConfig类,封装应用程序配置:

  1. 从配置文件读取
  2. 提供类型安全的访问
  3. 支持默认值
  4. 配置变更通知

6. 密码管理器:设计一个安全的PasswordManager类:

  1. 密码加密存储
  2. 密码强度验证
  3. 历史密码记录
  4. 密码过期检查

挑战练习

7. 访问控制框架:实现一个基于角色的访问控制系统:

  1. 用户、角色、权限的定义
  2. 资源级的访问控制
  3. 支持权限继承
  4. 访问日志记录

8. API客户端封装:封装一个HTTP API客户端:

  1. 隐藏HTTP细节
  2. 提供强类型接口
  3. 自动重试机制
  4. 响应缓存
  5. 错误处理

本章小结

本章学习了C#的封装与访问修饰符:

  1. 封装的概念和好处
  2. 五种访问修饰符的详细用法
  3. 属性与封装的深度应用
  4. 不可变对象设计
  5. 常见设计模式(单例、工厂)
  6. 完整的封装实战示例

良好的封装是编写可维护、安全代码的基础。