C++实现高频设计模式
面试能说出这几种常用的设计模式即可
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 模式结构
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方式)。走类似这么一个流程,如下:
一个请求都会经历这几个流程:
- 查询商户信息
- 对请求报文加签
- 发送http请求出去
- 对返回的报文验签
这里,有的商户可能是走代理出去的,有的是走直连。
3.2 定义
定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。
模板方法模式是通过把不变行为搬移到超类中,去除子类中的重复代码来提现他的优势。
模板方法模式提供了一个很好的代码复用。
当不变和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复不变行为的纠缠。
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++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
最推荐的一种单例实现方式:
- 通过局部静态变量的特性保证了线程安全 (C++11, GCC > 4.3, VS2015支持该特性);
- 不需要使用共享指针,代码简洁;不需要使用互斥锁。
- 注意在使用的时候需要声明单例的引用 SingletonPattern_V3& 才能获取对象
6.2.2 饿汉模式
比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
设计模式分类
创建型模式(Creational Patterns)
这些模式关注对象的创建过程,以确保系统在创建对象时更灵活、更高效。
- 单例模式(Singleton Pattern)
- 工厂方法模式(Factory Method Pattern)
- 抽象工厂模式(Abstract Factory Pattern)
- 建造者模式(Builder Pattern)
- 原型模式(Prototype Pattern)
结构型模式(Structural Patterns):
这些模式关注对象组合的方式,以实现更大的结构。
- 适配器模式(Adapter Pattern)
- 装饰器模式(Decorator Pattern)
- 代理模式(Proxy Pattern)
- 外观模式(Facade Pattern)
- 桥接模式(Bridge Pattern)
- 组合模式(Composite Pattern)
- 享元模式(Flyweight Patter)
行为型模式(Behavioral Patterns)
这些模式关注对象之间的通信、交互和分配职责。
- 策略模式(Strategy Pattern)
- 模板方法模式(Template Method Pattern)
- 观察者模式(Observer Pattern):发布订阅
- 迭代器模式(Iterator Pattern)
- 责任链模式(Chain of Responsibility Pattern)
- 命令模式(Command Pattern)
- 备忘录模式(Memento Pattern)
- 状态模式(State Pattern)
- 访问者模式(Visitor Pattern)
- 中介者模式(Mediator Pattern)
- 解释器模式(Interpreter Pattern)
设计模式原则
依赖倒置原则(Dependency Inversion Principle)
“要依赖于抽象,不要依赖于具体,针对接口编程,不要针对实现编程 "
它强调了模块间的松耦合和可扩展性。该原则的核心思想是:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象接口。
- 抽象接口不应该依赖于具体实现细节,具体实现细节应该依赖于抽象接口。
模块间的依赖关系应该通过抽象接口进行,而不是直接依赖于具体的实现。这样可以实现模块之间的解耦,使得系统更加灵活、可扩展和可维护。
通过针对接口编程,我们可以定义抽象的接口,高层模块只依赖于这些抽象接口,而不需要关心具体的实现细节。这样,在需要替换具体实现或引入新的实现时,只需要保证实现接口的兼容性,而不需要修改高层模块的代码。
这种方式也促进了代码的可测试性,因为我们可以轻松地通过使用模拟对象或者依赖注入来进行单元测试,而不需要依赖于具体的实现。
总之,依赖倒置原则强调了面向抽象编程的重要性,通过解耦和抽象,提高系统的灵活性、可扩展性和可维护性。
开闭原则(Open-Closed Principle)
定义:“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”
开闭原则强调了在设计和编写代码时,应该通过扩展来增加功能,而不是通过修改已有的代码来实现功能的变化。它促使我们设计出更加稳定、灵活、可扩展和可维护的软件系统。
开闭原则的核心思想是通过抽象和多态来实现可扩展性。具体来说,可以通过以下方法来满足开闭原则:
- 使用抽象来定义可扩展的接口或基类,以便在不修改现有代码的情况下进行扩展。
- 通过多态实现具体实现的替换,以便在运行时选择不同的实现,而不需要修改调用方的代码。
- 使用策略模式、工厂模式等设计模式来实现开闭原则。
遵循开闭原则的好处包括:
- 更好的可维护性:通过扩展而不是修改已有代码,减少了引入错误和破坏现有功能的风险。
- 更高的可复用性:通过抽象和多态,可以将通用的功能封装为可重用的模块或组件。
- 更好的可扩展性:通过扩展接口或基类,并实现新的具体实现,可以灵活地增加新的功能。
开闭原则是面向对象设计中的一个重要指导原则,它强调通过扩展而不是修改来实现功能变化,以提高系统的可维护性、可复用性和可扩展性。
接口隔离原则(Interface Segregation Principle)
接口隔离原则的定义是:“一个类对其他类的依赖应该建立在最小的接口上。”
接口隔离原则的目标是设计精简、高内聚、低耦合的接口,避免不必要的接口依赖和接口膨胀,以提高系统的灵活性和可维护性。
接口隔离原则的关键思想是将庞大的接口拆分为更小、更具体的接口,以满足客户端的精确需求。这样,客户端只需依赖于它们所需的接口,而无需依赖于不相关或不需要的接口。
以下是遵循接口隔离原则的一些指导原则:
- 定义细粒度的接口:将大型接口拆分为更小的、更具体的接口,以适应不同的客户端需求。
- 接口应该精简而专注:接口应该只包含客户端所需的方法,不应该强迫客户端实现不需要的方法。
- 避免"胖接口"和"接口污染":避免在接口中定义过多的方法,以免给客户端带来不必要的负担和依赖。
- 根据实际需求进行接口拆分:根据具体的业务需求和使用场景,灵活地拆分接口,以满足客户端的需要。
遵循接口隔离原则的好处包括:
- 减少接口间的依赖关系,降低耦合度。
- 提高系统的可维护性和可扩展性。
- 提高代码的复用性和可测试性。
- 更好地支持单一职责原则,使接口和类的责任更加清晰。
总之,接口隔离原则指导我们设计细粒度、专注、精简的接口,避免接口膨胀和不必要的接口依赖,以提高系统的灵活性和可维护性。
里氏替换原则(Liskov Substitution Principle)
它强调子类型必须能够替换其基类型,而不会引发程序的错误或异常。
定义是:“如果S是T的子类型,那么在所有T类型的程序中,对象可以被替换为S类型的对象,而不会影响程序的正确性。”
换句话说,子类对象应该能够在不破坏程序正确性的前提下替换其基类对象,且程序的行为不会产生意外或错误的结果。
里氏替换原则要求子类必须遵循其基类的约束和契约。即子类的方法必须遵循基类的方法声明,不能缩小基类方法的前置条件(输入参数约束)和扩大基类方法的后置条件(输出结果约束)。也就是说,子类应该保持与基类相同的行为规范。
遵循里氏替换原则的好处包括:
- 提高代码的可重用性和可扩展性:通过子类对象的替换,可以扩展系统的功能并保持代码的兼容性。
- 降低代码的耦合性:通过基于抽象的设计,减少对具体实现的依赖,提高代码的灵活性和可维护性。
- 保证系统的稳定性:子类的替换不会破坏系统的正确性,提供了更可靠的程序行为。
然而,应该注意的是,里氏替换原则并不意味着子类完全不能修改或扩展基类的行为。子类可以通过方法重写和方法重载来添加新的功能或修改基类的行为,但必须保持替换的一致性和不破坏原有约束。
总之,里氏替换原则是一种指导设计和编写代码的原则,通过保持子类对基类的替换能力,提高系统的可重用性、可扩展性和稳定性。
相关文章:

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

