以下面代码为例
一、分解Shape.h
class Point{
public:
int x;
int y;
};
class Line{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
};
class Rect{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
};
//增加
class Circle{
public:
point center;//圆心
int r;//半径
Circle(const Point& c, int r){
this->center = c;
this->r = r;
}
};
MainForm.cpp
//这里写的伪代码 继承的Form不管他
class MainForm : public Form {
private:
Point p1;
Point p2;
//改变
vector lineVector;
vector rectVector;
//增加圆
vector circleVector;
public:
MainForm(){
//...
}
protected:
virtual void onMouseDown(const MouseEventArgs& e);
virtual void onMouseUp(const MouseEventArgs& e);
virtual void onPaint(const PaintEventArgs& e);
};
void MainForm::onMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::onMouseDown(e);
}
void MainForm::onMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
Line line(p1, p2);
lineVector.push_back(line);
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
Rect rect(p1, width, height);
rectVector.push_back(rect);
}
//增加
else if (rdoCircle.Checked){
Point center=p1;
int r=sqrt((p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y));
Circle circle(center,r);
circleVector.push_back(circle);
}
//...
this->Refresh();
Form::onMouseUp(e);
}
void MainForm::onPaint(const PaintEventArgs& e){
//画线
for (int i = 0; i < lineVector.size(); i++){
e.Graphics.DrawLine(Pens.Red,
lineVector[i].start.x,
lineVector[i].start.y,
lineVector[i].end.x,
lineVector[i].end.y);
}
//画矩阵
for (int i = 0; i < rectVector.size(); i++){
e.Graphics.DrawRectangle(Pens.Red,
rectVector[i].leftUp,
rectVector[i].width,
rectVector[i].height);
}
//憎加
//画圆
for (int i = 0; i < circleVector.size(); i++){
e.Graphics.DrawCircle(Pens.Red,
circleVector[i].center,
circleVector[i].r );
}
//...
Form::onPaint(e);
}
二、抽象
Shape.h
采用多态机制
class Shape{
public:
virtual void Draw(const Graphics& g)=0;
virtual ~Shape() { }
};
class Point{
public:
int x;
int y;
};
class Line: public Shape{
public:
Point start;
Point end;
Line(const Point& start, const Point& end){
this->start = start;
this->end = end;
}
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
g.DrawLine(Pens.Red,
start.x, start.y,end.x, end.y);
}
};
class Rect: public Shape{
public:
Point leftUp;
int width;
int height;
Rect(const Point& leftUp, int width, int height){
this->leftUp = leftUp;
this->width = width;
this->height = height;
}
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
g.DrawRectangle(Pens.Red,
leftUp,width,height);
}
};
//增加圆的数据结构
class Circle : public Shape{
public:
point center;//圆心
int r;//半径
Circle(const Point& c, int r){
this->center = c;
this->r = r;
}
//实现自己的Draw,负责画自己
virtual void Draw(const Graphics& g){
g.DrawCircle(Pens.Red,
center,r);
}
};
MainForm.cpp
class MainForm : public Form {
private:
Point p1;
Point p2;
//针对所有形状 统一处理
vector shapeVector;// shape*指针 是因为动态的机制
public:
MainForm(){
//...
}
protected:
virtual void onMouseDown(const MouseEventArgs& e);
virtual void onMouseUp(const MouseEventArgs& e);
virtual void onPaint(const PaintEventArgs& e);
};
void MainForm::onMouseDown(const MouseEventArgs& e){
p1.x = e.X;
p1.y = e.Y;
//...
Form::onMouseDown(e);
}
void MainForm::onMouseUp(const MouseEventArgs& e){
p2.x = e.X;
p2.y = e.Y;
if (rdoLine.Checked){
shapeVector.push_back(new Line(p1,p2));// new 一个堆指针
}
else if (rdoRect.Checked){
int width = abs(p2.x - p1.x);
int height = abs(p2.y - p1.y);
shapeVector.push_back(new Rect(p1, width, height));
}
//增加
else if (...){
Point center=p1;
int r=sqrt((p2.x-p1.x)*(p2.x-p1.x) + (p2.y-p1.y)*(p2.y-p1.y));
Circle circle(center,r);
shapeVector.push_back(circle);
}
//...
this->Refresh();
Form::onMouseUp(e);
}
void MainForm::onPaint(const PaintEventArgs& e){
for (int i = 0; i < shapeVector.size(); i++){
shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责 是line就调用line的Draw 是Rect就调用Rect 的Draw
}
//...
Form::onPaint(e);
}
面向对象设计原则
这个其实就是扩展
一个类不能乱指向
子类继承父类,那肯定是有原因的
不要把不必要的方法public出去,如果只是子类用就protected ,如果是本类用就private
对象组合 比如就是 class a里面放一个 class b
继承 子类父类耦合度过高 就是 父类给子类暴露了太多东西
封装变化点 一侧变化 一侧稳定
这里放的就是具体类型,违背面向对象原则
而这个则就是给了一个抽象接口,符合原则。
其实软件的设计也是借鉴其他行业的原则!!!
模板方法Template Method 早绑定这里的方法没有采用template_method
template_lib.cpp
//程序库开发人员
class Library{
public:
void Step1(){
//...
}
void Step3(){
//...
}
void Step5(){
//...
}
};
template_app.cpp
//应用程序开发人员
class Application{
public:
bool Step2(){
//...
}
void Step4(){
//...
}
};
int main()
{
//模拟程序流程
Library lib();
Application app();
lib.Step1();
if (app.Step2()){
lib.Step3();
}
for (int i = 0; i < 4; i++){
app.Step4();
}
lib.Step5();
}
一个晚的东西调用早的东西就是早绑定
晚绑定采用 template method 的设计模式—前提是你要有稳定的骨架
程序主流程相对稳定
template_lib.cpp
//程序库开发人员
class Library{
public:
//稳定 template method
void Run(){
Step1();
if (Step2()) { //支持变化 ==> 虚函数的多态调用
Step3();
}
for (int i = 0; i < 4; i++){
Step4(); //支持变化 ==> 虚函数的多态调用
}
Step5();
}
virtual ~Library(){ }//基类的析构写成虚的 因为如果你 new一个子类出来 你要delete子类的话 如果基类的虚构没有写成虚的,可能子类不一定能调用到自己的析构
protected:
void Step1() { //稳定
//.....
}
void Step3() {//稳定
//.....
}
void Step5() { //稳定
//.....
}
//虚函数支持变化
virtual bool Step2() = 0;//变化
virtual void Step4() =0; //变化
};
template_app.cpp
//应用程序开发人员
//继承库 来进行开发
class Application : public Library {
protected:
virtual bool Step2(){
//... 子类重写实现
}
virtual void Step4() {
//... 子类重写实现
}
};
int main()
{
//多态指针
Library* pLib=new Application();
lib->Run();//这里依然会根据流程来走
delete pLib;//
}
}
早的东西调用晚的东西就是晚绑定
红色的代表稳定
蓝色的代表不稳定
策略模式 违背开闭原则enum Taxbase {
CN_Tax,
US_Tax,
DE_Tax,
FR_Tax //更改 违背开闭原则
};
class SalesOrder{
Taxbase tax;
public:
double CalculateTax(){
//...
if (tax == CN_Tax){
//CN***********
}
else if (tax == US_Tax){
//US***********
}
else if (tax == DE_Tax){
//DE***********
}
else if (tax == FR_Tax){ //更改
//...
}
//....
}
};
这里要改动的东西多,同时if else 也消耗性能
符合开闭原则策略模式
class TaxStrategy{
public:
virtual double Calculate(const Context& context)=0;
virtual ~TaxStrategy(){}
};
class CNTax : public TaxStrategy{
public:
virtual double Calculate(const Context& context){
/
class House{
//....
};
class HouseBuilder {
public:
House* GetResult(){//获得house指针
return pHouse;
}
virtual ~HouseBuilder(){}
protected:
House* pHouse;
virtual void BuildPart1()=0;
virtual void BuildPart2()=0;
virtual void BuildPart3()=0;
virtual void BuildPart4()=0;
virtual void BuildPart5()=0;
};
class StoneHouse: public House{
};
class StoneHouseBuilder: public HouseBuilder{
protected:
virtual void BuildPart1(){
//pHouse->Part1 = ...;//这里面用到house里面的东西
}
virtual void BuildPart2(){
}
virtual void BuildPart3(){
}
virtual void BuildPart4(){
}
virtual void BuildPart5(){
}
};
class HouseDirector{
public:
HouseBuilder* pHouseBuilder;
HouseDirector(HouseBuilder* pHouseBuilder){
this->pHouseBuilder=pHouseBuilder;
}
House* Construct(){
pHouseBuilder->BuildPart1();
for (int i = 0; i < 4; i++){
pHouseBuilder->BuildPart2();
}
bool flag=pHouseBuilder->BuildPart3();
if(flag){
pHouseBuilder->BuildPart4();
}
pHouseBuilder->BuildPart5();
return pHouseBuilder->GetResult();
}
};
红色的代表稳定,蓝色的代表变化
这里面就是指
HouseBuilder HouseDirector稳定
StoneHouseBuilder等具体的子类是变化的
单件模式
class Singleton{
private://这样写就是外部无法使用默认构造
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
};
Singleton* Singleton::m_instance=nullptr;
//线程非安全版本
Singleton* Singleton::getInstance() {
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;//保证唯一
}
//线程安全版本,但锁的代价过高---因为对于都是读操作的线程是浪费的。
//就是只有35行执行结束了,才会释放锁,其他线程才能到32行 这时候m_instance已经不是空了
Singleton* Singleton::getInstance() {
Lock lock;
if (m_instance == nullptr) {
m_instance = new Singleton();
}
return m_instance;
}
//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
if(m_instance==nullptr){//锁前检查避免读操作代价过高的问题
Lock lock;
if (m_instance == nullptr) {//双检查
m_instance = new Singleton();//默认的顺序:先分配内存 再调用构造器,再把地址赋值给m_instance
//reorder之后顺序有可能为:先分配内存 ,把地址赋值给m_instance,再调用构造器 这样就导致读存在问题 因为只是分配了原生的内存,没有执行构造器
}
}
return m_instance;
}
//C++ 11版本之后的跨平台实现 (volatile)
std::atomic Singleton::m_instance;//原子对象
std::mutex Singleton::m_mutex;
Singleton* Singleton::getInstance() {
Singleton* tmp = m_instance.load(std::memory_order_relaxed);//tmp这个指针乐意屏蔽编译器的reorder
std::atomic_thread_fence(std::memory_order_acquire);//内存fence (内存的屏障 内存reorder的保护)
//下面的tmp就不会被reorder了
if (tmp == nullptr) {
std::lock_guard lock(m_mutex);//上锁
tmp = m_instance.load(std::memory_order_relaxed);//1内存
if (tmp == nullptr) {//
tmp = new Singleton;//2 构造 不会被reorder一定是先执行它 再执行构造器,再把值返回
std::atomic_thread_fence(std::memory_order_release);//释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);//3返回到m_instance
}
}
return tmp;
}
享元模式
class Font {
private:
//unique object key
string key;
//object state
//....
public:
Font(const string& key){
//...
}
};
ß
class FontFactory{//共享 把要用的对象放在一个共享池里
private:
map fontPool;
public:
Font* GetFont(const string& key){
map::iterator item=fontPool.find(key);
if(item!=footPool.end()){
return fontPool[key];
}
else{
Font* font = new Font(key);
fontPool[key]= font;
return font;
}
}
void clear(){
//...
}
};
门面模式
以数据库为例
红色的部分 是稳定的,比如返回数据库对象啊什么的
蓝色部分改变的,比如数据表结构,数据连接方式
代理模式client.cpp
没有使用代理模式
class ISubject{
public:
virtual void process();
};
class RealSubject: public ISubject{
public:
virtual void process(){
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new RealSubject();
}
void DoTask(){
//...
subject->process();
//....
}
};
proxy.cpp
使用了代理模式
class ISubject{
public:
virtual void process();
};
//Proxy的设计
class SubjectProxy: public ISubject{
public:
virtual void process(){
//对RealSubject的一种间接访问 就是这里和上面的client.cpp有区别 这里面实际上会比上面的复杂很多 ,用SubjectProxy类进行隔离。
//....
}
};
class ClientApp{
ISubject* subject;
public:
ClientApp(){
subject=new SubjectProxy();
}
void DoTask(){
//...
subject->process();
//....
}
};
适配器
老的东西放在新的里面使用
//目标接口(新接口)
class ITarget{
public:
virtual void process()=0;
};
//遗留接口(老接口)
class IAdaptee{
public:
virtual void foo(int data)=0;
virtual int bar()=0;
};
//遗留类型
class OldClass: public IAdaptee{
//.... 重写IAdaptee里面的方法等操作
};
//对象适配器
class Adapter: public ITarget{ //继承
protected:
IAdaptee* pAdaptee;//组合
public:
Adapter(IAdaptee* pAdaptee){
this->pAdaptee=pAdaptee;
}
virtual void process(){
int data=pAdaptee->bar();
pAdaptee->foo(data);
}
};
//类适配器 没有灵活性
class Adapter: public ITarget,
protected OldClass{ //多继承 // protected 和private继承可以叫做实现继承 就是我没有继承你的接口,但是我用你的实现
}
int main(){
IAdaptee* pAdaptee=new OldClass();//旧类对象
ITarget* pTarget=new Adapter(pAdaptee);//旧类对象塞进adpater里面产生新类对象
pTarget->process();
}
//这些也是用到了adapter
class stack{
deqeue container;
};
class queue{
deqeue container;
};
中介者模式
状态模式
没有使用状态模式
cenum NetworkState
{
Network_Open,
Network_Close,
Network_Connect,
};
class NetworkProcessor{
NetworkState state;
public:
void Operation1(){
if (state == Network_Open){
//**********
state = Network_Close;
}
else if (state == Network_Close){
//..........
state = Network_Connect;
}
else if (state == Network_Connect){
//$$$$$$$$$$
state = Network_Open;
}
}
public void Operation2(){
if (state == Network_Open){
//**********
state = Network_Connect;
}
else if (state == Network_Close){
//.....
state = Network_Open;
}
else if (state == Network_Connect){
//$$$$$$$$$$
state = Network_Close;
}
}
public void Operation3(){
}
};
上面的代码采用了很多if else
使用状态模式class NetworkState{
public:
NetworkState* pNext;//指向状态改变后的状态
virtual void Operation1()=0;
virtual void Operation2()=0;
virtual void Operation3()=0;
virtual ~NetworkState(){}
};
//我们把各个不同state的operation都分别在不同的state类里面实现 这样程序的框架更加明显 也更方便后期代码的添加和修改
class OpenState :public NetworkState{
static NetworkState* m_instance;
public:
static NetworkState* getInstance(){
if (m_instance == nullptr) {
m_instance = new OpenState();
}
return m_instance;
}
void Operation1(){
//**********
pNext = CloseState::getInstance();
}
void Operation2(){
//..........
pNext = ConnectState::getInstance();
}
void Operation3(){
//$$$$$$$$$$
pNext = OpenState::getInstance();
}
};
class CloseState :public NetworkState{
static NetworkState* m_instance;
public:
static NetworkState* getInstance(){
if (m_instance == nullptr) {
m_instance = new CloseState();
}
return m_instance;
}
void Operation1(){
//**********
pNext = ConnectState::getInstance();
}
void Operation2(){
//..........
pNext = OpenState::getInstance();
}
void Operation3(){
//$$$$$$$$$$
}
};
//
class NetworkProcessor
{
NetworkState* pState;
public:
NetworkProcessor(NetworkState* pState)
{
this->pState=pState;
}
void Operation1()
{
//...
pState->Operation1();
pState=pState->pNext;
//...
}
void Operation2(){
//...
pState->Operation2();
pState = pState->pNext;
//...
}
void Operation3(){
//...
pState->Operation3();
pState = pState->pNext;
//...
}
};
红色的是稳定的
蓝色的是变化的
备忘录Memento
class Memento//备忘录
{
string state;
//..
public:
Memento(const string & s) : state(s) {}
string getState() const { return state; }
void setState(const string & s) { state = s; }
};
class Originator
{
string state;
//....
public:
Originator() {}
Memento createMomento() {
Memento m(state);
return m;
}
void setMomento(const Memento & m) {
state = m.getState();
}
};
int main()
{
Originator orginator;
//捕获对象状态,存储到备忘录
Memento mem = orginator.createMomento();//类似于深拷贝 把原来的状态拷贝到新的对象里
//... 改变orginator状态
//从备忘录中恢复
orginator.setMomento(mem);
}
组合模式
#include迭代器#include #include
#include using namespace std; class Component { public: virtual void process() = 0;//统一接口 virtual ~Component(){} }; //树节点 class Composite : public Component{ string name; list elements; //这里list里面有可能是 Composite 也有可能是 Leaf public: Composite(const string & s) : name(s) {} void add(Component* element) { elements.push_back(element); } void remove(Component* element){ elements.remove(element); } void process(){ //1. process current node //2. process leaf nodes for (auto &e : elements) e->process(); //多态调用 } }; //叶子节点 class Leaf : public Component{ string name; public: Leaf(string s) : name(s) {} void process(){ //process current node } }; //客户程序 void Invoke(Component & c){ //... c.process(); //... } int main() { Composite root("root"); Composite treeNode1("treeNode1"); Composite treeNode2("treeNode2"); Composite treeNode3("treeNode3"); Composite treeNode4("treeNode4"); Leaf leat1("left1"); Leaf leat2("left2"); root.add(&treeNode1); treeNode1.add(&treeNode2); treeNode2.add(&leaf1); root.add(&treeNode3); treeNode3.add(&treeNode4); treeNode4.add(&leaf2); process(root); process(leaf2); process(treeNode3); }
现在在c++里面来看 这个模式已经是落伍了 因为它是基于虚函数面向对象的迭代器 相比于stl 泛型编程基于模板的多态迭代器 效率要低。
template职责链class Iterator { public: virtual void first() = 0; virtual void next() = 0; virtual bool isDone() const = 0; virtual T& current() = 0; }; template class MyCollection{ public: Iterator GetIterator(){ //... } }; template class CollectionIterator : public Iterator { MyCollection mc; public: CollectionIterator(const MyCollection & c): mc(c){ } void first() override { } void next() override { } bool isDone() const override{ } T& current() override{ } }; void MyAlgorithm() { MyCollection mc; Iterator iter= mc.GetIterator(); for (iter.first(); !iter.isDone(); iter.next()){//虚函数是运行时多态 效率低于编译时多态(编译时已经把工作做了 就不需要运行的时候再计算函数地址了) cout << iter.current() << endl; } }
#include#include using namespace std; enum class RequestType { REQ_HANDLER1, REQ_HANDLER2, REQ_HANDLER3 }; // 请求类 class Reqest { string description; RequestType reqType; public: Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {} RequestType getReqType() const { return reqType; } const string& getDescription() const { return description; } }; //职责链 class ChainHandler{ ChainHandler *nextChain;//多态指针指向自身 形成链表 这样j void sendReqestTonextHandler(const Reqest & req) { if (nextChain != nullptr) nextChain->handle(req); } protected: virtual bool canHandleRequest(const Reqest & req) = 0;//判断能不能处理这个请求 virtual void processRequest(const Reqest & req) = 0;//具体处理请求 public: ChainHandler() { nextChain = nullptr; }//初始化 void setNextChain(ChainHandler *next) { nextChain = next; } void handle(const Reqest & req) { if (canHandleRequest(req)) processRequest(req); else sendReqestTonextHandler(req);//跳到下一个要处理 } }; //职责链中具体要处理的类 class Handler1 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER1; } void processRequest(const Reqest & req) override { cout << "Handler1 is handle reqest: " << req.getDescription() << endl; } }; class Handler2 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER2; } void processRequest(const Reqest & req) override { cout << "Handler2 is handle reqest: " << req.getDescription() << endl; } }; class Handler3 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER3; } void processRequest(const Reqest & req) override { cout << "Handler3 is handle reqest: " << req.getDescription() << endl; } }; int main(){ Handler1 h1; Handler2 h2; Handler3 h3; h1.setNextChain(&h2); h2.setNextChain(&h3); Reqest req("process task ... ", RequestType::REQ_HANDLER3); h1.handle(req); return 0; }
void next() override {
}
bool isDone() const override{
}
T& current() override{
}
};
void MyAlgorithm()
{
MyCollection mc;
Iteratoriter= mc.GetIterator(); for (iter.first(); !iter.isDone(); iter.next()){//虚函数是运行时多态 效率低于编译时多态(编译时已经把工作做了 就不需要运行的时候再计算函数地址了) cout << iter.current() << endl; }
}
[外链图片转存中...(img-fyKsXrfx-1633924054004)] [外链图片转存中...(img-CEbxcAeZ-1633924054004)] [外链图片转存中...(img-z2eQhKpq-1633924054004)] # 职责链 [外链图片转存中...(img-MLPjq5ZX-1633924054005)] [外链图片转存中...(img-CC0IPwVj-1633924054005)] ~~~c++ #include#include using namespace std; enum class RequestType { REQ_HANDLER1, REQ_HANDLER2, REQ_HANDLER3 }; // 请求类 class Reqest { string description; RequestType reqType; public: Reqest(const string & desc, RequestType type) : description(desc), reqType(type) {} RequestType getReqType() const { return reqType; } const string& getDescription() const { return description; } }; //职责链 class ChainHandler{ ChainHandler *nextChain;//多态指针指向自身 形成链表 这样j void sendReqestTonextHandler(const Reqest & req) { if (nextChain != nullptr) nextChain->handle(req); } protected: virtual bool canHandleRequest(const Reqest & req) = 0;//判断能不能处理这个请求 virtual void processRequest(const Reqest & req) = 0;//具体处理请求 public: ChainHandler() { nextChain = nullptr; }//初始化 void setNextChain(ChainHandler *next) { nextChain = next; } void handle(const Reqest & req) { if (canHandleRequest(req)) processRequest(req); else sendReqestTonextHandler(req);//跳到下一个要处理 } }; //职责链中具体要处理的类 class Handler1 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER1; } void processRequest(const Reqest & req) override { cout << "Handler1 is handle reqest: " << req.getDescription() << endl; } }; class Handler2 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER2; } void processRequest(const Reqest & req) override { cout << "Handler2 is handle reqest: " << req.getDescription() << endl; } }; class Handler3 : public ChainHandler{ protected: bool canHandleRequest(const Reqest & req) override { return req.getReqType() == RequestType::REQ_HANDLER3; } void processRequest(const Reqest & req) override { cout << "Handler3 is handle reqest: " << req.getDescription() << endl; } }; int main(){ Handler1 h1; Handler2 h2; Handler3 h3; h1.setNextChain(&h2); h2.setNextChain(&h3); Reqest req("process task ... ", RequestType::REQ_HANDLER3); h1.handle(req); return 0; }
[外链图片转存中…(img-mifDppys-1633924054005)]
[外链图片转存中…(img-0p6AM0Rn-1633924054006)]
[外链图片转存中…(img-Ax0WbJZd-1633924054006)]



