设计模式:桥接模式和组合模式

引言

本篇文章主要是关于桥接模式和组合模式的代码和理论介绍

桥接模式

简介

桥接模式相比于设配器模式来说有一个很大的不同点,桥接的结构是在代码实现前就要设计好的,但是设配器模式是在代码实现了之后因为后来的业务变化我们需要兼容一些不适配的类引入的。

它的核心思想是:将「抽象部分」与「实现部分」分离,使它们可以独立变化。桥接模式主要通过组合而非继承来建立两个类之间的联系,从而避免类的数量因两个维度的交叉而爆炸式增长。

比如说两个品牌、三种操作,已经要 6 个类。再加一个品牌变 9 个,再加一种操作变 12 个------这就是「类爆炸」。而且每次增加品牌或操作,都要同时改好几个地方,牵一发动全身。图形编辑器里,每种图形(圆、方、三角)都可以有蓝、红、黄三种颜色。不用桥接就要写 9 个类;用桥接只需要 3 种图形类 + 3 种颜色类 = 6 个类,颜色和图形两个维度完全独立扩展,互不影响。

  • 抽象(Abstraction):通常是抽象类,定义抽象部分的接口,内部持有一个对「实现」接口的引用------这个引用就是那座桥。
  • 修正抽象(RefinedAbstraction) :继承 Abstraction,对抽象接口进行扩展,代表抽象维度上的具体变体(例如不同的遥控器类型:普通遥控、音量遥控)。
  • 实现(Implementor) :定义实现部分的接口。注意这个接口不必与 Abstraction 的接口完全一致,它只需要提供底层能力。
  • 具体实现(ConcreteImplementor) :实现 Implementor 接口的具体类,负责实际执行(例如 SonyTV、TCLTV)。

代码

我们这里要实现一个数据持久化的一个操作,里面有不同的持久化操作。对于客户端来说,操作的是持久化类Persistence。但是持久化的操作里面必须要关联持久化的实现抽象类,这个地方我们可以换一种理解方式,我们这里没有使用virtual来写继承,而是包含了对应的类,这样子当我们业务发生改变的时候,我们可以引入一个新的类,到时候把传入的参数impl变成另一个,这样子可扩展性大大的增加

cpp 复制代码
#pragma once
#ifndef _PERSISTENCE_H_
#define _PERSISTENCE_H_

namespace bep {
	class PersistenceImplentatior;
	class Persistence {
	private:
		PersistenceImplentatior* m_pImpl;
	public:
		Persistence(PersistenceImplentatior* pImpl);
		void saveData();
		void updateData();
	};
}

#endif
cpp 复制代码
#include "Persistence.h"
#include "impl/PersistenceImplementor.h"


bep::Persistence::Persistence(PersistenceImplentatior* pImpl)
{
	this->m_pImpl = pImpl;
}

void bep::Persistence::saveData()
{
	m_pImpl->saveData();
}

void bep::Persistence::updateData()
{
	m_pImpl->updateData();
}

这是持久化实现的抽象类

cpp 复制代码
#pragma once
#ifndef _PERSISTENCEIMPLEMENTOR_H_
#define _PERSISTENCEIMPLEMENTOR_H_

namespace bep {
	class PersistenceImplentatior {
	public:
		virtual void saveData() = 0;
		virtual void updateData() = 0;
	};
}
#endif

这是对于抽象类的具体实现。

cpp 复制代码
#pragma once
#include "../../util/ClassHelper.h"
#include "PersistenceImplementor.h"
#ifndef _DATABASE_H_
#define _DATABASE_H_

namespace bep {
	class Database : public PersistenceImplentatior {
		DECLARE_CLASS(Database);
	public:
		void saveData() override;
		void updateData() override;
	};
}
#endif
cpp 复制代码
#include "DataBase.h"
#include <iostream>

IMPLEMENT_CLASS(bep::Database);

void bep::Database::saveData()
{
	std::cout << "Database save data" << std::endl;
}

void bep::Database::updateData()
{
	std::cout << "Database update data" << std::endl;
}

