大数据系统把文件推送过来,根据不同类型采取不同的解析方式。多数的小伙伴就会写出以下的代码:
if(type=="A"){
//按照A格式解析
}else if(type=="B"){
//按照B格式解析
}else{
//按照默认格式解析
}
存在问题?
以上代码,违背了面向对象编程的开闭原则以及单一原则。
如果你的代码有多个if…else等条件分支,并且每个条件分支,可以封装起来替换的,我们就可以使用策略模式来优化。
策略模式定义了算法族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的的客户。
大白话:
假设你跟不同性格类型的小姐姐约会,要用不同的策略,有的请电影比较好,有的则去吃小吃效果不错,有的去逛街买买买最合适。当然,目的都是为了得到小姐姐的芳心,请看电影、吃小吃、逛街就是不同的策略。
策略模式针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
context,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用。
策略模式和工厂模式的不同之处:
工厂来生成算法对象,这没有错,但算法只是一种策略,最重要的是这些算法是随时间都可能互相替换的,这就是变化点,而封装变化就是面向对象的一个重要的思维方式,
#pragma once
#include
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;
}
请假是我们日常生活中经常遇到的事,一般请假按请的时间长短需要跟不同级别的管理者请,就是请假这个请求根据时间长短可由不同的处理者处理,非常适合责任链模式。
场景:不同的请求需要不同权限的对象来处理的情况。
当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。
责任链模式为请求创建了一个接收者对象的链。执行链上有多个对象节点,每个对象节点都有机会(条件匹配)处理请求事务,如果某个对象节点处理完了,就可以根据实际业务需求传递给下一个节点继续处理或者返回处理完毕。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
责任链模式实际上是一种处理请求的模式,它让多个处理器(对象节点)都有机会处理该请求,直到其中某个处理成功为止。责任链模式把多个处理器串成链,然后让请求在链上传递:
大白话:
假设你晚上去上选修课,坐到了最后一排。来到教室,发现前面坐了好几个漂亮的小姐姐,于是你找张纸条,写上:“你好, 可以做我的女朋友吗?如果不愿意请向前传”。纸条就一个接一个的传上去了,后来传到第一排的那个妹子手上。
#include
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天假,同意了!
你请的假事假太长了,不同意!*/
单一职责原则。 你可对发起操作和执行操作的类进行解耦。
开闭原则。 你可以在不更改现有代码的情况下在程序中新增处理者。
每个请求都从头到尾遍历。
如果建链不当,可能会造成循环调用,这将导致系统陷入死循环
假设有这么一个业务场景:内部系统不同商户,调用我们系统接口,去跟外部第三方系统交互(http方式)。走类似这么一个流程,如下:
一个请求都会经历这几个流程:
这里,有的商户可能是走代理出去的,有的是走直连。
定义一个操作中的算法的骨架流程,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。它的核心思想就是:定义一个操作的一系列步骤,对于某些暂时确定不下来的步骤,就留给子类去实现,这样不同的子类就可以定义出不同的步骤。
模板方法模式是通过把不变行为搬移到超类中,去除子类中的重复代码来提现他的优势。
模板方法模式提供了一个很好的代码复用。
当不变和可变的行为在方法的子类实现中混合在一起的时候,不变的行为就会在子类中重复出现。通过模板方法模式把这些行为搬移到单一的地方,这样就帮助子类摆脱重复不变行为的纠缠。
#ifndef HEAD_H
#define HEAD_H
class 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
#include
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
#include
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;
}
登陆注册应该是最常见的业务场景了。就拿注册来说事,我们经常会遇到类似的场景,就是用户注册成功后,我们给用户发一条消息,又或者发个邮件等等,因此经常有如下的代码:
void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendEmail();
}
这块代码会有什么问题呢?如果产品又加需求:现在注册成功的用户,再给用户发一条短信通知。于是你又得改register方法的代码了。。。这是不是违反了开闭原则啦。
void register(User user){
insertRegisterUser(user);
sendIMMessage();
sendMobileMessage();
sendEmail();
}
并且,如果调发短信的接口失败了,是不是又影响到用户注册了?!这时候,是不是得加个异步方法给通知消息才好。可以使用观察者模式优化。
其它场景:
1)服务器发布新版本,让客户端更新。
2)游戏群发邮件给补贴等。
3)聊天室系统。
观察者模式又叫做发布-订阅模式;
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动的更新自己。
观察者模式属于行为模式,一个对象(被观察者)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。它的主要成员就是观察者和被观察者。
#ifndef HEAD_H
#define HEAD_H
#include
#include
#include
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;
}
工厂模式一般配合策略模式一起使用。用来去优化大量的if…else…或switch…case…条件语句。
在程序中,需要创建的对象很多,导致对象的new操作多且杂时,需要使用简单工厂模式;
由于对象的创建过程是我们不需要去关心的,而我们注重的是对象的实际操作,所以,我们需要分离对象的创建和操作两部分,如此,方便后期的程序扩展和维护。
简单工厂模式(Simple Factory Pattern)专门定义一个类来负责创建其他类的实例,被创建的实例通常具有共同的父类。
是一种实例化对象的方式,只要输入需要实例化对象的名字,就可以通过工厂对象的相应工厂函数来制造你需要的对象。
本着高内聚低耦合的原则,将系统的逻辑部分和功能分开。
简单工厂模式会增加系统类的个数,在一定程度上增加了系统的复杂度和理解难度;
系统扩展难,一旦增加新产品,就需要修改工厂逻辑,不利于系统的扩展与维护;简单工厂模式中所有产品的创建都是由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间耦合度高,严重影响了系统的灵活性和扩展性。
某电视机厂为各个品牌代工生产电视机,可以使用简单工厂的模式来实现。
#include
#include
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;
}
单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。I/O与数据库的连接,一般就用单例模式实现的。Windows里面的Task Manager(任务管理器)也是很典型的单例模式。
全局有且只有一个类的static实例,在程序任何地方都能够调用到。比如游戏客户端的本地Excel的加载,我们都会格式化成json。
一个好的单例应该具备下面4点
懒汉式(Lazy-Initialization)的方法是直到使用时才实例化对象,也就说直到调用Instance() 方法的时候才 new 一个单例的对象, 如果不被调用就不会占用内存。如果单线程没有问题,当多线程的时候就会出现不可靠的情况。
#include
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
using namespace std;
#include // C++11 shared_ptr头文件
#include // 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也是一长串代码不好维护且不美观。非必要不应该提出这种约束; 使用锁也有开销; 同时代码量也增多了,实际上设计最简单的才是最好的。
#include
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++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。
最推荐的一种单例实现方式:
比较饥饿、比较勤奋,实例在初始化的时候就已经建好了,不管你后面有没有用到,都先新建好实例再说。这个就没有线程安全的问题,但是呢,浪费内存空间呀。
这些模式关注对象的创建过程,以确保系统在创建对象时更灵活、更高效。
这些模式关注对象组合的方式,以实现更大的结构。
这些模式关注对象之间的通信、交互和分配职责。
“要依赖于抽象,不要依赖于具体,针对接口编程,不要针对实现编程 "
它强调了模块间的松耦合和可扩展性。该原则的核心思想是:
定义:“软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。”
开闭原则强调了在设计和编写代码时,应该通过扩展来增加功能,而不是通过修改已有的代码来实现功能的变化。它促使我们设计出更加稳定、灵活、可扩展和可维护的软件系统。
开闭原则的核心思想是通过抽象和多态来实现可扩展性。具体来说,可以通过以下方法来满足开闭原则:
接口隔离原则的定义是:“一个类对其他类的依赖应该建立在最小的接口上。”
接口隔离原则的目标是设计精简、高内聚、低耦合的接口,避免不必要的接口依赖和接口膨胀,以提高系统的灵活性和可维护性。
接口隔离原则的关键思想是将庞大的接口拆分为更小、更具体的接口,以满足客户端的精确需求。这样,客户端只需依赖于它们所需的接口,而无需依赖于不相关或不需要的接口。
以下是遵循接口隔离原则的一些指导原则:
它强调子类型必须能够替换其基类型,而不会引发程序的错误或异常。
定义是:“如果S是T的子类型,那么在所有T类型的程序中,对象可以被替换为S类型的对象,而不会影响程序的正确性。”
换句话说,子类对象应该能够在不破坏程序正确性的前提下替换其基类对象,且程序的行为不会产生意外或错误的结果。
里氏替换原则要求子类必须遵循其基类的约束和契约。即子类的方法必须遵循基类的方法声明,不能缩小基类方法的前置条件(输入参数约束)和扩大基类方法的后置条件(输出结果约束)。也就是说,子类应该保持与基类相同的行为规范。
遵循里氏替换原则的好处包括: