当前位置: 首页 > news >正文

C++实现高频设计模式

image.png
面试能说出这几种常用的设计模式即可

1.策略模式

1.1 业务场景

大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:

if(type=="A"){//按照A格式解析
}else if(type=="B"){//按照B格式解析
}else{//按照默认格式解析
}

存在问题

  • 如果分支变多,这里的代码就会变得臃肿,难以维护,可读性低
  • 如果你需要接入一种新的解析类型,那只能在原有代码上修改

以上代码,违背了面向对象编程的开闭原则以及单一原则

  • 开闭原则(对于扩展是开放的,但是对于修改是封闭的):增加或者删除某个逻辑,都需要修改到原来代码
  • 单一原则(规定一个类应该只有一个发生变化的原因):修改任何类型的分支逻辑代码,都需要改动当前类的代码。

如果你的代码有多个if…else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。

1.2 策略模式定义

策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。
大白话:
假设你跟不同性格类型的小姐姐约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去逛街买买买最合适。当然,目的都是为了得到小姐姐的芳心,请看电影、吃小吃、逛街就是不同的策略。
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。

1.3 策略模式使用

context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。
策略模式和工厂模式的不同之处:
工厂来生成算法对象,这没有错,但算法只是一种策略,最重要的是这些算法是随时间都可能互相替换的,这就是变化点,而封装变化就是面向对象的一个重要的思维方式,
在这里插入图片描述

1.4代码示例

#pragma once
#include <iostream>
using namespace std;class Strategy{public:virtual void algorithmInterface() = 0;virtual ~Strategy(){cout << "~Strategy() called!" << endl;}};class ConcreteStrategyA: public Strategy{public:void algorithmInterface(){cout << "arithmetic A is called!" << endl;}~ConcreteStrategyA(){cout << "~ConcreteStrategyA() called!" << endl;}
};class ConcreteStrategyB: public Strategy{public:void algorithmInterface(){cout << "arithmetic A is called!" << endl;}~ConcreteStrategyB(){cout << "~ConcreteStrategyB() called!" << endl;}
};class ConcreteStrategyC: public Strategy{public:void algorithmInterface(){cout << "arithmetic C is called!" << endl;}~ConcreteStrategyC(){cout << "~ConcreteStrategyC() called!" << endl;}
};class Context{public:Context(Strategy *strategy){m_strategy = strategy;}~Context(){delete m_strategy;}void contextInterface(){m_strategy->algorithmInterface();}private:Strategy *m_strategy;
};
#include "strategy.h" 
int main(){Context *contextA, *contextB, *contextC;//由于实例化不同的策略,所以最终在调用context->contextInterface()时,所获得的结果就不尽相同contextA = new Context(new ConcreteStrategyA);contextA->contextInterface();delete contextA;cout << endl << "----------**********----------" << endl;contextB = new Context(new ConcreteStrategyB);contextB->contextInterface();delete contextB;cout << endl << "----------**********----------" << endl;contextC = new Context(new ConcreteStrategyC);contextC->contextInterface();delete contextC;getchar();return 0;
}

2. 责任链模式

2.1 业务场景

请假是我们日常生活中经常遇到的事,一般请假按请的时间长短需要跟不同级别的管理者请,就是请假这个请求根据时间长短可由不同的处理者处理,非常适合责任链模式。

场景:不同的请求需要不同权限的对象来处理的情况。

2.2 模式结构

image.png

2.3 责任链模式定义

当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式
责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:
在这里插入图片描述

大白话:
假设你晚上去上选修课,坐到了最后一排。来到教室,发现前面坐了好几个漂亮的小姐姐,于是你找张纸条,写上:“你好, 可以做我的女朋友吗?如果不愿意请向前传”。纸条就一个接一个的传上去了,后来传到第一排的那个妹子手上。

2.4代码示例