这个是客户端的实现,首先我们确定我们要什么样子的持久化操作,然后把对应的操作(抽象类)交给我们持久化Persistence,假如之后我们的业务发生了变化,要换一个存储方式或者操作,我们要么增加一些虚函数,要么增加一个大的抽象类,反正最后传进去对应的抽象类。

cpp 复制代码
#include "../util/Properties.h"
#include "impl/PersistenceImplementor.h"
#include "Persistence.h"
#include "../util/ClassHelper.h"
#include <vld.h>
using namespace bep;
int main() {
	CREATE_PROPERTIES(props, conf);
	std::string cname = props.getProperty("bep");

	GET_INSTANCE_BY_NAME(PersistenceImplentatior*, impl, cname);
	Persistence* p = new Persistence(impl);
	p->saveData();
	p->updateData();
	delete p;
	delete impl;
	return 0;
}

这个模式没有很大的缺点,但是很考验架构师的经验

组合模式

简介

这个设计模式非常好理解,就是树形结构,一个接口里面包含了叶子和容器,那么容器里面又包含了叶子和容器,这和我们c盘d盘的结构是一模一样的。

  • Component 组件:组合模式的"根节点",定义组合中所有对象的通用接口,可以是抽象类或接口。该类中定义了子类的共性内容。
  • Leaf 叶子:实现了 Component 接口的叶子节点,表示组合中的叶子对象,叶子节点没有子节点。
  • Composite 合成:作用是存储子部件,并且在 Composite 中实现了对子部件的相关操作,比如添加、删除、获取子组件等。

代码

句子是由单词组成的,单词是由字母组成的,那么总的来说我们写一个字母的接口。字母接口类里面我们有一个std::vector<LetterComposte*> children,注意一下,我们的print()放在public是因为客户端可以直接打印使用,而add()这个放在public里面是方便子类使用,我们树形结构最大的一个特点就是有些接口是的统一,所有的子类都可以用根类的函数,所以子类不需要重写函数,不过也有些函数需要重写是因为子类和父类还是有些区别的。

cpp 复制代码
#pragma once
#include <vector>
#ifndef _LETTERCOMPOSITE_H_
#define _LETTERCOMPOSITE_H_

namespace comp {
	class LetterComposte {
	protected:
		std::vector<LetterComposte*> children;
		virtual void printThisBefore() = 0;
		virtual void printThisAfter() = 0;
	public:
		void add(LetterComposte* comp);
		size_t count();
		void print();
		virtual ~LetterComposte();
	};
}
#endif

这里其实还是比较难理解的,一个是打印函数,这有点类似于递归的操作了,一个类里面调用父类的打印函数,所以大家会看到有些类的printThisBefore()实现了,有些类printThisBefore()没有实现,主要原因就是基于不同的类,有着不同的打印策略。

cpp 复制代码
#include "LeterComposite.h"



void comp::LetterComposte::printThisBefore()
{

}

void comp::LetterComposte::printThisAfter()
{

}

void comp::LetterComposte::add(LetterComposte* comp)
{
	children.push_back(comp);
}

size_t comp::LetterComposte::count()
{
	return children.size();
}

void comp::LetterComposte::print()
{
	printThisBefore();
	for (auto& child : children) {
		child->print();
	}
	printThisAfter();
}

comp::LetterComposte::~LetterComposte()
{
	for (auto& child : children)
	{
		delete child;
	}
}

这个是单个的字母类,我们不需要数组来存储。

cpp 复制代码
#pragma once
#include "LeterComposite.h"
#include <vector>
#ifndef _LETTER_H_
#define _LETTER_H_

namespace comp {
	class Letter : public LetterComposte {
	private:
		char m_letter;
	protected:
		void printThisAfter() override;
		void printThisBefore() override;
	public:
		Letter(char letter) : m_letter(letter) {}
	};
}
#endif
cpp 复制代码
#include "Letter.h"
#include <iostream>



void comp::Letter::printThisAfter()
{

}

void comp::Letter::printThisBefore()
{
	std::cout << m_letter;
}

这个是单词,一个单词由多个字母类组成,所以有一个私有的数组。然后这里我们要解释用std::initializer_list 函数,它和vector的区别是无法修改,然后我们在里面用逗号表达式,所以里面最后保存的还是0,只是保证了我们可以按照顺序执行对应的函数。

