从零开始学C#封装:打造高内聚,低耦合代码的必修课

一、什么是封装及面向对象基础概念回顾

在面向对象编程(OOP)中,有三个重要的特性:封装、继承和多态。简单来说,继承允许一个类(子类)继承另一个类(父类)的属性和方法;多态则让不同的对象对同一消息做出不同的响应。而封装,就像是把数据和操作数据的方法捆绑在一起,形成一个独立的单元,并且隐藏内部的实现细节,只对外提供必要的接口。

举个生活中的例子,汽车就是一个封装的实例。我们开车时,只需要知道如何操作方向盘、油门、刹车这些外部接口就可以了,并不需要了解汽车发动机内部复杂的工作原理。同样,在编程中,封装可以提高代码的安全性、可维护性和复用性。

二、封装的核心实现手段

1. 访问修饰符的魔法

访问修饰符用于控制类、字段、方法等的访问权限,就像不同等级的门禁卡一样,决定了谁可以访问这些代码元素。下面是C#中常见的访问修饰符及其具体解释和使用场景:

访问修饰符访问级别具体解释使用场景
public公共的任何代码都可以访问,就像公共通道,所有人都能自由进出。当你希望某个类、方法或字段可以被项目中的任何地方使用时,比如定义一个公共的工具类方法。
private私有的只有在定义它的类内部才能访问,如同私人密室,外人无法进入。通常用于隐藏类的内部实现细节,防止外部代码直接访问和修改。比如类中的一些中间计算变量。
protected受保护的可以在定义它的类以及该类的子类中访问,类似家族通道,只有家族成员(子类)才能使用。当你希望子类能够访问父类的某些成员,但又不希望外部类访问时使用。
internal内部的可以在同一程序集(简单理解为同一个项目)内的代码中访问,就像公司内部的办公区域,只有公司员工(同一项目的代码)才能进入。用于在项目内部共享代码,而不希望被其他项目直接访问。
protected internal受保护的内部可以在同一程序集内的任何代码以及该类的子类中访问,不管子类是否在同一程序集。相当于既可以让公司员工使用,也允许家族成员(子类)在其他公司也能使用。当你希望在项目内部以及子类中都能访问某个成员时使用。

2. 属性的优雅封装

在C#中,属性是封装字段的标准方式。直接使用公共字段存在一些问题,比如无法对数据进行验证和控制。例如,如果一个类有一个公共的年龄字段,外部代码可以随意将其设置为一个不合理的值(如负数),这可能会导致程序出现错误。而属性通过get和set访问器,可以在读写数据时执行额外的逻辑,从而保证数据的有效性。

// 定义一个Person类
public class Person
{
    // 定义一个私有字段,用于存储姓名
    // 私有字段只能在类内部访问,外部无法直接修改,保证了数据的安全性
    private string _name;

    // 定义一个公共属性,用于对外提供对姓名的访问和设置
    public string Name
    {
        // get访问器,用于获取姓名的值
        // 当外部代码调用Person对象的Name属性时,会执行这个get访问器
        get => _name;

        // set访问器,用于设置姓名的值
        // 当外部代码给Person对象的Name属性赋值时,会执行这个set访问器
        set
        {
            // 验证输入的姓名是否为空
            if (string.IsNullOrEmpty(value))
                // 如果为空,抛出一个ArgumentException异常,提示姓名不能为空
                throw new ArgumentException("姓名不能为空");
            // 验证输入的姓名长度是否超过50
            if (value.Length > 50)
                // 如果超过50,抛出一个ArgumentException异常,提示姓名过长
                throw new ArgumentException("姓名过长");
            // 将输入的姓名转换为大写并赋值给私有字段
            _name = value.ToUpper();
        }
    }
}

3. 方法的职责划分

方法的职责划分遵循单一职责原则,即一个方法只负责一项明确的任务。这样可以提高代码的可读性和可维护性。

// 定义订单项类
public class OrderItem
{
    // 定义公共属性,用于存储订单项的产品名称
    // 每个订单项都有一个对应的产品名称,方便识别订单项的内容
    public string ProductName { get; set; }

    // 定义公共属性,用于存储订单项的价格
    // 价格是订单项的重要信息,用于计算订单总价
    public decimal Price { get; set; }

    // 定义公共属性,用于存储订单项的数量
    // 数量表示该产品在订单中的购买份数,与价格一起计算该订单项的总价
    public int Quantity { get; set; }
}

// 定义订单类
public class Order
{
    // 定义一个私有字段,用于存储订单项的列表
    // 订单由多个订单项组成,使用列表来存储这些订单项
    private List<OrderItem> _items = new List<OrderItem>();

    // 定义一个公共方法,用于向订单中添加订单项
    public void AddItem(OrderItem item)
    {
        // 将订单项添加到列表中
        _items.Add(item);
    }

    // 定义一个公共方法,用于计算订单的总价
    public decimal CalculateTotal()
    {
        // 遍历订单项列表,计算每个订单项的总价并求和
        return _items.Sum(i => i.Price * i.Quantity);
    }
}

三、封装的高阶应用场景

1. 数据验证层

在实际开发中,我们经常需要对用户输入的数据进行验证,确保数据的有效性。通过封装数据验证逻辑,可以提高代码的复用性和可维护性。

// 定义一个User类
public class User
{
    // 定义一个私有字段,用于存储用户的邮箱
    private string _email;

    // 定义一个公共属性,用于对外提供对邮箱的访问和设置
    public string Email
    {
        // get访问器,用于获取邮箱的值
        get => _email;
        // set访问器,用于设置邮箱的值
        set
        {
            // 验证输入的邮箱是否有效
            if (!IsValidEmail(value))
                // 如果无效,抛出一个FormatException异常,提示邮箱格式错误
                throw new FormatException("无效邮箱格式");
            // 将输入的邮箱赋值给私有字段
            _email = value;
        }
    }

    // 定义一个私有方法,用于验证邮箱格式是否有效
    private bool IsValidEmail(string email)
    {
        // 这里可以实现复杂的邮箱验证逻辑,例如使用正则表达式
        // 简单示例,判断是否包含@符号
        return email.Contains("@");
    }
}

2. 懒加载优化

懒加载(延迟初始化)是一种优化技术,它允许我们在真正需要使用某个对象时才进行初始化,而不是在对象创建时就进行初始化。这样可以提高程序的性能,特别是在处理一些占用资源较大的对象时。

// 定义一个Product类
public class Product
{
    // 定义一个私有字段,用于存储产品的评论列表
    // 初始值为null,表示还没有加载评论
    private List<Review> _reviews;

    // 定义一个公共属性,用于对外提供对评论列表的访问
    public List<Review> Reviews
    {
        get
        {
            // 如果评论列表为空,则从数据库加载评论
            if (_reviews == null)
            {
                // 调用私有方法LoadReviewsFromDB从数据库加载评论
                _reviews = LoadReviewsFromDB();
            }
            // 返回评论列表
            return _reviews;
        }
    }

    // 定义一个私有方法,用于从数据库加载评论
    private List<Review> LoadReviewsFromDB()
    {
        // 这里可以实现数据库查询逻辑,例如使用Entity Framework
        // 简单示例,返回一个空列表
        return new List<Review>();
    }
}

// 定义评论类
public class Review
{
    // 可以定义评论的相关属性,例如评论内容、评分等
}

3. 日志监控系统

日志监控系统可以记录程序的运行状态和关键操作,方便后续的调试和分析。通过封装日志记录逻辑,可以在不影响原有业务逻辑的情况下添加日志功能。

// 定义一个Account类
public class Account
{
    // 定义一个私有字段,用于存储账户的余额
    private decimal _balance;

    // 定义一个公共属性,用于对外提供对余额的访问和设置
    public decimal Balance
    {
        // get访问器,用于获取余额的值,并记录日志
        get
        {
            // 调用Logger类的Log方法记录查询余额的日志
            Logger.Log($"查询余额:{_balance}");
            // 返回余额的值
            return _balance;
        }
        // set访问器,用于设置余额的值,并记录日志
        set
        {
            // 调用Logger类的Log方法记录修改余额的日志
            Logger.Log($"修改余额:{_balance}{value}");
            // 将输入的余额赋值给私有字段
            _balance = value;
        }
    }
}