#include <iostream>
using namespace std;//抽象处理者
class Handler{
public:Handler() { m_pNextHandler = NULL; }virtual ~Handler() {}//设置下一个处理者void SetNextHandler(Handler *successor) { m_pNextHandler = successor; }//处理请求virtual void HandleRequest(int days) = 0;
protected:Handler *m_pNextHandler;  // 后继者
};//具体处理者、主管
class Director :public Handler{
public://处理请求virtual void HandleRequest(int days){if (days <= 1){cout << "我是主管,有权批准一天假,同意了!" << endl; }else{m_pNextHandler->HandleRequest(days);}}
};//具体处理者、经理
class Manager :public Handler{
public://处理请求virtual void HandleRequest(int days){if (days <= 3){cout << "我是经理,有权批准三以下的假,同意了!" << endl;}else{m_pNextHandler->HandleRequest(days);}}
};//具体处理者、老板
class Boss :public Handler{
public://处理请求virtual void HandleRequest(int days){if (days <= 7){cout << "我是老板,最多让你请7天假,同意了!" << endl;}else{cout << "你请的假事假太长了,不同意!" << endl;}}
};//场景
int main(){Handler *director = new Director;Handler *manager = new Manager;Handler *boss = new Boss;//设置责任链director->SetNextHandler(manager);manager->SetNextHandler(boss);director->HandleRequest(1);director->HandleRequest(2);director->HandleRequest(5);director->HandleRequest(8);return 0;
}/*输出:
我是主管,有权批准一天假,同意了!
我是经理,有权批准三以下的假,同意了
我是老板,最多让你请7天假,同意了!
你请的假事假太长了,不同意!*/

2.5优缺点

优点

单一职责原则。 你可对发起操作和执行操作的类进行解耦。
开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。

缺点

每个请求都从头到尾遍历。
如果建链不当,可能会造成循环调用,这将导致系统陷入死循环

3.模板方法模式

3.1 适合场景

假设有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)。走类似这么一个流程,如下:
image.png
一个请求都会经历这几个流程:

  • 查询商户信息
  • 对请求报文加签
  • 发送http请求出去
  • 对返回的报文验签

这里,有的商户可能是走代理出去的,有的是走直连。

3.2 定义

定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。
image.png模板方法模式是通过把不变行为搬移到超类中,去除子类中的重复代码来提现他的优势。
模板方法模式提供了一个很好的代码复用。
当不变和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复不变行为的纠缠。

3.3 代码示例

#ifndef HEAD_H
#define HEAD_Hclass Fundamental{
public:virtual void primitiveOperation1();virtual void primitiveOperation2();void templateMethod();virtual ~Fundamental();
};class ConcreteClassA: public Fundamental{
public:void primitiveOperation1();void primitiveOperation2();virtual ~ConcreteClassA();
};class ConcreteClassB: public Fundamental{
public:void primitiveOperation1();void primitiveOperation2();virtual ~ConcreteClassB();
};#endif	//HEAD_H
#include "Head.h"
#include <iostream>
#include <string>
using namespace std;void Fundamental::primitiveOperation1(){cout << "Fundamental::primitiveOperation1()" << endl;
}void Fundamental::primitiveOperation2(){cout << "Fundamental::primitiveOperation1()" << endl;
}void Fundamental::templateMethod(){this->primitiveOperation1();this->primitiveOperation2();
}Fundamental::~Fundamental(){cout << "Fundamental::~Fundamental()" << endl;
}void ConcreteClassA::primitiveOperation1(){cout << "ConcreteClassA::primitiveOperation1()" << endl;
}void ConcreteClassA::primitiveOperation2(){cout << "ConcreteClassA::primitiveOperation1()" << endl;
}ConcreteClassA::~ConcreteClassA(){cout << "ConcreteClassA::~ConcreteClassA()" << endl;
}void ConcreteClassB::primitiveOperation1(){cout << "ConcreteClassB::primitiveOperation1()" << endl;
}void ConcreteClassB::primitiveOperation2(){cout << "ConcreteClassB::primitiveOperation2()" << endl;
}ConcreteClassB::~ConcreteClassB(){cout << "ConcreteClassB::~ConcreteClassB()" << endl;
}
#include "Head.h"
#include <iostream>
#include <string>
using namespace std;int main(){  Fundamental *fundamental = new ConcreteClassA;fundamental->templateMethod();delete fundamental;cout << "-------------- 华丽的分割线 --------------" << endl;Fundamental *concrete = new ConcreteClassB;concrete->templateMethod();delete concrete;getchar();return 0;
}

4. 观察者模式

4.1 适合场景

登陆注册应该是最常见的业务场景了。就拿注册来说事,我们经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条消息,又或者发个邮件等等,因此经常有如下的代码:

void register(User user){insertRegisterUser(user);sendIMMessage();sendEmail();
}

这块代码会有什么问题呢?如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。

void register(User user){insertRegisterUser(user);sendIMMessage();sendMobileMessage();sendEmail();
}

并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法给通知消息才好。可以使用观察者模式优化。
其它场景:
1)服务器发布新版本,让客户端更新。
2)游戏群发邮件给补贴等。
3)聊天室系统。