opencv(2): 视频采集和录制
视频采集 相关API VideoCapture()cap.read(): 返回两个值,第一个参数,如果读到frame,返回 True. 第二个参数为相应的图像帧。cap.release() VideoCapture cv2.VideoCapture(0) 0 表示自动检测,如果在笔记本上运行&…...
SpringBoot+EasyExcel设置excel样式
方式一:使用注解方式设置样式 模板可通过HeadFontStyle、HeadStyle、ContentFontStyle、ContentStyle、HeadRowHeight ContentRowHeight等注解设置excel单元格样式; //字体样式及字体大小 HeadFontStyle(fontName "宋体",fontHeightInPoints…...
自定义View之Measure(二)
measure 用来测量 View 的宽和高,它的流程分为 View 的 measure 流程和 ViewGroup 的measure流程,只不过ViewGroup的measure流程除了要完成自己的测量,还要遍历地调用子元素的measure()方法。 上一回说到performMeasur…...

SQL注入学习--GTFHub(布尔盲注+时间盲注+MySQL结构)
目录 布尔盲注 手工注入 笔记 Boolean注入 # 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 时间盲注 手工注入 使用脚本注入 sqlmap注入 使用Burpsuite进行半自动注入 MySQL结构 手工注入 sqlmap注入 笔记 union 联合注入,手工注入的一般步骤 …...

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

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

黑马程序员微服务第四天课程 分布式搜索引擎1
分布式搜索引擎01 – elasticsearch基础 0.学习目标 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容 例如: …...

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

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

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

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

创作者等级终于升到4级了
写了两个月的文章,终于等到4级了。发文纪念一下:...

Games104现代游戏引擎笔记 面向数据编程与任务系统
Basics of Parallel Programming 并行编程的基础 核达到了上限,无法越做越快,只能通过更多的核来解决问题 Process 进程 有独立的存储单元,系统去管理,需要通过特殊机制去交换信息 Thread 线程 在进程之内,共享了内存…...

系列三、GC垃圾回收【总体概览】
一、GC垃圾回收【总体概览】 JVM进行GC时,并非每次都对上面的三个内存区域(新生区、养老区、元空间/永久代)一起回收,大部分回收的是新生区里边的垃圾,因此GC按照回收的区域又分为了两种类型,一种是发生在新…...
无线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 的插件系统:Plugin2.4. 插件开发原则2.4.1. 插件独立2.4.2. 使用接口类型作为边界2.4.3. Unix 模块化原则2.4.4. 版本控制 2.5. 插件开发示例2.5.1. 编写…...
Python的2042小游戏及其详解
源码: 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 截图,可见1指向的总有回车符; 故障原因: 不小心误按了箭头4这个选项; 解决方法: 点击2箭头确保tab展开; 点击3以找到箭头4. 取消勾选或者多次点击,即可解决。...

Git-概念与架构
GIT-概念与架构 一、背景和起源二、版本控制系统1.版本控制分类1.1 集中式版本控制1.2 分布式版本控制 2.Git和SVN对比2.1 SVN2.2 GIT 三、GIT框架1.工作区(working directory)2.暂存区(staging area)3.本地仓库(local…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
嵌入式常见 CPU 架构
架构类型架构厂商芯片厂商典型芯片特点与应用场景PICRISC (8/16 位)MicrochipMicrochipPIC16F877A、PIC18F4550简化指令集,单周期执行;低功耗、CIP 独立外设;用于家电、小电机控制、安防面板等嵌入式场景8051CISC (8 位)Intel(原始…...