结构型设计模式

结构型设计模式,第1张

文章目录
  • 一、单例模式
    • 版本一
    • 版本二
    • 版本三
    • 版本四
    • 版本五
    • 版本六
  • 二、工厂方法
    • 版本一
    • 版本二
  • 抽象工厂
  • 责任链
  • 装饰器


一、单例模式 版本一

我们来看看第一种单例模式

class Singleton { 
public: 
	static Singleton * GetInstance() { 
		if (_instance == nullptr) { 
			_instance = new Singleton(); 
		}
		return _instance; 
	} 
private: 
//放在private里边是为了调用默认的初始化与释放的 *** 作
	Singleton() {} //构造,放在private是为了保证全局只有一个实例
	~Singleton() {} //析构函数放在private当中的原因是避免我们直接去delete它,否则在使用的时候容易变成野指针
	Singleton(const Singleton &clone){} //拷⻉构造 
	Singleton& operator=(const Singleton&) {} 
	static Singleton * _instance;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化

第一个版本我们来思考一下,这个没有考虑到哪些问题
线程安全
还有一个问题就是,在new这个对象的时候,是放在堆区的,这个对象是静态的放在全局区,我们的单例在进程退出的时候,析构函数是调不到的,所以会有一个内存泄漏的风险。这是一个多线程的风险,属于线程安全问题
所以我们引出版本二

版本二

这个版本二,主要是解决单例的全局静态变量的释放问题

// 类对象之间是友元的 
class Singleton { 
public: 
	static Singleton * GetInstance() { 
		if (_instance == nullptr) {
 			_instance = new Singleton(); 
 			atexit(Destructor); //对象在使用完以后会被释放掉,这个函数是在main函数结束之后调用的
 		}
 		return _instance; 
 	}
 	~Singleton() {} 
 private: 
 	static void Destructor() { 
 		if (nullptr != _instance) { 
 		// delete _instance; 
 			_instance = nullptr; 
 		} 
 	}
 	Singleton();//构造 
 	~Singleton() {} 
 	Singleton(const Singleton &cpy); //拷⻉构造 
	Singleton& operator=(const Singleton& other) {} 
 	static Singleton * _instance; 
};
Singleton* Singleton::_instance = nullptr;
//静态成员需要初始化 
// 还可以使⽤ 内部类,智能指针来解决; 此时还有线程安全问题

我们还有多线程问题需要解决

版本三

多线程问题的解决方式就是加锁,3.1这个锁是为了避免这个对象被重复创建,但是有一个性能问题。比如说我们使用10000次这种单例模式,只有一次是需要加这个锁去创建对象的。这个锁没有问题, *** 作就太重了,我们用的是互斥锁,在切换线程的时候,会降低我们获取这个单例的性能,我们把3.1锁注释掉。
我们来看3.2,这也是有问题的,可能会出现两次new的情况,第一次使用的时候,会有两个线程同时到达,因为两个线程同时在等待的话,会有一个线程进去,就会被new两次,所以就要使用一个if判断语句

#include  
class Singleton { // 懒汉模式 lazy load 
public: 
	static Singleton * GetInstance() { 
		// std::lock_guard lock(_mutex); // 3.1 切换线程 
		if (_instance == nullptr) { 
			std::lock_guard<std::mutex> lock(_mutex); // 3.2 
			if (_instance == nullptr) { 
				_instance = new Singleton(); // 1. 分配内存 // 2. 调用构造函数 // 3. 返回指针 // 多线程环境下 cpu reorder *** 作 
				atexit(Destructor); 
			} 
		}
		return _instance; 
	} 
private: 
	static void Destructor() {
		if (nullptr != _instance) { 
			delete _instance; 
			_instance = nullptr;
		}
	}
	Singleton(){} //构造 
	Singleton(const Singleton &cpy){} //拷⻉构造 
	Singleton& operator=(const Singleton&) {} 
	static Singleton * _instance;
	static std::mutex _mutex;
};
Singleton* Singleton::_instance = nullptr;//静态成员需要初始化 
std::mutex Singleton::_mutex; //互斥锁初始化

版本三考虑了一个多线程问题,但是有个特殊情况,我们先屡一下对象创建的过程,1、分配内存。2、调用构造函数 3、返回对象指针
这里就涉及到多线程环境下的问题
多线程环境下cpu有一个reorder *** 作,会帮我们自动优化上边创建对象的三个步骤,可能我们执行的顺序会被打乱。
所以我们有时候去gdb调试代码的时候,会发现我们构造的对象的值是一些乱码的值。
在java中有一个关键字叫valitile来进行内存屏障,可以解决问题

版本四

这里我们需要用到内存屏障,这里就使用到了C++11的内存屏障,获取内存屏障以后会使得new的这个 *** 作会安装顺序执行,就解决了内存重排的问题。

// volitile 
#include  
#include  
class Singleton { 
public: 
	static Singleton * GetInstance() { 
		Singleton* tmp = _instance.load(std::memory_order_relaxed); 	
		std::atomic_thread_fence(std::memory_order_acquire);//获取内存屏障 
		if (tmp == nullptr) { 
			std::lock_guard<std::mutex> lock(_mutex); 
			tmp = _instance.load(std::memory_order_relaxed); 
			if (tmp == nullptr) {
				tmp = new Singleton; 
				std::atomic_thread_fence(std::memory_order_release);//释放内 存屏障 
				_instance.store(tmp, std::memory_order_relaxed); 	
				atexit(Destructor); 
			} 
		}
		return tmp; 
	} 
private: 
	static void Destructor() { 
		Singleton* tmp = _instance.load(std::memory_order_relaxed); 
		if (nullptr != tmp) { 
			delete tmp; 
		} 
	}
	Singleton(){} 
	Singleton(const Singleton&) {} 
	Singleton& operator=(const Singleton&) {} 
	static std::atomic<Singleton*> _instance; 
	static std::mutex _mutex; };
	std::atomic<Singleton*> Singleton::_instance;//静态成员需要初始化 
	std::mutex Singleton::_mutex; //互斥锁初始化 
// g++ Singleton.cpp -o singleton -std=c++11
版本五

这里为什么没有多线程问题呢,主要是在C++11当中,static有一个magic的特性,如果有多线程进来的话,并发线程会阻塞等待初始化结束,静态变量只能被初始化一次,只会有一个线程进去,也没有cpu reorder的问题。
析构函数也是能调用成功的,因为静态是可以自动调用析构函数的,只有在堆上才需要手动去释放

// c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将 会阻塞等待初始化结束。 
// c++ effective 
class Singleton {
public:
	static Singleton& GetInstance() { 
	static Singleton instance;//采用静态对象的一种方式
	return instance;
}
private: 
	Singleton(){} 
	~Singleton() {} 
	Singleton(const Singleton&) {} 
	Singleton& operator=(const Singleton&) {} 
};
// 继承 Singleton 
// g++ Singleton.cpp -o singleton -std=c++11 
/*该版本具备 版本5 所有优点: 
1. 利⽤静态局部变量特性,延迟加载;
2. 利⽤静态局部变量特性,系统⾃动回收内存,⾃动调⽤析构函数; 
3. 静态局部变量初始化时,没有 new  *** 作带来的cpu指令reorder *** 作; 
4. c++11 静态局部变量初始化时,具备线程安全; 
5. */

这个方法的局限性在于扩展性比较差,如果一个项目有多个单例就比较麻烦,每个单例都要去这样写。
所以我们考虑使用模板

版本六
template<typename T> 
class Singleton { 
public: 
	static T& GetInstance() { 
		static T instance; // 这⾥要初始化DesignPattern,需要调⽤DesignPattern 构造函数,同时会调⽤⽗类的构造函数。 
		return instance; 
	} 
protected: 
	virtual ~Singleton() {} 
	Singleton() {} // protected修饰构造函数,才能让别⼈继承 
	Singleton(const Singleton&) {} 
	Singleton& operator =(const Singleton&) {} 
};
class DesignPattern : public Singleton<DesignPattern> { 
	friend class Singleton<DesignPattern>; // friend 能让Singleton 访问到 DesignPattern构造函数 
public: 
	~DesignPattern() {} 
private: 
	DesignPattern() {} 
	DesignPattern(const DesignPattern&) {} 
	DesignPattern& operator=(const DesignPattern&) {} 
};

采用这种继承的方式可避免重复代码

手撕单例模式可以用版本5,记性好就使用版本6

二、工厂方法

定义
定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使得一个类的实例化延迟到子类。 ——《设计模式》GoF
背景
实现一个导出数据的接口,让客户选择数据的导出方式;
要点
解决创建过程比较复杂,希望对外隐藏这些细节的场景;
比如连接池、线程池
隐藏对象真实类型;
对象创建会有很多参数来决定如何创建;
创建对象有复杂的依赖关系;
本质
延迟到子类来选择实现;

版本一

这个版本,是在父类的基础上去写这个子类,用子类去实现不同的方法

#include 
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:
    virtual bool Export(const std::string &data) = 0;
    virtual ~IExport(){}
};
class ExportXml : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};
class ExportJson : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};
// csv
class ExportTxt : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

