结构型设计模式之代理模式

结构型设计模式之代理模式

1. 代理模式概述

代理模式(Proxy Pattern)是结构型设计模式的一种,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,可以在不改变目标对象的情况下,透明地添加额外的功能或控制逻辑。

在现实生活中,代理模式随处可见。比如,我们通过房产中介购买房子,中介作为房主的代理;我们请律师代表我们处理法律事务,律师作为我们的代理。在软件开发中,代理模式同样有着广泛的应用。

2. 代理模式的结构

代理模式主要包含以下几个角色:

«interface»
Subject
Request()
RealSubject
Request()
Proxy
realSubject: RealSubject
Request()
  • Subject(抽象主题): 定义了RealSubject和Proxy的共同接口,这样就可以在任何使用RealSubject的地方使用Proxy。
  • RealSubject(真实主题): 定义了Proxy所代表的真实对象,包含了最终要执行的业务逻辑。
  • Proxy(代理): 保存一个引用使得代理可以访问实体,并提供一个与Subject接口相同的接口,这样代理就可以用来替代实体。

3. 代理模式的类型

3.1 静态代理

静态代理是在编译时就确定了代理与被代理对象之间的关系。代理类和被代理类都实现同一个接口或继承同一个类,在代理类中维护一个被代理对象的引用,通过构造函数注入或者其他方式初始化。

3.2 动态代理

动态代理是在运行时动态生成代理类,不需要手动编写代理类的代码。在C#中,可以通过反射、表达式树、IL生成等技术实现动态代理。

3.3 远程代理

远程代理为一个位于不同地址空间的对象提供本地代表。例如,Web服务客户端代理就是一种远程代理,它使开发人员可以像调用本地方法一样调用远程服务。

3.4 虚拟代理

虚拟代理根据需要创建开销很大的对象,使得对象只在需要时才会被真正创建。例如,图片查看器中的图片加载就可以使用虚拟代理实现,当需要显示图片时才真正加载图片数据。

3.5 保护代理

保护代理用于控制对原始对象的访问,用于对象应该有不同的访问权限的情况。例如,在员工信息系统中,不同级别的用户对员工信息的访问权限不同。

3.6 缓存代理

缓存代理为开销大的运算结果提供临时存储,以便多个客户端共享使用。例如,Web浏览器的缓存就是一种缓存代理,它可以缓存网页内容,减少网络请求。

4. C#中的代理实现

在C#中,代理模式的实现主要有以下几种方式:

4.1 基于接口的静态代理

这是最基本的代理实现方式,代理类和被代理类都实现同一个接口。

// 定义抽象主题接口
public interface ISubject
{
    void Request();
}

// 实现真实主题类
public class RealSubject : ISubject
{
    public void Request()
    {
        Console.WriteLine("RealSubject: 处理请求");
    }
}

// 实现代理类
public class Proxy : ISubject
{
    private RealSubject _realSubject;

    public Proxy()
    {
        // 可以在构造函数中创建被代理对象
        _realSubject = new RealSubject();
    }

    public void Request()
    {
        // 在调用真实对象的方法前可以执行一些预处理
        Console.WriteLine("Proxy: 请求前的处理");
        
        // 调用真实对象的方法
        _realSubject.Request();
        
        // 在调用真实对象的方法后可以执行一些后处理
        Console.WriteLine("Proxy: 请求后的处理");
    }
}

// 客户端代码
public class Client
{
    public static void Main()
    {
        // 使用代理对象
        ISubject subject = new Proxy();
        subject.Request();
    }
}

4.2 基于透明代理的动态代理

在.NET中,可以使用System.Runtime.Remoting.Proxies.RealProxy类和System.Runtime.Remoting.Messaging.IMessage接口实现动态代理。

using System;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;

// 定义抽象主题接口(必须继承MarshalByRefObject才能被透明代理代理)
public interface ISubject
{
    void Request();
}

// 实现真实主题类
public class RealSubject : MarshalByRefObject, ISubject
{
    public void Request()
    {
        Console.WriteLine("RealSubject: 处理请求");
    }
}