// 定义日志记录类
// 静态类表示该类不能被实例化,只能通过类名直接调用其静态成员
public static class Logger
{
    // 定义一个静态方法,用于记录日志
    public static void Log(string message)
    {
        // 这里可以实现日志记录逻辑,例如将日志写入文件或数据库
        // 简单示例,将日志输出到控制台
        Console.WriteLine(message);
    }
}

4. 安全权限控制

在一些应用中,我们需要对某些操作进行权限控制,只有具备相应权限的用户才能执行这些操作。通过封装权限验证逻辑,可以确保系统的安全性。

// 定义一个AdminPanel类
public class AdminPanel
{
    // 定义一个私有字段,用于存储是否已登录的状态
    private bool _isLoggedIn;

    // 定义一个公共方法,用于执行需要权限控制的操作
    public void AccessControl()
    {
        // 如果未登录,则抛出未授权访问异常
        if (!_isLoggedIn)
            throw new UnauthorizedAccessException();
        // 执行敏感操作,例如修改系统配置等
        // 这里可以添加具体的业务逻辑
        Console.WriteLine("执行敏感操作");
    }
}

四、封装的注意事项

1. 避免过度设计

过度设计是指在代码中添加了过多不必要的封装和抽象,导致代码变得复杂难懂,增加了维护成本。下面是一个反模式示例及改进方案:

// 反模式示例
public class BadExample
{
    // 定义一个公共方法,用于获取数据
    public void GetData()
    {
        // 调用私有方法执行步骤1
        Step1();
        // 调用私有方法执行步骤2
        Step2();
        // 调用私有方法执行步骤3
        Step3();
    }

    // 定义一个私有方法,用于执行步骤1
    private void Step1() { }
    // 定义一个私有方法,用于执行步骤2
    private void Step2() { }
    // 定义一个私有方法,用于执行步骤3
    private void Step3() { }
}

// 改进方案
public class GoodExample
{
    // 定义一个公共方法,用于获取数据
    public void GetData()
    {
        // 调用私有方法执行所有步骤
        PerformAllSteps();
    }

    // 定义一个私有方法,将相关步骤合并执行
    private void PerformAllSteps()
    {
        // 这里可以将步骤1、2、3的逻辑合并
    }
}

判断是否过度设计的一个简单方法是看代码是否为了封装而封装,是否增加了不必要的复杂性。如果一个功能可以用简单直接的方式实现,就不需要过度封装。

2. 合理暴露接口

遵循最小知识原则,只提供必要的公共方法和属性。这样可以减少外部代码对类内部实现的依赖,提高代码的可维护性。例如,一个类只需要提供一个计算结果的方法,而不需要将计算过程中的中间变量暴露给外部。

3. 性能优化考量

避免在属性中执行复杂计算。属性应该是轻量级的访问器,只用于获取或设置数据。如果需要执行复杂计算,应该使用方法来实现。

// 反模式
public class BadPerformanceExample
{
    // 定义一个公共属性,在get访问器中执行复杂计算
    public decimal TotalPrice
    {
        get { return CalculateTotal(); }
    }

    // 定义一个私有方法,用于计算总价
    private decimal CalculateTotal()
    {
        // 这里可以实现复杂的计算逻辑
        return 0;
    }
}

// 推荐方式
public class GoodPerformanceExample
{
    // 定义一个公共方法,用于获取总价
    public decimal GetTotalPrice()
    {
        return CalculateTotal();
    }

    // 定义一个私有方法,用于计算总价
    private decimal CalculateTotal()
    {
        // 这里可以实现复杂的计算逻辑
        return 0;
    }
}

4. 封装与继承的平衡

子类不应直接访问父类私有成员,而是通过公共接口交互。这样可以保证父类的封装性,避免子类对父类内部实现的过度依赖。

五、封装与设计模式的结合

1. 工厂模式

工厂模式的目的是将对象的创建和使用分离,提供一个统一的接口来创建对象,从而提高代码的可维护性和可扩展性。例如,当我们需要根据不同的条件创建不同类型的对象时,使用工厂模式可以避免在业务代码中出现大量的条件判断语句。

// 定义交通工具类型的枚举
public enum VehicleType
{
    Car,
    Truck
}