// class ExportCSV : public IExport {
// public:
//     virtual bool Export(const std::string &data) {
//         return true;
//     }
// };

// =====1
int main() {
    std::string choose/* = */;
    if (choose == "txt") {
        /***/
        IExport *e = new ExportTxt();
        /***/
        e->Export("hello world");
    } else if (choose == "json") {
        /***/
        IExport *e = new ExportJson();
        /***/
        e->Export("hello world");
    } else if (choose == "xml") {
        IExport *e = new ExportXml();
        e->Export("hello world");
    } else if (choose == "csv") {
        IExport *e = new ExportXml();
        e->Export("hello world");
    }
}

版本二
#include 
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:
    virtual bool Export(const std::string &data) = 0;
    virtual ~IExport(){}
};
class ExportXml : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};
class ExportJson : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};
class ExportTxt : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};
class IExportFactory {
public:
    virtual IExport * NewExport(/* ... */) = 0;
};
//根据这个来生成不同工厂方法的子类
class ExportXmlFactory : public IExportFactory {
public:
    IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportXml;
        // 可能之后有什么 *** 作
        return temp;
    }
};
class ExportJsonFactory : public IExportFactory {
public:
    IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportJson;
        // 可能之后有什么 *** 作
        return temp;
    }
};
class ExportTxtFactory : public IExportFactory {
public:
    IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportTxt;
        // 可能之后有什么 *** 作
        return temp;
    }
};
class ExportData {
public:
    ExportData(IExportFactory *factory) : _factory(factory) {}
    ~ExportData() {
        if (_factory) {
            delete _factory;
            _factory = nullptr;
        }
    }
    bool Export(const std::string &data) { // 稳定的流程 往基类放 
        IExport * e = _factory->NewExport();
        e->Export(data);
    }
private:
    IExportFactory *_factory;
};