// 实现动态代理类
public class DynamicProxy : RealProxy
{
    private readonly object _target;

    public DynamicProxy(Type type, object target) : base(type)
    {
        _target = target;
    }

    // 重写Invoke方法来拦截方法调用
    public override IMessage Invoke(IMessage msg)
    {
        // 转换为方法调用消息
        IMethodCallMessage methodCall = msg as IMethodCallMessage;
        
        Console.WriteLine($"DynamicProxy: 拦截方法 {methodCall.MethodName} 调用");
        
        try
        {
            // 在调用真实对象的方法前执行一些预处理
            Console.WriteLine("DynamicProxy: 请求前的处理");
            
            // 调用真实对象的方法
            object returnValue = methodCall.MethodBase.Invoke(_target, methodCall.InArgs);
            
            // 在调用真实对象的方法后执行一些后处理
            Console.WriteLine("DynamicProxy: 请求后的处理");
            
            // 返回方法调用结果
            return new ReturnMessage(returnValue, null, 0, 
                methodCall.LogicalCallContext, methodCall);
        }
        catch (Exception e)
        {
            // 处理异常
            return new ReturnMessage(e, methodCall);
        }
    }
}

// 客户端代码
public class Client
{
    public static void Main()
    {
        // 创建实际对象
        RealSubject realSubject = new RealSubject();
        
        // 创建动态代理
        DynamicProxy proxy = new DynamicProxy(typeof(ISubject), realSubject);
        
        // 获取代理对象(强制转换为ISubject接口)
        ISubject subject = (ISubject)proxy.GetTransparentProxy();
        
        // 通过代理对象调用方法
        subject.Request();
    }
}

4.3 基于DispatchProxy的动态代理(.NET Core)

在.NET Core中,可以使用System.Reflection.DispatchProxy类实现动态代理,这种方式比RealProxy更简洁。

using System;
using System.Reflection;

// 定义抽象主题接口
public interface ISubject
{
    void Request();
}

// 实现真实主题类
public class RealSubject : ISubject
{
    public void Request()
    {
        Console.WriteLine("RealSubject: 处理请求");
    }
}

// 实现动态代理类
public class SubjectProxy : DispatchProxy
{
    private ISubject _realSubject;

    // 设置被代理的实际对象
    public void SetRealSubject(ISubject realSubject)
    {
        _realSubject = realSubject;
    }

    // 创建代理
    public static ISubject Create<T>(T target) where T : ISubject
    {
        // 创建代理实例
        var proxy = Create<ISubject, SubjectProxy>();
        
        // 设置被代理对象
        ((SubjectProxy)proxy).SetRealSubject(target);
        
        return proxy;
    }

    // 实现拦截方法
    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        try
        {
            // 在调用真实对象的方法前执行一些预处理
            Console.WriteLine($"SubjectProxy: 拦截方法 {targetMethod.Name} 调用");
            Console.WriteLine("SubjectProxy: 请求前的处理");
            
            // 调用真实对象的方法
            var result = targetMethod.Invoke(_realSubject, args);
            
            // 在调用真实对象的方法后执行一些后处理
            Console.WriteLine("SubjectProxy: 请求后的处理");
            
            return result;
        }
        catch (Exception ex)
        {
            // 处理异常
            Console.WriteLine($"SubjectProxy: 调用过程中出现异常: {ex.Message}");
            throw;
        }
    }
}

// 客户端代码
public class Client
{
    public static void Main()
    {
        // 创建实际对象
        RealSubject realSubject = new RealSubject();
        
        // 创建代理
        ISubject subject = SubjectProxy.Create(realSubject);
        
        // 通过代理调用方法
        subject.Request();
    }
}

5. 代理模式的实际应用场景

5.1 虚拟代理示例 - 图片延迟加载

虚拟代理可以用于延迟加载大型资源,如图片、视频等。下面是一个图片延迟加载的示例:

// 定义图片加载器接口
public interface IImageLoader
{
    void DisplayImage();
}

// 实现真实图片加载器
public class RealImageLoader : IImageLoader
{
    private string _fileName;