// 定义交通工具接口
public interface IVehicle
{
    // 定义一个方法,用于启动交通工具
    void Start();
}

// 定义汽车类,实现交通工具接口
public class Car : IVehicle
{
    // 实现启动方法
    public void Start()
    {
        Console.WriteLine("汽车启动");
    }
}

// 定义卡车类,实现交通工具接口
public class Truck : IVehicle
{
    // 实现启动方法
    public void Start()
    {
        Console.WriteLine("卡车启动");
    }
}

// 定义交通工具工厂类
public class VehicleFactory
{
    // 定义一个静态方法,用于根据交通工具类型创建具体的交通工具实例
    public static IVehicle CreateVehicle(VehicleType type)
    {
        return type switch
        {
            // 如果是汽车类型,则创建汽车实例
            VehicleType.Car => new Car(),
            // 如果是卡车类型,则创建卡车实例
            VehicleType.Truck => new Truck(),
            // 如果是其他类型,则抛出参数异常
            _ => throw new ArgumentException()
        };
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        // 通过工厂类创建汽车实例
        IVehicle car = VehicleFactory.CreateVehicle(VehicleType.Car);
        car.Start();

        // 通过工厂类创建卡车实例
        IVehicle truck = VehicleFactory.CreateVehicle(VehicleType.Truck);
        truck.Start();
    }
}

2. 策略模式

策略模式允许在运行时选择不同的算法或行为。它将每个算法封装成一个独立的类,这些类实现同一个接口,然后在需要使用算法的地方可以根据不同的需求动态切换算法。这样可以提高代码的灵活性和可维护性。

// 定义支付策略接口
public interface IPaymentStrategy
{
    // 定义一个方法,用于处理支付
    void ProcessPayment(decimal amount);
}

// 定义信用卡支付类,实现支付策略接口
public class CreditCardPayment : IPaymentStrategy
{
    // 实现处理支付的方法
    public void ProcessPayment(decimal amount)
    {
        // 实现信用卡支付的具体逻辑
        Console.WriteLine($"使用信用卡支付了 {amount} 元");
    }
}

// 定义支付宝支付类,实现支付策略接口
public class AlipayPayment : IPaymentStrategy
{
    // 实现处理支付的方法
    public void ProcessPayment(decimal amount)
    {
        // 实现支付宝支付的具体逻辑
        Console.WriteLine($"使用支付宝支付了 {amount} 元");
    }
}

// 定义订单类,使用支付策略
public class Order
{
    // 定义一个私有字段,用于存储支付策略
    private IPaymentStrategy _paymentStrategy;

    // 定义一个公共方法,用于设置支付策略
    public void SetPaymentStrategy(IPaymentStrategy strategy)
    {
        _paymentStrategy = strategy;
    }

    // 定义一个公共方法,用于处理订单支付
    public void Pay(decimal amount)
    {
        // 调用支付策略的处理支付方法
        _paymentStrategy.ProcessPayment(amount);
    }
}

3. 装饰器模式

// 定义订单接口
public interface IOrder
{
    // 定义一个方法,用于下单
    void PlaceOrder();
}

// 定义具体订单类,实现订单接口
public class ConcreteOrder : IOrder
{
    // 实现下单方法
    public void PlaceOrder()
    {
        Console.WriteLine("下单操作");
    }
}

// 定义日志记录订单类,实现订单接口
public class LoggedOrder : IOrder
{
    // 定义一个私有字段,用于存储被装饰的订单对象
    private readonly IOrder _order;

    // 构造函数,接收被装饰的订单对象
    public LoggedOrder(IOrder order)
    {
        _order = order;
    }

    // 实现下单方法,在下单前后记录日志
    public void PlaceOrder()
    {
        // 记录下单开始的日志
        Logger.Log("下单开始");
        // 调用被装饰订单对象的下单方法
        _order.PlaceOrder();
        // 记录下单完成的日志
        Logger.Log("下单完成");
    }
}

六、常见面试问题解答