4.2 观察者模式定义

观察者模式又叫做发布-订阅模式;
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新自己。
观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者

  • 被观察者(Observerable):目标对象,状态发生变化时,将通知所有的观察者。
  • 观察者(observer):接受被观察者的状态变化通知,执行预先定义的业务。

使用场景: 完成某件事情后,异步通知场景。如登陆成功
在这里插入图片描述

4.3代码示例

#ifndef HEAD_H
#define HEAD_H#include <iostream>
#include <string>
#include <list>
using namespace std;class Observer;class Subject{
public:void attach(Observer *observer);void detach(Observer *observer);void notify();~Subject();private:list<Observer*> observers;
};class Observer{
public:virtual void update(){}virtual string getName(){ return ""; }
};class ConcreteSubject : public Subject{
public:string getSubjectState();void setSubjectState(const string &str);private:string subjectState;
};class ConcreteObserver : public Observer{
public:ConcreteObserver(ConcreteSubject *subject, string name);void update();string getName();private:string name;string observerState;ConcreteSubject *subject;
};#endif //HEAD_H
#include "Head.h"void Subject::attach(Observer *observer){observers.push_back(observer);
}void Subject::detach(Observer *observer){observers.remove(observer);
}void Subject::notify(){list<Observer*>::iterator it = observers.begin();while(it != observers.end()){(*it)->update();it++;}
}Subject::~Subject(){cout << "开始析构了" << endl;list<Observer*>::iterator it = observers.begin();while(it != observers.end()){cout << "开始删除: " << (*it)->getName() << endl;delete *it;it++;}observers.clear();
}std::string ConcreteSubject::getSubjectState(){return subjectState;
}void ConcreteSubject::setSubjectState(const string &str){subjectState = str;
}ConcreteObserver::ConcreteObserver(ConcreteSubject *subject, string name){this->subject = subject;this->name = name;
}void ConcreteObserver::update(){observerState = subject->getSubjectState();cout << "发布者更新东西了 !!! 订阅者:" << name << "  状态:" << observerState << endl; 
}std::string ConcreteObserver::getName(){return name;
}
#include "Head.h"
int main(int *argc, int *argv[]){ConcreteSubject *subject = new ConcreteSubject;subject->attach(new ConcreteObserver(subject, "第一个粉丝"));subject->attach(new ConcreteObserver(subject, "第二个粉丝"));subject->attach(new ConcreteObserver(subject, "第三个粉丝"));subject->setSubjectState("Hello");subject->notify();cout << "----------- 华 丽 的 分 割 线 -----------" << endl;subject->attach(new ConcreteObserver(subject, "王二麻子"));subject->setSubjectState("呵呵");subject->notify();cout << endl;delete subject;getchar();return 0;
}

5. 工厂模式

5.1 业务场景

工厂模式一般配合策略模式一起使用。用来去优化大量的if…else…或switch…case…条件语句。

在程序中,需要创建的对象很多,导致对象的new操作多且杂时,需要使用简单工厂模式;
由于对象的创建过程是我们不需要去关心的,而我们注重的是对象的实际操作,所以,我们需要分离对象的创建和操作两部分,如此,方便后期的程序扩展和维护。

5.2定义

简单工厂模式(Simple Factory Pattern)专门定义一个类来负责创建其他类的实例,被创建的实例通常具有共同的父类。
是一种实例化对象的方式,只要输入需要实例化对象的名字,就可以通过工厂对象的相应工厂函数来制造你需要的对象。

5.3优缺点

优点:

本着高内聚低耦合的原则,将系统的逻辑部分和功能分开。

缺点:

简单工厂模式会增加系统类的个数,在一定程度上增加了系统的复杂度和理解难度;
系统扩展难,一旦增加新产品,就需要修改工厂逻辑,不利于系统的扩展与维护;简单工厂模式中所有产品的创建都是由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间耦合度高,严重影响了系统的灵活性和扩展性。

5.4 示例场景

某电视机厂为各个品牌代工生产电视机,可以使用简单工厂的模式来实现。

5.5示例代码