int main() {
    ExportData ed(new ExportTxtFactory);
    //屏蔽了所有实现细节,直接调用接口
    ed.Export("hello world");
    return 0;
} 
抽象工厂

定义
提供一个接口,让该接口负责创建一系列“相关或者相互依赖的对象”,无需指定它们具体的类。——《设计模式》GoF
背景
实现一个拥有导出导入数据的接口,让客户选择数据的导出导入方式;
结构图

#include 
// 实现导出数据的接口, 导出数据的格式包含 xml,json,文本格式txt 后面可能扩展excel格式csv
class IExport {
public:
    virtual bool Export(const std::string &data) = 0;
    virtual ~IExport(){}
};

class ExportXml : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportJson : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportTxt : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class ExportCSV : public IExport {
public:
    virtual bool Export(const std::string &data) {
        return true;
    }
};

class IImport {
public:
    virtual bool Import(const std::string &data) = 0;
    virtual ~IImport(){}
};

class ImportXml : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

class ImportJson : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

class ImportTxt : public IImport {
public:
    virtual bool Import(const std::string &data) {
        return true;
    }
};

// 对于初学者: 知道扩展代码
// 5年
class ImportCSV : public IImport {
public:
    virtual bool Import(const std::string &data) {
        // ....
        return true;
    }
};

class IDataApiFactory {
public:
    IDataApiFactory() {
        _export = nullptr;
        _import = nullptr;
    }
    virtual ~IDataApiFactory() {
        if (_export) {
            delete _export;
            _export = nullptr;
        }
        if (_import) {
            delete _import;
            _import = nullptr;
        }
    }
    bool Export(const std::string &data) {
        if (_export == nullptr) {
            _export = NewExport();
        }
        return _export->Export(data);
    }
    bool Import(const std::string &data) {
        if (_import == nullptr) {
            _import = NewImport();
        }
        return _import->Import(data);
    }
protected:
    virtual IExport * NewExport(/* ... */) = 0;
    virtual IImport * NewImport(/* ... */) = 0;
private:
    IExport *_export;
    IImport *_import;
};

class XmlApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportXml;
        // 可能之后有什么 *** 作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IImport * temp = new ImportXml;
        // 可能之后有什么 *** 作
        return temp;
    }
};

class JsonApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportJson;
        // 可能之后有什么 *** 作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IImport * temp = new ImportJson;
        // 可能之后有什么 *** 作
        return temp;
    }
};
class TxtApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportTxt;
        // 可能之后有什么 *** 作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IImport * temp = new ImportTxt;
        // 可能之后有什么 *** 作
        return temp;
    }
};

class CSVApiFactory : public IDataApiFactory {
protected:
    virtual IExport * NewExport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IExport * temp = new ExportCSV;
        // 可能之后有什么 *** 作
        return temp;
    }
    virtual IImport * NewImport(/* ... */) {
        // 可能有其它 *** 作,或者许多参数
        IImport * temp = new ImportCSV;
        // 可能之后有什么 *** 作
        return temp;
    }
};

// 相关性  依赖性    工作当中
int main () {
    IDataApiFactory *factory = new CSVApiFactory();
    factory->Import("hello world");
    factory->Export("hello world");
    return 0;
}
责任链

定义
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。 ——《设计模式》GoF
背景
请求流程,1 天内需要主程序批准,3 天内需要项目经理批准,3 天以上需要老板批准;
要点
解耦请求方和处理方,请求方不知道请求是如何被处理,处理方的组成是由相互独立的子处理构成,子处理流程通过链表的方式连接,子处理请求可以按任意顺序组合;
责任链请求强调请求最终由一个子处理流程处理;通过了各个子处理条件判断;
责任链扩展就是功能链,功能链强调的是,一个请求依次经由功能链中的子处理流程处理;
将职责以及职责顺序运行进行抽象,那么职责变化可以任意扩展,同时职责顺序也可以任意扩展;
本质
分离职责,动态组合;

稳定点就是流程是稳定的
天数是变化点

#include 
class Context {
public:
    std::string name;
    int day;
};
// 稳定点 和 变化点
// 稳定点:批假流程   由职位从低到高批假  有权限
// 变化点:请假天数、增加批假人员
// 在一个整洁的房子(稳定点),好动的猫(变化),怎么保证这个房子的整洁?
// 把猫放在笼子里
// 如果某一个需求 只有稳定点  不需要设计模式(应对变化的)
// 如果全是变化点,也不需要设计模式?软件开发怎么解决全是变化点?脚本语言 热更新 来应对变化点
// 如果两者都存在,不是消灭变化点,是隔离变化点   解耦合
// 多线程锁  锁的类型  锁的粒度 线程池  
class LeaveRequest {
public:
    bool HandleRequest(const Context &ctx) {
        if (ctx.day <= 1)
            HandleByBeaty(ctx);
        if (ctx.day <= 1)
            HandleByMainProgram(ctx);
        else if (ctx.day <= 10)
            HandleByProjMgr(ctx);
        else
            HandleByBoss(ctx);
    }

public:
    bool HandleByBeaty(const Context &ctx) {        
    }
    bool HandleByMainProgram(const Context &ctx) {
    }
    bool HandleByProjMgr(const Context &ctx) {  
    }
    bool HandleByBoss(const Context &ctx) {
    }
};
#include 

class Context {
public:
    std::string name;
    int day;
};

// 稳定点抽象,变化点扩展
class IHandler {
public:
    virtual ~IHandler() {}
    void SetNextHandler(IHandler *next) { // 链表关系
        next = next;
    }
    // 抽象稳定点,对扩展开放
    // 模板模式:固定算法骨架,通过子类去扩展子流程
    bool Handle(const Context &ctx) {
        if (CanHandle(ctx)) {
            return HandleRequest(ctx);
        } else if (GetNextHandler()) {
            return GetNextHandler()->Handle(ctx);
        } else {
            // err
        }
        return false;
    }
protected:
//变化点接口
    virtual bool HandleRequest(const Context &ctx) = 0;
    virtual bool CanHandle(const Context &ctx) =0;
    IHandler * GetNextHandler() {
        return next;
    }
private:
    IHandler *next;
};