Q1:封装和数据隐藏的区别?
A:封装是更广泛的概念,包含数据隐藏和方法聚合。数据隐藏是封装的一部分。
Q2:属性和字段的区别?
A:属性是字段的保护层,允许在读写时执行逻辑,而字段是直接存储数据的变量。
Q3:何时使用 protected vs internal?
A:protected 用于继承场景,internal 用于程序集内部访问。
Q4:如何实现只读属性?
A:只提供 get 访问器,或在 set 中抛出异常。

七、项目实战

实战项目1:用户管理系统

// 定义用户权限枚举,包含普通用户和管理员两种角色
public enum UserRole
{
    NormalUser,
    Administrator
}

// 用户类,用于封装用户的基本信息和操作
public class User
{
    // 私有字段,用于存储用户的姓名
    private string _name;
    // 私有字段,用于存储用户的邮箱
    private string _email;
    // 私有字段,用于存储用户的密码
    private string _password;
    // 私有字段,用于存储用户的角色
    private UserRole _role;

    // 公共属性 - 姓名
    // get访问器:返回用户的姓名
    // set访问器:对输入的姓名进行验证,若为空则抛出异常,否则将其赋值给私有字段
    public string Name
    {
        get => _name;
        set
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentException("姓名不能为空");
            _name = value;
        }
    }

    // 公共属性 - 邮箱
    // get访问器:返回用户的邮箱
    // set访问器:对输入的邮箱进行格式验证,若格式不正确则抛出异常,否则将其赋值给私有字段
    public string Email
    {
        get => _email;
        set
        {
            if (!IsValidEmail(value))
                throw new FormatException("邮箱格式不正确");
            _email = value;
        }
    }

    // 公共属性 - 密码(示例仅做基础验证)
    // get访问器:返回用户的密码
    // set访问器:对输入的密码进行长度验证,若长度小于6位则抛出异常,否则将其赋值给私有字段
    public string Password
    {
        get => _password;
        set
        {
            if (value.Length < 6)
                throw new ArgumentException("密码长度至少6位");
            _password = value;
        }
    }

    // 公共属性 - 用户角色
    // get访问器:返回用户的角色
    // set访问器:进行权限控制,只有当前登录用户为管理员时才能设置用户角色,否则抛出异常
    public UserRole Role
    {
        get => _role;
        set
        {
            // 权限控制:只有管理员可以设置其他用户角色
            if (CurrentUser?.Role != UserRole.Administrator)
                throw new UnauthorizedAccessException();
            _role = value;
        }
    }

    // 私有验证方法 - 邮箱格式
    // 简单判断邮箱中是否包含@符号,用于验证邮箱格式是否正确
    private bool IsValidEmail(string email)
    {
        return email.Contains("@"); // 简化验证逻辑
    }

    // 静态属性 - 当前登录用户(模拟单例)
    // 用于存储当前登录的用户信息
    public static User CurrentUser { get; private set; }

    // 登录方法
    // 模拟用户登录过程,将传入的邮箱和密码创建一个用户对象并赋值给当前登录用户
    public static void Login(string email, string password)
    {
        // 模拟数据库查询
        var user = new User { Email = email, Password = password };
        CurrentUser = user;
        Console.WriteLine("登录成功");
    }

    // 注销方法
    // 将当前登录用户置为null,表示用户已注销
    public static void Logout()
    {
        CurrentUser = null;
        Console.WriteLine("已注销");
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        try
        {
            // 模拟管理员用户登录
            User.Login("admin@example.com", "admin123");

            // 创建一个新用户对象,并设置其姓名、邮箱和密码
            var user = new User
            {
                Name = "张三",
                Email = "zhangsan@example.com",
                Password = "pass123"
            };

            // 管理员设置角色
            user.Role = UserRole.NormalUser;

            // 输出新用户创建成功的信息,包括用户姓名和角色
            Console.WriteLine($"用户 {user.Name} 创建成功,角色:{user.Role}");
        }
        catch (Exception ex)
        {
            // 捕获并输出可能出现的异常信息
            Console.WriteLine($"错误:{ex.Message}");
        }
    }
}

实战项目2:订单系统

// 订单项类,用于表示订单中的一个商品项
public class OrderItem
{
    // 商品名称属性,用于存储订单项中商品的名称
    public string ProductName { get; set; }
    // 商品价格属性,用于存储订单项中商品的单价
    public decimal Price { get; set; }
    // 商品数量属性,用于存储订单项中商品的购买数量
    public int Quantity { get; set; }
}