#include <iostream>
#include <vector>
using namespace std;typedef enum ProductTypeTag{Hair,Hisense,
}PRODUCTTYPE;//抽象产品类 TV(电视机类)
class TV{
public:virtual void Show() = 0;virtual ~TV(){};//声明析构函数为虚函数,防止内存泄漏
};//具体产品类 HairTV(海尔电视类)
class HairTV : public TV{
public:void Show(){cout<<"I'm HairTV "<<endl;}
};//具体产品类 HisenseTV(海信电视类)  
class  HisenseTV : public TV{
public:void Show(){cout<<"I'm HisenseTV"<<endl;}
};// 工厂类 TVFactory(电视机工厂类)
class TVFactory{
public:TV* CreateTV(PRODUCTTYPE type){switch (type){case Hair:return new HairTV(); case Hisense:return new HisenseTV();default:return NULL;}}
};int main(int argc, char *argv[]){// 创建工厂类对象TVFactory* myTVFactory = new  TVFactory();TV* hairTV = myTVFactory->CreateTV(Hair);if (hairTV != NULL)hairTV->Show();TV* hisenseTV = myTVFactory->CreateTV(Hisense);if (hisenseTV != NULL)hisenseTV->Show();delete  myTVFactory;myTVFactory = NULL;delete hairTV;hairTV = NULL;delete hisenseTV;hisenseTV = NULL;  return 0;
}

6. 单例模式

6.1 业务场景

单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。

6.2定义

全局有且只有一个类的static实例,在程序任何地方都能够调用到。比如游戏客户端的本地Excel的加载,我们都会格式化成json。

一个好的单例应该具备下面4点

  • 1.全局只有一个实例:static 特性,同时禁止用户自己声明并定义实例(把构造函数设为 private)
  • 2.线程安全
  • 3.禁止赋值和拷贝
  • 4.用户通过接口获取实例:使用 static 类成员函数

6.3 单例模式的经典写法

6.2.1 汉模式

懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用Instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。如果单线程没有问题,当多线程的时候就会出现不可靠的情况。

#include <iostream>
using namespace std;/*
*	版本1 SingletonPattern_V1 存在以下两个问题
*	
*	1. 线程不安全, 非线程安全版本
*	2. 内存泄露
*/
class SingletonPattern_V1{
private:SingletonPattern_V1() {cout << "constructor called!" << endl;}SingletonPattern_V1(SingletonPattern_V1&) = delete;SingletonPattern_V1& operator=(const SingletonPattern_V1&) = delete;static SingletonPattern_V1* m_pInstance;public:~SingletonPattern_V1() {cout << "destructor called!" << endl;}//在这里实例化static SingletonPattern_V1* Instance() {if (!m_pInstance) {m_pInstance = new SingletonPattern_V1();}return m_pInstance;}void use() const { cout << "in use" << endl; }
};//在类外初始化静态变量
SingletonPattern_V1* SingletonPattern_V1::m_pInstance = nullptr;//函数入口
int main(){//测试SingletonPattern_V1* p1 = SingletonPattern_V1::Instance();SingletonPattern_V1* p2 = SingletonPattern_V1::Instance();return 0;
}
存在的问题

获取了两次类的实例,构造函数被调用一次,表明只生成了唯一实例
1)线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_pInstance是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_pInstance还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来;
解决办法:加锁
2)内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。
**解决办法1:**当然我们自己手动调用delete来进行释放是可以的,但是维护在何处释放又成了问题。
解决办法2: 使用共享指针;

** 线程安全、内存安全的懒汉式单例 **
#include <iostream>
using namespace std;
#include <memory> // C++11 shared_ptr头文件
#include <mutex>  // C++11 mutex头文件
/*
*	版本2 SingletonPattern_V2 解决了V1中的问题
*
*	1. 通过加锁让线程安全了
*	2. 通过智能指针(shareptr 基于引用计数)内存没有泄露了
*/
class SingletonPattern_V2 
{
public:~SingletonPattern_V2() {std::cout << "destructor called!" << std::endl;}SingletonPattern_V2(SingletonPattern_V2&) = delete;SingletonPattern_V2& operator=(const SingletonPattern_V2&) = delete;//在这里实例化static std::shared_ptr<SingletonPattern_V2> Instance() {//双重检查锁if (m_pInstance == nullptr) {std::lock_guard<std::mutex> lk(m_mutex);if (m_pInstance == nullptr) {m_pInstance = std::shared_ptr<SingletonPattern_V2>(new SingletonPattern_V2());}}return m_pInstance;}private:SingletonPattern_V2() {std::cout << "constructor called!" << std::endl;}static std::shared_ptr<SingletonPattern_V2> m_pInstance;static std::mutex m_mutex;
};//在类外初始化静态变量
std::shared_ptr<SingletonPattern_V2> SingletonPattern_V2::m_pInstance = nullptr;
std::mutex SingletonPattern_V2::m_mutex;int main(){std::shared_ptr<SingletonPattern_V2> p1 = SingletonPattern_V2::Instance();std::shared_ptr<SingletonPattern_V2> p2 = SingletonPattern_V2::Instance();return 0;
}
优缺点
优点