    public RealImageLoader(string fileName)
    {
        _fileName = fileName;
        LoadImageFromDisk();
    }

    private void LoadImageFromDisk()
    {
        // 模拟从磁盘加载图片的耗时操作
        Console.WriteLine($"加载图片: {_fileName}");
        Thread.Sleep(1000); // 模拟加载耗时
    }

    public void DisplayImage()
    {
        Console.WriteLine($"显示图片: {_fileName}");
    }
}

// 实现虚拟代理图片加载器
public class ProxyImageLoader : IImageLoader
{
    private RealImageLoader _realImageLoader;
    private string _fileName;

    public ProxyImageLoader(string fileName)
    {
        _fileName = fileName;
    }

    public void DisplayImage()
    {
        // 在真正需要显示图片时才创建RealImageLoader对象
        if (_realImageLoader == null)
        {
            _realImageLoader = new RealImageLoader(_fileName);
        }
        
        // 调用真实对象的方法
        _realImageLoader.DisplayImage();
    }
}

// 客户端代码
public class ImageGallery
{
    public static void Main()
    {
        // 创建多个图片代理
        IImageLoader image1 = new ProxyImageLoader("image1.jpg");
        IImageLoader image2 = new ProxyImageLoader("image2.jpg");
        IImageLoader image3 = new ProxyImageLoader("image3.jpg");
        
        // 只有当用户需要查看图片时,才会真正加载图片
        Console.WriteLine("图库已初始化,但尚未加载任何图片");
        
        // 用户点击查看第一张图片
        Console.WriteLine("\n用户点击了第一张图片");
        image1.DisplayImage();
        
        // 用户再次点击查看第一张图片(此时图片已加载,不会再次从磁盘加载)
        Console.WriteLine("\n用户再次点击了第一张图片");
        image1.DisplayImage();
        
        // 用户点击查看第二张图片
        Console.WriteLine("\n用户点击了第二张图片");
        image2.DisplayImage();
    }
}

5.2 保护代理示例 - 访问控制

保护代理可以用于控制对敏感资源的访问。下面是一个简单的访问控制示例:

// 定义文档接口
public interface IDocument
{
    void Open(string username);
    void Edit(string username);
    void Save(string username);
}

// 实现真实文档
public class RealDocument : IDocument
{
    private string _fileName;
    
    public RealDocument(string fileName)
    {
        _fileName = fileName;
        Load();
    }
    
    private void Load()
    {
        Console.WriteLine($"加载文档: {_fileName}");
    }
    
    public void Open(string username)
    {
        Console.WriteLine($"打开文档: {_fileName}");
    }
    
    public void Edit(string username)
    {
        Console.WriteLine($"编辑文档: {_fileName}");
    }
    
    public void Save(string username)
    {
        Console.WriteLine($"保存文档: {_fileName}");
    }
}

// 实现保护代理
public class DocumentProtectionProxy : IDocument
{
    private RealDocument _document;
    private string _fileName;
    private Dictionary<string, string[]> _userPermissions;
    
    public DocumentProtectionProxy(string fileName)
    {
        _fileName = fileName;
        // 初始化用户权限配置
        _userPermissions = new Dictionary<string, string[]>
        {
            {"admin", new[] {"open", "edit", "save"}},
            {"editor", new[] {"open", "edit"}},
            {"viewer", new[] {"open"}}
        };
    }
    
    private bool CheckPermission(string username, string operation)
    {
        if (!_userPermissions.ContainsKey(username))
        {
            Console.WriteLine($"用户 {username} 不存在");
            return false;
        }
        
        bool hasPermission = _userPermissions[username].Contains(operation);
        if (!hasPermission)
        {
            Console.WriteLine($"用户 {username} 没有 {operation} 权限");
        }
        
        return hasPermission;
    }
    
    public void Open(string username)
    {
        if (CheckPermission(username, "open"))
        {
            if (_document == null)
            {
                _document = new RealDocument(_fileName);
            }
            _document.Open(username);
        }
    }
    
    public void Edit(string username)
    {
        if (CheckPermission(username, "edit"))
        {
            if (_document == null)
            {
                _document = new RealDocument(_fileName);
            }
            _document.Edit(username);
        }
    }
    