// 折扣策略接口,定义了应用折扣的方法
public interface IDiscountStrategy
{
    // 应用折扣的方法,接收一个总价作为参数,返回折扣后的价格
    decimal ApplyDiscount(decimal total);
}

// 百分比折扣策略类,实现了折扣策略接口
public class PercentageDiscount : IDiscountStrategy
{
    // 私有字段,用于存储折扣的百分比
    private decimal _percentage;

    // 构造函数,接收一个百分比参数并赋值给私有字段
    public PercentageDiscount(decimal percentage)
    {
        _percentage = percentage;
    }

    // 实现接口中的应用折扣方法,根据百分比计算折扣后的价格
    public decimal ApplyDiscount(decimal total)
    {
        return total * (1 - _percentage / 100);
    }
}

// 固定金额折扣策略类,实现了折扣策略接口
public class FixedAmountDiscount : IDiscountStrategy
{
    // 私有字段,用于存储固定的折扣金额
    private decimal _amount;

    // 构造函数,接收一个固定金额参数并赋值给私有字段
    public FixedAmountDiscount(decimal amount)
    {
        _amount = amount;
    }

    // 实现接口中的应用折扣方法,从总价中减去固定金额得到折扣后的价格
    public decimal ApplyDiscount(decimal total)
    {
        return Math.Max(total - _amount, 0);
    }
}

// 订单类,用于管理订单的相关操作
public class Order
{
    // 私有字段,用于存储订单项的列表
    private List<OrderItem> _items = new List<OrderItem>();
    // 私有字段,用于存储当前使用的折扣策略
    private IDiscountStrategy _discountStrategy;

    // 添加订单项的方法
    // 接收一个订单项对象,将其添加到订单项列表中,并记录日志
    public void AddItem(OrderItem item)
    {
        _items.Add(item);
        Logger.Log($"添加商品:{item.ProductName} x{item.Quantity}");
    }

    // 设置折扣策略的方法
    // 接收一个折扣策略对象,将其赋值给私有字段
    public void SetDiscountStrategy(IDiscountStrategy strategy)
    {
        _discountStrategy = strategy;
    }

    // 计算总价的方法
    // 先计算所有订单项的总价,记录原价日志
    // 如果设置了折扣策略,则应用折扣并记录折扣后价格日志
    // 最后返回最终的总价
    public decimal CalculateTotal()
    {
        decimal total = _items.Sum(i => i.Price * i.Quantity);
        Logger.Log($"原价:{total:C}");

        if (_discountStrategy != null)
        {
            total = _discountStrategy.ApplyDiscount(total);
            Logger.Log($"折扣后价格:{total:C}");
        }

        return total;
    }
}

// 日志记录类,使用静态类实现,方便在不同地方调用日志记录方法
public static class Logger
{
    // 静态方法,用于记录日志
    // 接收一个日志消息作为参数,将当前时间和消息输出到控制台
    public static void Log(string message)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {message}");
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        // 创建一个订单对象
        var order = new Order();

        // 向订单中添加一个笔记本电脑订单项
        order.AddItem(new OrderItem
        {
            ProductName = "笔记本电脑",
            Price = 8999,
            Quantity = 1
        });

        // 向订单中添加一个无线鼠标订单项
        order.AddItem(new OrderItem
        {
            ProductName = "无线鼠标",
            Price = 199,
            Quantity = 2
        });

        // 应用10%折扣
        order.SetDiscountStrategy(new PercentageDiscount(10));

        // 计算订单的最终总价
        decimal total = order.CalculateTotal();

        // 输出订单的最终总价
        Console.WriteLine($"最终总价:{total:C}");
    }
}

实战项目3:配置管理模块

// 配置项类型枚举,定义了不同类型的配置项
public enum ConfigType
{
    DatabaseConnection,
    AppSettings
}

// 配置变更事件参数类,继承自EventArgs类
// 用于在配置发生变更时传递相关信息
public class ConfigChangedEventArgs : EventArgs
{
    // 配置项的键
    public string Key { get; set; }
    // 配置项的旧值
    public string OldValue { get; set; }
    // 配置项的新值
    public string NewValue { get; set; }
}