1)基于 shared_ptr,内部实现的是基于引用计数的智能指针,每次实例被赋值或者拷贝,都会引用+1,在内部的析构中判断引用计数为0的时候会调用真正的delete。用了C++比较倡导的 RAII思想,用对象管理资源,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉。以此避免内存泄漏。
2)加了锁,使用互斥锁来达到线程安全。这里使用了两个 if判断语句的技术称为双重检测锁;好处是,只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,锁的开销毕竟还是有点大的。

缺点

使用智能指针会要求外部调用也得使用智能指针,就算用个typedef也是一长串代码不好维护且不美观。非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实际上设计最简单的才是最好的。

最推荐的懒汉式单例(magic static)——局部静态变量-记这个
#include <iostream>
using namespace std;
/*
*	版本3 SingletonPattern_V3 使用局部静态变量 解决了V2中使用智能指针和锁的问题
*
*	1. 代码简洁 无智能指针调用
*	2. 也没有双重检查锁定模式的风险
*/
class SingletonPattern_V3{
public:~SingletonPattern_V3() {std::cout << "destructor called!" << std::endl;}SingletonPattern_V3(const SingletonPattern_V3&) = delete;SingletonPattern_V3& operator=(const SingletonPattern_V3&) = delete;static SingletonPattern_V3& Instance() {static SingletonPattern_V3 m_pInstance;return m_pInstance;}
private:SingletonPattern_V3() {std::cout << "constructor called!" << std::endl;}
};int main(){SingletonPattern_V3& instance_1 = SingletonPattern_V3::Instance();SingletonPattern_V3& instance_2 = SingletonPattern_V3::Instance();return 0;
}

这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。

最推荐的一种单例实现方式:

  1. 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
  2. 不需要使用共享指针,代码简洁;不需要使用互斥锁。
  3. 注意在使用的时候需要声明单例的引用 SingletonPattern_V3& 才能获取对象

6.2.2 饿汉模式

比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。

设计模式分类

创建型模式(Creational Patterns)

这些模式关注对象的创建过程,以确保系统在创建对象时更灵活、更高效。

  1. 单例模式(Singleton Pattern)
  2. 工厂方法模式(Factory Method Pattern)
  3. 抽象工厂模式(Abstract Factory Pattern)
  4. 建造者模式(Builder Pattern)
  5. 原型模式(Prototype Pattern)

结构型模式(Structural Patterns):

这些模式关注对象组合的方式,以实现更大的结构。

  1. 适配器模式(Adapter Pattern)
  2. 装饰器模式(Decorator Pattern)
  3. 代理模式(Proxy Pattern)
  4. 外观模式(Facade Pattern)
  5. 桥接模式(Bridge Pattern)
  6. 组合模式(Composite Pattern)
  7. 享元模式(Flyweight Patter)

行为型模式(Behavioral Patterns)

这些模式关注对象之间的通信、交互和分配职责。

  1. 策略模式(Strategy Pattern)
  2. 模板方法模式(Template Method Pattern)
  3. 观察者模式(Observer Pattern):发布订阅
  4. 迭代器模式(Iterator Pattern)
  5. 责任链模式(Chain of Responsibility Pattern)
  6. 命令模式(Command Pattern)
  7. 备忘录模式(Memento Pattern)
  8. 状态模式(State Pattern)
  9. 访问者模式(Visitor Pattern)
  10. 中介者模式(Mediator Pattern)
  11. 解释器模式(Interpreter Pattern)

设计模式原则

依赖倒置原则(Dependency Inversion Principle)