    public void Save(string username)
    {
        if (CheckPermission(username, "save"))
        {
            if (_document == null)
            {
                _document = new RealDocument(_fileName);
            }
            _document.Save(username);
        }
    }
}

// 客户端代码
public class DocumentManager
{
    public static void Main()
    {
        // 创建文档代理
        IDocument document = new DocumentProtectionProxy("sensitive_data.doc");
        
        // 不同用户尝试执行不同操作
        document.Open("viewer");   // 成功,查看者有open权限
        document.Edit("viewer");   // 失败,查看者没有edit权限
        document.Save("viewer");   // 失败,查看者没有save权限
        
        Console.WriteLine();
        
        document.Open("editor");   // 成功,编辑者有open权限
        document.Edit("editor");   // 成功,编辑者有edit权限
        document.Save("editor");   // 失败,编辑者没有save权限
        
        Console.WriteLine();
        
        document.Open("admin");    // 成功,管理员有open权限
        document.Edit("admin");    // 成功,管理员有edit权限
        document.Save("admin");    // 成功,管理员有save权限
        
        Console.WriteLine();
        
        document.Open("guest");    // 失败,guest用户不存在
    }
}

5.3 缓存代理示例 - 计算结果缓存

缓存代理可以用于缓存耗时操作的结果,提高性能。下面是一个计算结果缓存的示例:

// 定义计算服务接口
public interface ICalculator
{
    double Calculate(int number);
}

// 实现真实计算服务
public class RealCalculator : ICalculator
{
    public double Calculate(int number)
    {
        Console.WriteLine($"执行复杂计算 f({number})");
        
        // 模拟耗时的复杂计算
        Thread.Sleep(1000);
        
        // 返回一个简单计算结果作为示例
        return Math.Pow(number, 2) + Math.Sqrt(number);
    }
}

// 实现缓存代理
public class CalculatorCacheProxy : ICalculator
{
    private RealCalculator _realCalculator;
    private Dictionary<int, double> _cache;
    
    public CalculatorCacheProxy()
    {
        _realCalculator = new RealCalculator();
        _cache = new Dictionary<int, double>();
    }
    
    public double Calculate(int number)
    {
        // 检查结果是否已缓存
        if (_cache.ContainsKey(number))
        {
            Console.WriteLine($"从缓存返回结果 f({number})");
            return _cache[number];
        }
        
        // 调用真实对象进行计算
        double result = _realCalculator.Calculate(number);
        
        // 缓存结果
        _cache[number] = result;
        Console.WriteLine($"缓存结果 f({number}) = {result}");
        
        return result;
    }
}

// 客户端代码
public class CalculationClient
{
    public static void Main()
    {
        // 创建计算器代理
        ICalculator calculator = new CalculatorCacheProxy();
        
        // 首次计算,需要执行真实计算并缓存结果
        Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
        
        // 再次计算相同的值,将直接从缓存返回
        Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
        
        // 计算不同的值,需要执行真实计算并缓存结果
        Console.WriteLine($"计算结果: {calculator.Calculate(10)}");
        
        // 再次计算相同的值,将直接从缓存返回
        Console.WriteLine($"计算结果: {calculator.Calculate(10)}");
        
        // 再次计算之前计算过的值,将直接从缓存返回
        Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
    }
}

5.4 远程代理示例 - Web API客户端

远程代理可以用于封装远程服务的复杂调用逻辑。下面是一个简单的Web API客户端代理示例:

using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;

// 定义用户数据模型
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

// 定义用户服务接口
public interface IUserService
{
    Task<User> GetUserAsync(int userId);
    Task<bool> UpdateUserAsync(User user);
    Task<bool> DeleteUserAsync(int userId);
}

// 实现远程代理
public class UserServiceProxy : IUserService
{
    private readonly HttpClient _httpClient;
    private readonly string _baseUrl;
    
    public UserServiceProxy(string baseUrl)
    {
        _httpClient = new HttpClient();
        _baseUrl = baseUrl;
    }
    