然后在调用add()函数的时候同时触发了Letter()的构造函数,初始化m_letters的同时把这个放进了接口的函数里面。那么我们调用Word('a','b')的时候就会把a,b依次放入数组内部,最后在print()里面取出word的每一个字母的时候,就会执行每一个字母的print(),也就是把一个字母打印出来。

cpp 复制代码
#pragma once
#include "LeterComposite.h"
#include "Letter.h"
#include <initializer_list>
#ifndef _WORD_H_
#define _WORD_H_

namespace comp {
	class Word : public LetterComposte {
	private:
		std::vector<Letter*> letters;
	protected:
		void printThisAfter() override;
		void printThisBefore() override;
	public:
		template<typename... Rest>
		Word(Rest... args) {
			std::initializer_list<int>{(add(new Letter(args)), 0)...};
		}
	};
}
#endif	
cpp 复制代码
#include "Word.h"
#include <iostream>



void comp::Word::printThisAfter()
{

}

void comp::Word::printThisBefore()
{
	std::cout << " ";
}

把每一个字母放入数组内部,当执行print()的时候从数组里面取出每一个word,然后对其单独执行print()逻辑。

cpp 复制代码
#pragma once
#include "LeterComposite.h"
#include "Word.h"
#ifndef _SENTENCE_H_
#define _SENTENCE_H_

namespace comp {
	class Sentence : public LetterComposte {
	private:
		std::vector<Word*> sentences;
	protected:
		void printThisAfter() override;
		void printThisBefore() override;
	public:
		template<typename... Rest>
		Sentence(Rest... args) {
			std::initializer_list<int>{(add(args), 0)...};
		}
	};
}
#endif
cpp 复制代码
#include "Sentence.h"
#include <iostream>



void comp::Sentence::printThisAfter()
{
	std::cout << std::endl;
}

void comp::Sentence::printThisBefore()
{

}

这个是客户端的实现。

cpp 复制代码
#include "comp/LeterComposite.h"
#include "comp/Sentence.h"
#include "comp/Word.h"
using namespace comp;
int main() {
	LetterComposte* s1 = new Sentence(new Word('a', 'b', 'c'), new Word('d', 'e', 'f'), new Word('g', 'h', 'i'));
	LetterComposte* s2 = new Sentence(new Word('j', 'k', 'l'), new Word('m', 'n', 'o'), new Word('p', 'q', 'r'));

	s1->print();
	s2->print();

	delete s1;
	delete s2;
	return 0;
}

下面是一整个的输出过程,有助于大家的理解。

复制代码
Sentence::print()
│
├── Sentence::printThisBefore()  什么也不输出
│
├── Word("abc")::print()
│   ├── Word::printThisBefore()  输出空格
│   └── Letter 分别输出 a、b、c
│
├── Word("def")::print()
│   ├── 输出空格
│   └── 输出 d、e、f
│
├── Word("ghi")::print()
│   ├── 输出空格
│   └── 输出 g、h、i
│
└── Sentence::printThisAfter()   输出换行

总结

本篇文章就到这里结束了!!!希望可以帮助大家理解~~~

相关推荐
-森屿安年-1 小时前
647. 回文子串
c++·算法·动态规划
fpcc1 小时前
c++应用网络编程之十七WebSocket
网络·c++·websocket
haolin123.2 小时前
类和对象(下)
c语言·开发语言·c++
皓月斯语2 小时前
P5736 【深基7.例2】质数筛
c++·算法·题解
鸿芯微控科技2 小时前
MFC质量流量控制器流量不准怎么排查?设定值、反馈值与参考流量对比,附Python代码
c++·python·mfc
拂拉氏2 小时前
【知识讲解】 让指针拥有“自我意识”?--C++智能指针相关内容讲解
c++·智能指针
CVer儿2 小时前
c++二级指针
开发语言·c++
mlidongfeng3 小时前
[AI][C++26] SIMD 编程模型思考
开发语言·c++·人工智能
梓䈑3 小时前
【用 Vibe Coding 实现的 C++17 在线判题系统】OpenCode 入门指南:从环境搭建到上下文管理
c++·ai编程