“要依赖于抽象,不要依赖于具体,针对接口编程,不要针对实现编程 "
它强调了模块间的松耦合和可扩展性。该原则的核心思想是:

  1. 高层模块不应该依赖于低层模块,两者都应该依赖于抽象接口。
  2. 抽象接口不应该依赖于具体实现细节,具体实现细节应该依赖于抽象接口。
    模块间的依赖关系应该通过抽象接口进行,而不是直接依赖于具体的实现。这样可以实现模块之间的解耦,使得系统更加灵活、可扩展和可维护。
    通过针对接口编程,我们可以定义抽象的接口,高层模块只依赖于这些抽象接口,而不需要关心具体的实现细节。这样,在需要替换具体实现或引入新的实现时,只需要保证实现接口的兼容性,而不需要修改高层模块的代码。
    这种方式也促进了代码的可测试性,因为我们可以轻松地通过使用模拟对象或者依赖注入来进行单元测试,而不需要依赖于具体的实现。
    总之,依赖倒置原则强调了面向抽象编程的重要性,通过解耦和抽象,提高系统的灵活性、可扩展性和可维护性。

开闭原则(Open-Closed Principle)

定义:“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”
开闭原则强调了在设计和编写代码时,应该通过扩展来增加功能,而不是通过修改已有的代码来实现功能的变化。它促使我们设计出更加稳定、灵活、可扩展和可维护的软件系统。
开闭原则的核心思想是通过抽象和多态来实现可扩展性。具体来说,可以通过以下方法来满足开闭原则:

  1. 使用抽象来定义可扩展的接口或基类,以便在不修改现有代码的情况下进行扩展。
  2. 通过多态实现具体实现的替换,以便在运行时选择不同的实现,而不需要修改调用方的代码。
  3. 使用策略模式、工厂模式等设计模式来实现开闭原则。
    遵循开闭原则的好处包括:
  • 更好的可维护性:通过扩展而不是修改已有代码,减少了引入错误和破坏现有功能的风险。
  • 更高的可复用性:通过抽象和多态,可以将通用的功能封装为可重用的模块或组件。
  • 更好的可扩展性:通过扩展接口或基类,并实现新的具体实现,可以灵活地增加新的功能。
    开闭原则是面向对象设计中的一个重要指导原则,它强调通过扩展而不是修改来实现功能变化,以提高系统的可维护性、可复用性和可扩展性。

接口隔离原则(Interface Segregation Principle)

接口隔离原则的定义是:“一个类对其他类的依赖应该建立在最小的接口上。”
接口隔离原则的目标是设计精简、高内聚、低耦合的接口,避免不必要的接口依赖和接口膨胀,以提高系统的灵活性和可维护性。
接口隔离原则的关键思想是将庞大的接口拆分为更小、更具体的接口,以满足客户端的精确需求。这样,客户端只需依赖于它们所需的接口,而无需依赖于不相关或不需要的接口。
以下是遵循接口隔离原则的一些指导原则:

  1. 定义细粒度的接口:将大型接口拆分为更小的、更具体的接口,以适应不同的客户端需求。
  2. 接口应该精简而专注:接口应该只包含客户端所需的方法,不应该强迫客户端实现不需要的方法。
  3. 避免"胖接口"和"接口污染":避免在接口中定义过多的方法,以免给客户端带来不必要的负担和依赖。
  4. 根据实际需求进行接口拆分:根据具体的业务需求和使用场景,灵活地拆分接口,以满足客户端的需要。
    遵循接口隔离原则的好处包括:
  • 减少接口间的依赖关系,降低耦合度。
  • 提高系统的可维护性和可扩展性。
  • 提高代码的复用性和可测试性。
  • 更好地支持单一职责原则,使接口和类的责任更加清晰。
    总之,接口隔离原则指导我们设计细粒度、专注、精简的接口,避免接口膨胀和不必要的接口依赖,以提高系统的灵活性和可维护性。

里氏替换原则(Liskov Substitution Principle)

它强调子类型必须能够替换其基类型,而不会引发程序的错误或异常。
定义是:“如果S是T的子类型,那么在所有T类型的程序中,对象可以被替换为S类型的对象,而不会影响程序的正确性。”
换句话说,子类对象应该能够在不破坏程序正确性的前提下替换其基类对象,且程序的行为不会产生意外或错误的结果。
里氏替换原则要求子类必须遵循其基类的约束和契约。即子类的方法必须遵循基类的方法声明,不能缩小基类方法的前置条件(输入参数约束)和扩大基类方法的后置条件(输出结果约束)。也就是说,子类应该保持与基类相同的行为规范。
遵循里氏替换原则的好处包括:

  • 提高代码的可重用性和可扩展性:通过子类对象的替换,可以扩展系统的功能并保持代码的兼容性。
  • 降低代码的耦合性:通过基于抽象的设计,减少对具体实现的依赖,提高代码的灵活性和可维护性。
  • 保证系统的稳定性:子类的替换不会破坏系统的正确性,提供了更可靠的程序行为。
    然而,应该注意的是,里氏替换原则并不意味着子类完全不能修改或扩展基类的行为。子类可以通过方法重写和方法重载来添加新的功能或修改基类的行为,但必须保持替换的一致性和不破坏原有约束。
    总之,里氏替换原则是一种指导设计和编写代码的原则,通过保持子类对基类的替换能力,提高系统的可重用性、可扩展性和稳定性。