    public async Task<User> GetUserAsync(int userId)
    {
        // 添加日志
        Console.WriteLine($"获取用户信息: 用户ID = {userId}");
        
        try
        {
            // 发送GET请求
            var response = await _httpClient.GetAsync($"{_baseUrl}/users/{userId}");
            
            // 检查响应状态
            response.EnsureSuccessStatusCode();
            
            // 解析响应内容
            var content = await response.Content.ReadAsStringAsync();
            var user = JsonSerializer.Deserialize<User>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            
            return user;
        }
        catch (Exception ex)
        {
            // 记录异常
            Console.WriteLine($"获取用户时出错: {ex.Message}");
            throw;
        }
    }
    
    public async Task<bool> UpdateUserAsync(User user)
    {
        // 添加日志
        Console.WriteLine($"更新用户信息: 用户ID = {user.Id}, 名称 = {user.Name}");
        
        try
        {
            // 序列化用户数据
            var content = new StringContent(
                JsonSerializer.Serialize(user),
                System.Text.Encoding.UTF8,
                "application/json");
            
            // 发送PUT请求
            var response = await _httpClient.PutAsync($"{_baseUrl}/users/{user.Id}", content);
            
            // 检查响应状态
            return response.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            // 记录异常
            Console.WriteLine($"更新用户时出错: {ex.Message}");
            throw;
        }
    }
    
    public async Task<bool> DeleteUserAsync(int userId)
    {
        // 添加日志
        Console.WriteLine($"删除用户: 用户ID = {userId}");
        
        try
        {
            // 发送DELETE请求
            var response = await _httpClient.DeleteAsync($"{_baseUrl}/users/{userId}");
            
            // 检查响应状态
            return response.IsSuccessStatusCode;
        }
        catch (Exception ex)
        {
            // 记录异常
            Console.WriteLine($"删除用户时出错: {ex.Message}");
            throw;
        }
    }
}

// 客户端代码
public class ApiClient
{
    public static async Task Main()
    {
        // 创建用户服务代理
        IUserService userService = new UserServiceProxy("https://api.example.com");
        
        try
        {
            // 获取用户信息
            User user = await userService.GetUserAsync(123);
            Console.WriteLine($"获取到用户: {user.Name}, 邮箱: {user.Email}");
            
            // 更新用户信息
            user.Name = "新名称";
            bool updateResult = await userService.UpdateUserAsync(user);
            Console.WriteLine($"更新用户结果: {(updateResult ? "成功" : "失败")}");
            
            // 删除用户
            bool deleteResult = await userService.DeleteUserAsync(123);
            Console.WriteLine($"删除用户结果: {(deleteResult ? "成功" : "失败")}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"操作失败: {ex.Message}");
        }
    }
}

6. 代理模式与AOP

代理模式是面向切面编程(AOP)的基础。AOP是一种编程范式,它允许开发人员定义横切关注点(如日志记录、性能监控、事务管理等),并将这些关注点与业务逻辑分离。代理模式可以用来实现AOP,通过在代理类中添加横切关注点的代码,对目标对象的方法进行增强。

在.NET中,可以使用各种AOP框架来简化代理的创建和使用,如Castle DynamicProxy、PostSharp等。下面是一个使用Castle DynamicProxy实现AOP的简单示例:

using Castle.DynamicProxy;

// 定义接口
public interface IUserService
{
    void CreateUser(string username);
    void UpdateUser(int userId, string newUsername);
    void DeleteUser(int userId);
}

// 实现类
public class UserService : IUserService
{
    public void CreateUser(string username)
    {
        Console.WriteLine($"创建用户: {username}");
    }
    
    public void UpdateUser(int userId, string newUsername)
    {
        Console.WriteLine($"更新用户: ID = {userId}, 新用户名 = {newUsername}");
    }
    
    public void DeleteUser(int userId)
    {
        Console.WriteLine($"删除用户: ID = {userId}");
    }
}

// 定义拦截器
public class LoggingInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        // 方法执行前的处理
        Console.WriteLine($"[日志] 开始执行: {invocation.Method.Name}");
        Console.WriteLine($"[日志] 参数: {string.Join(", ", invocation.Arguments)}");
        