// 能不能处理,以及怎么处理
class HandleByMainProgram : public IHandler {//主程处理
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 10)
            return true;
        return false;
    }
};

class HandleByProjMgr : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 20)
            return true;
        return false;
    }
};
class HandleByBoss : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day < 30)
            return true;
        return false;
    }
};

class HandleByBeauty : public IHandler {
protected:
    virtual bool HandleRequest(const Context &ctx){
        //
        return true;
    }
    virtual bool CanHandle(const Context &ctx) {
        //
        if (ctx.day <= 3)
            return true;
        return false;
    }
};

int main() {
    // IHandler * h1 = new HandleByMainProgram();
    // IHandler * h2 = new HandleByProjMgr();
    // IHandler * h3 = new HandleByBoss();
    // h1->SetNextHandler(h2);
    // h2->SetNextHandler(h3);
// 抽象工厂
// nginx http 处理 ,也用的责任链
    IHandler * h0 = new HandleByBeauty();
    IHandler * h1 = new HandleByMainProgram();
    IHandler * h2 = new HandleByProjMgr();
    IHandler * h3 = new HandleByBoss();
    h0->SetNextHandler(h1);
    h1->SetNextHandler(h2);
    h2->SetNextHandler(h3);
    // 设置下一指针 
    Context ctx;
    h0->Handle(ctx);
    
    return 0;
}

例如我们的http协议的处理流程,就可以使用责任链模式

装饰器

定义
动态地给一个对象增加一些额外的职责。就增加功能而言,装饰器模式比生产子类更为灵活。
—— 《设计模式》GoF
背景
普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖
金,同时可能针对不同的职位产生不同的奖金组合;
稳定点是扩展流程
变化点就是待遇的多少
要点
通过采用组合而非继承的手法, 装饰器模式实现了在运行时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。 避免了使用继承带来的“灵活性差”和“多子类衍生问题”。
不是解决“多子类衍生问题”问题,而是解决“父类在多个方向上的扩展功能”问题;
装饰器模式把一系列复杂的功能分散到每个装饰器当中,一般一个装饰器只实现一个功能,实现复用装饰器的功能;
本质
动态组合

#include 
// 普通员工有销售奖金,累计奖金,部门经理除此之外还有团队奖金;后面可能会添加环比增长奖金,同时可能产生不同的奖金组合;
// 销售奖金 = 当月销售额 * 4%
// 累计奖金 = 总的回款额 * 0.2%
// 部门奖金 = 团队销售额 * 1%
// 环比奖金 = (当月销售额-上月销售额) * 1%
// 销售后面的参数可能会调整
using namespace std;
class Context {
public:
    bool isMgr;
    // User user;
    // double groupsale;
};


class CalcBonus {    
public:
    CalcBonus(CalcBonus * c = nullptr) : cc(c) {}
    virtual double Calc(Context &ctx) {
        return 0.0; // 基本工资
    }
    virtual ~CalcBonus() {}

protected:
    CalcBonus* cc;
};

class CalcMonthBonus : public CalcBonus {
public:
    CalcMonthBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double mbonus /*= 计算流程忽略*/; 
        return mbonus + cc->Calc(ctx);
    }
};

class CalcSumBonus : public CalcBonus {
public:
    CalcSumBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double sbonus /*= 计算流程忽略*/; 
        return sbonus + cc->Calc(ctx);
    }
};

class CalcGroupBonus : public CalcBonus {
public:
    CalcGroupBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

class CalcCycleBonus : public CalcBonus {
public:
    CalcCycleBonus(CalcBonus * c) : CalcBonus(c) {}
    virtual double Calc(Context &ctx) {
        double gbnonus /*= 计算流程忽略*/; 
        return gbnonus + cc->Calc(ctx);
    }
};

int main() {
    // 1. 普通员工
    Context ctx1;
    CalcBonus *base = new CalcBonus();
    CalcBonus *cb2 = new CalcSumBonus(base);
    CalcBonus *cb1 = new CalcMonthBonus(cb2);


    cb2->Calc(ctx1);
    // 2. 部门经理
    Context ctx2;
    CalcBonus *cb3 = new CalcGroupBonus(cb2);
    cb3->Calc(ctx2);
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1324083.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-12
下一篇 2022-06-12

发表评论

登录后才能评论

评论列表(0条)

保存