相关文章:

C++实现高频设计模式

面试能说出这几种常用的设计模式即可 1.策略模式 1.1 业务场景 大数据系统把文件推送过来&#xff0c;根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码&#xff1a; if(type"A"){//按照A格式解析 }else if(type"B"){//按照B格式解析 …...

opencv(2): 视频采集和录制

视频采集 相关API VideoCapture()cap.read()&#xff1a; 返回两个值&#xff0c;第一个参数&#xff0c;如果读到frame&#xff0c;返回 True. 第二个参数为相应的图像帧。cap.release() VideoCapture cv2.VideoCapture(0) 0 表示自动检测&#xff0c;如果在笔记本上运行&…...

SpringBoot+EasyExcel设置excel样式

方式一&#xff1a;使用注解方式设置样式 模板可通过HeadFontStyle、HeadStyle、ContentFontStyle、ContentStyle、HeadRowHeight ContentRowHeight等注解设置excel单元格样式&#xff1b; //字体样式及字体大小 HeadFontStyle(fontName "宋体",fontHeightInPoints…...

自定义View之Measure(二)

measure 用来测量 View 的宽和高&#xff0c;它的流程分为 View 的 measure 流程和 ViewGroup 的measure流程&#xff0c;只不过ViewGroup的measure流程除了要完成自己的测量&#xff0c;还要遍历地调用子元素的measure&#xff08;&#xff09;方法。 上一回说到performMeasur…...

SQL注入学习--GTFHub(布尔盲注+时间盲注+MySQL结构)

目录 布尔盲注 手工注入 笔记 Boolean注入 # 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 时间盲注 手工注入 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 MySQL结构 手工注入 sqlmap注入 笔记 union 联合注入&#xff0c;手工注入的一般步骤 …...

Kubernetes学习-概念2

参考&#xff1a;关于 cgroup v2 | Kubernetes 关于 cgroup v2 在 Linux 上&#xff0c;控制组约束分配给进程的资源。 kubelet 和底层容器运行时都需要对接 cgroup 来强制执行为 Pod 和容器管理资源&#xff0c; 这包括为容器化工作负载配置 CPU/内存请求和限制。 Linux 中…...

StyleGAN:彻底改变生成对抗网络的艺术

一、介绍 多年来&#xff0c;人工智能领域取得了显着的进步&#xff0c;其中最令人兴奋的领域之一是生成模型的发展。这些模型旨在生成与人类创作没有区别的内容&#xff0c;例如图像和文本。其中&#xff0c;StyleGAN&#xff08;即风格生成对抗网络&#xff09;因其创建高度逼…...

黑马程序员微服务第四天课程 分布式搜索引擎1

分布式搜索引擎01 – elasticsearch基础 0.学习目标 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; …...

向量以及矩阵

0.前言 好了那我们新的征程也即将开始&#xff0c;那么在此呢我也先啰嗦两句&#xff0c;本篇文章介绍数学基础的部分&#xff0c;因为个人精力有限我不可能没一字一句都讲得非常清楚明白&#xff0c;像矩阵乘法之类的一些基础知识我都是默认你会了&#xff08;还不会的同学推…...

9.程序的机器级代码表示,CISC和RISC

目录 一. x86汇遍语言基础&#xff08;Intel格式&#xff09; 二. AT&T格式汇编语言 三. 程序的机器级代码表示 &#xff08;1&#xff09;选择语句 &#xff08;2&#xff09;循环语句 &#xff08;3&#xff09;函数调用 1.函数调用命令 2.栈帧及其访问 3.栈帧的…...

《硅基物语.AI写作高手:从零开始用ChatGPT学会写作》《从零开始读懂相对论》

文章目录 《硅基物语.AI写作高手&#xff1a;从零开始用ChatGPT学会写作》内容简介核心精华使用ChatGPT可以高效搞定写作的好处如下 《从零开始读懂相对论》内容简介关键点书摘最后 《硅基物语.AI写作高手&#xff1a;从零开始用ChatGPT学会写作》 内容简介 本书从写作与ChatG…...