        try
        {
            // 执行原方法
            invocation.Proceed();
            
            // 方法执行后的处理
            Console.WriteLine($"[日志] 执行成功: {invocation.Method.Name}");
        }
        catch (Exception ex)
        {
            // 异常处理
            Console.WriteLine($"[日志] 执行异常: {invocation.Method.Name}, 错误: {ex.Message}");
            throw;
        }
        finally
        {
            // 清理工作
            Console.WriteLine($"[日志] 结束执行: {invocation.Method.Name}");
            Console.WriteLine(new string('-', 40));
        }
    }
}

// 客户端代码
public class AopClient
{
    public static void Main()
    {
        // 创建代理生成器
        var proxyGenerator = new ProxyGenerator();
        
        // 创建拦截器
        var interceptor = new LoggingInterceptor();
        
        // 创建目标对象
        var target = new UserService();
        
        // 创建代理对象
        IUserService proxy = proxyGenerator.CreateInterfaceProxyWithTarget<IUserService>(target, interceptor);
        
        // 通过代理对象调用方法
        proxy.CreateUser("张三");
        proxy.UpdateUser(1, "李四");
        proxy.DeleteUser(2);
    }
}

7. 代理模式的优缺点

7.1 优点

  1. 职责清晰:真实主题类只需关注核心业务逻辑,而代理类负责其他非核心功能。
  2. 高扩展性:在不修改目标对象的前提下,扩展目标对象的功能。
  3. 智能化:动态代理可以在运行时动态地决定是否代理某些方法,增加了灵活性。
  4. 隔离性:代理对象可以起到中介和保护目标对象的作用。

7.2 缺点

  1. 增加系统复杂度:引入代理会增加系统的复杂度。
  2. 请求处理速度可能变慢:增加了一个中间层,可能会导致请求处理变慢。
  3. 实现复杂:动态代理实现较为复杂,需要掌握反射等高级技术。

8. 代理模式与其他模式的比较

8.1 代理模式 vs 装饰器模式

代理模式和装饰器模式在结构上非常相似,但目的不同:

客户端
代理模式
装饰器模式
控制访问
增加功能
  • 代理模式:主要目的是控制对对象的访问,代理类通常会有自己的逻辑。
  • 装饰器模式:目的是动态地给对象添加功能,装饰类通常不会改变接口,而只是增强功能。

8.2 代理模式 vs 适配器模式

客户端
代理模式
适配器模式
相同接口
不同接口
  • 代理模式:客户端、代理类和目标类使用相同的接口。
  • 适配器模式:用于连接使用不同接口的类,适配器将一个类的接口转换成客户端期望的另一个接口。

9. 在.NET中应用代理模式的最佳实践

  1. 优先使用内置机制:.NET提供了多种实现代理的机制,如DispatchProxy、接口默认实现等。
  2. 考虑使用AOP框架:对于复杂的代理需求,考虑使用成熟的AOP框架,如Castle DynamicProxy。
  3. 接口设计:代理模式依赖于良好的接口设计,确保接口定义清晰、职责单一。
  4. 避免过度使用:代理会增加系统复杂度,只在确实需要时使用。
  5. 合理处理异常:在代理中处理异常时,确保不会掩盖原始异常信息。

10. 总结

代理模式是一种常用的结构型设计模式,它允许我们通过一个中间对象控制对另一个对象的访问。代理模式在.NET开发中有广泛的应用,从简单的访问控制到复杂的AOP实现。理解和掌握代理模式,可以帮助我们编写更加灵活、可维护的代码。

在实际应用中,代理模式常常与其他设计模式结合使用,如单例模式、工厂模式等。选择合适的代理类型和实现方式,取决于具体的业务需求和系统架构。

学习资源

  1. Design Patterns: Elements of Reusable Object-Oriented Software - GoF经典著作
  2. C# in Depth - Jon Skeet的C#进阶书籍
  3. Microsoft .NET文档 - DispatchProxy
  4. Castle DynamicProxy文档

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰茶_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值