// 配置管理类,用于管理配置项的加载、存储和变更通知
public class ConfigManager
{
    // 私有字段,使用字典存储配置项的键值对
    private Dictionary<string, string> _configs = new Dictionary<string, string>();
    // 私有字段,用于标记配置是否已经加载
    private bool _isLoaded = false;

    // 获取配置项的方法
    // 调用LoadConfigs方法确保配置已加载,然后尝试从字典中获取指定键的配置值
    public string GetConfig(string key)
    {
        LoadConfigs(); // 确保配置已加载
        return _configs.TryGetValue(key, out string value) ? value : null;
    }

    // 设置配置项的方法
    // 先获取旧值,然后将新值加密后存储到字典中
    // 最后触发配置变更事件
    public void SetConfig(string key, string value)
    {
        var oldValue = _configs.GetValueOrDefault(key);
        _configs[key] = Encrypt(value); // 加密存储
        OnConfigChanged(key, oldValue, value);
    }

    // 加载配置的方法
    // 如果配置已经加载则直接返回,否则模拟从文件读取配置并加密存储到字典中
    // 最后将加载标记置为true并输出加载完成的日志
    private void LoadConfigs()
    {
        if (_isLoaded) return;

        // 模拟加载过程
        _configs["DatabaseConnection"] = Encrypt("Server=localhost;Database=test");
        _configs["AppSettings"] = Encrypt("Theme=Dark;Language=zh-CN");

        _isLoaded = true;
        Console.WriteLine("配置加载完成");
    }

    // 加密方法(示例)
    // 将输入的字符串转换为字节数组,然后使用Base64编码进行加密
    private string Encrypt(string input)
    {
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
    }

    // 解密方法(示例)
    // 将Base64编码的字符串解码为字节数组,然后转换为字符串
    private string Decrypt(string input)
    {
        return Encoding.UTF8.GetString(Convert.FromBase64String(input));
    }

    // 配置变更事件,使用EventHandler委托
    // 用于在配置发生变更时通知订阅者
    public event EventHandler<ConfigChangedEventArgs> ConfigChanged;

    // 触发配置变更事件的方法
    // 当配置发生变更时,调用此方法触发事件并传递相关信息
    protected virtual void OnConfigChanged(string key, string oldValue, string newValue)
    {
        ConfigChanged?.Invoke(this, new ConfigChangedEventArgs
        {
            Key = key,
            OldValue = Decrypt(oldValue),
            NewValue = Decrypt(newValue)
        });
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        // 创建一个配置管理对象
        var config = new ConfigManager();

        // 注册事件
        // 当配置发生变更时,输出变更信息到控制台
        config.ConfigChanged += (sender, e) =>
        {
            Console.WriteLine($"配置变更:{e.Key}{e.OldValue} 变更为 {e.NewValue}");
        };

        // 获取配置(自动加载)
        // 获取数据库连接配置并解密后输出到控制台
        string connStr = config.GetConfig("DatabaseConnection");
        Console.WriteLine($"数据库连接:{config.Decrypt(connStr)}");

        // 修改配置
        // 设置应用程序设置配置,触发配置变更事件
        config.SetConfig("AppSettings", "Theme=Light;Language=en-US");
    }
}

代码说明

用户管理系统:

使用属性封装字段,实现数据验证
通过静态方法管理当前用户状态
权限控制通过枚举和角色检查实现

订单系统:

使用策略模式实现不同折扣算法
通过日志类记录操作过程
订单类封装业务逻辑,隐藏内部实现

配置管理模块:

懒加载通过LoadConfigs()方法实现
加密存储使用 Base64(实际应使用更安全算法)
事件机制实现配置变更通知
每个项目都包含完整的使用示例,建议从以下步骤开始练习:
复制代码到 Visual Studio 创建新项目
逐行阅读代码,理解每个部分的作用
修改验证逻辑或添加新功能(如用户注册、订单支付)
尝试扩展配置管理模块,支持更多加密方式

通过这些项目可以掌握:

封装的核心实现方式
数据验证与异常处理
简单设计模式的应用
事件与委托的使用
懒加载和单例模式的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值