【2016年数据结构真题】

已知由n&#xff08;M>2&#xff09;个正整数构成的集合A{a<k<n},将其划分为两个不相交的子集A1 和A2&#xff0c;元素个数分别是n1和n2&#xff0c;A1和A2中的元素之和分别为S1和S2。设计一个尽可能高效的划分算法&#xff0c;满足|n1-n2|最小且|s1-s2|最大。要求…...

创作者等级终于升到4级了

写了两个月的文章&#xff0c;终于等到4级了。发文纪念一下&#xff1a;...

Games104现代游戏引擎笔记 面向数据编程与任务系统

Basics of Parallel Programming 并行编程的基础 核达到了上限&#xff0c;无法越做越快&#xff0c;只能通过更多的核来解决问题 Process 进程 有独立的存储单元&#xff0c;系统去管理&#xff0c;需要通过特殊机制去交换信息 Thread 线程 在进程之内&#xff0c;共享了内存…...

系列三、GC垃圾回收【总体概览】

一、GC垃圾回收【总体概览】 JVM进行GC时&#xff0c;并非每次都对上面的三个内存区域&#xff08;新生区、养老区、元空间/永久代&#xff09;一起回收&#xff0c;大部分回收的是新生区里边的垃圾&#xff0c;因此GC按照回收的区域又分为了两种类型&#xff0c;一种是发生在新…...

无线WiFi安全渗透与攻防(N.3)WPA破解-创建Hash-table加速并用Cowpatty破解

WPA破解-创建Hash-table加速并用Cowpatty破解 WPA破解-创建Hash-table加速并用Cowpatty破解1.Cowpatty 软件介绍2.渗透流程1.安装CoWPAtty2.抓握手包1.查看网卡2.开启监听模式3.扫描wifi4.抓握手包5.进行冲突模式攻击3.STA重新连接wifi4.渗透WPA wifi5.使用大字典破解3.hash-ta…...

golang 动态库

目录 1. golang 动态库2. golang 语言使用动态库、调用动态链接库2.1. Go 插件系统2.2. 动态加载的优劣2.3. Go 的插件系统&#xff1a;Plugin2.4. 插件开发原则2.4.1. 插件独立2.4.2. 使用接口类型作为边界2.4.3. Unix 模块化原则2.4.4. 版本控制 2.5. 插件开发示例2.5.1. 编写…...

Python的2042小游戏及其详解

源码&#xff1a; import random import os# 游戏界面尺寸 SIZE 4# 游戏结束标志 GAME_OVER False# 初始化游戏界面 board [[0] * SIZE for _ in range(SIZE)]# 随机生成一个初始方块 def add_random_tile():empty_tiles [(i, j) for i in range(SIZE) for j in range(SIZ…...

怎么去掉邮件内容中的回车符

上图是Outlook 截图&#xff0c;可见1指向的总有回车符&#xff1b; 故障原因&#xff1a; 不小心误按了箭头4这个选项&#xff1b; 解决方法&#xff1a; 点击2箭头确保tab展开&#xff1b; 点击3以找到箭头4. 取消勾选或者多次点击&#xff0c;即可解决。...

Git-概念与架构

GIT-概念与架构 一、背景和起源二、版本控制系统1.版本控制分类1.1 集中式版本控制1.2 分布式版本控制 2.Git和SVN对比2.1 SVN2.2 GIT 三、GIT框架1.工作区&#xff08;working directory&#xff09;2.暂存区&#xff08;staging area&#xff09;3.本地仓库&#xff08;local…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

Qt Http Server模块功能及架构

Qt Http Server 是 Qt 6.0 中引入的一个新模块&#xff0c;它提供了一个轻量级的 HTTP 服务器实现&#xff0c;主要用于构建基于 HTTP 的应用程序和服务。 功能介绍&#xff1a; 主要功能 HTTP服务器功能&#xff1a; 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

【生成模型】视频生成论文调研

工作清单 上游应用方向&#xff1a;控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...

MySQL 知识小结(一)

一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库&#xff0c;分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷&#xff0c;但是文件存放起来数据比较冗余&#xff0c;用二进制能够更好管理咱们M…...

scikit-learn机器学习

# 同时添加如下代码, 这样每次环境(kernel)启动的时候只要运行下方代码即可: # Also add the following code, # so that every time the environment (kernel) starts, # just run the following code: import sys sys.path.append(/home/aistudio/external-libraries)机…...