文章目录
前言
设计模式系列文章之结构型模式汇总,并且分析他们的原理以及场景。并且分别用c++与go语言进行举例实现。
一、结构型模式
结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。
二、适配器模式
场景
当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。
适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。
如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。
你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。
原理
**适配器模式将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。**它包括类适配器和对象适配器,本文针对的是对象适配器。举个例子,在STL中就用到了适配器模式。STL实现了一种数据结构,称为双端队列(deque),支持前后两段的插入与删除。STL实现栈和队列时,没有从头开始定义它们,而是直接使用双端队列实现的。这里双端队列就扮演了适配器的角色。队列用到了它的后端插入,前端删除。而栈用到了它的后端插入,后端删除。假设栈和队列都是一种顺序容器,有两种操作:压入和弹出。下面给出相应的UML图,与DP上的图差不多。
c++实现
首先,代码中定义了一个叫做 Deque
的类,它具有 push_back
、push_front
、pop_back
和 pop_front
四个方法,用于在双端队列中进行元素的插入和删除操作。
接着,定义了一个名为 Sequence
的抽象基类,其中包含了纯虚函数 push
和 pop
,用于表示一个序列容器的基本操作。
接下来,通过继承 Sequence
类,分别定义了 Stack
和 Queue
两个派生类。Stack
类以组合方式使用了一个 Deque
对象,并实现了 push
和 pop
方法,底层使用了双端队列的 push_back
和 pop_back
方法。
类似地,Queue
类也以组合方式使用了一个 Deque
对象,并实现了 push
和 pop
方法,底层使用了双端队列的 push_back
和 pop_front
方法。
在 main
函数中,使用了指向 Stack
和 Queue
对象的基类指针,并通过这些指针调用 push
和 pop
方法,实现对栈和队列的操作。
//双端队列
class Deque
{
public:
void push_back(int x) {
cout<<"Deque push_back"<<endl; }
void push_front(int x) {
cout<<"Deque push_front"<<endl; }
void pop_back() {
cout<<"Deque pop_back"<<endl; }
void pop_front() {
cout<<"Deque pop_front"<<endl; }
};
//顺序容器
class Sequence
{
public:
virtual void push(int x) = 0;
virtual void pop() = 0;
};
//栈
class Stack: public Sequence
{
public:
void push(int x) {
deque.push_back(x); }
void pop() {
deque.pop_back(); }
private:
Deque deque; //双端队列
};
//队列
class Queue: public Sequence
{
public:
void push(int x) {
deque.push_back(x); }
void pop() {
deque.pop_front(); }
private:
Deque deque; //双端队列
};
int main()
{
Sequence *s1 = new Stack();
Sequence *s2 = new Queue();
s1->push(1); s1->pop();
s2->push(1); s2->pop();
delete s1; delete s2;
return 0;
}
go实现
将类中的成员函数改为使用方法定义,将继承关系使用组合方式实现,以及相应的语法调整。
在主函数中,创建了一个 Stack 对象和一个 Queue 对象,并通过它们的 push 和 pop 方法模拟栈和队列的操作。
package main
import "fmt"
// 双端队列
type Deque struct{
}
func (d *Deque) pushBack(x int) {
fmt.Println("Deque push_back")
}
func (d *Deque) pushFront(x int) {
fmt.Println("Deque push_front")
}
func (d *Deque) popBack() {
fmt.Println("Deque pop_back")
}
func (d *Deque) popFront() {
fmt.Println("Deque pop_front")
}
// 序列容器接口
type Sequence interface {
push(x int)
pop()
}
// 栈
type Stack struct {
deque Deque
}
func (s *Stack) push(x int) {
s.deque.pushBack(x)
}
func (s *Stack) pop() {
s.deque.popBack()
}
// 队列
type Queue struct {
deque Deque
}
func (q *Queue) push(x int) {
q.deque.pushBack(x)
}
func (q *Queue) pop() {
q.deque.popFront()
}
func main() {
s1 := &Stack{
}
s2 := &Queue{
}
s1.push(1)
s1.pop()
s2.push(1)
s2.pop()
}
三、桥接模式
场景
如果你想要拆分或重组一个具有多重功能的庞杂类 (例如能与多个数据库服务器进行交互的类), 可以使用桥接模式。
类的代码行数越多, 弄清其运作方式就越困难, 对其进行修改所花费的时间就越长。 一个功能上的变化可能需要在整个类范围内进行修改, 而且常常会产生错误, 甚至还会有一些严重的副作用。
桥接模式可以将庞杂类拆分为几个类层次结构。 此后, 你可以修改任意一个类层次结构而不会影响到其他类层次结构。 这种方法可以简化代码的维护工作, 并将修改已有代码的风险降到最低。
如果你希望在几个独立维度上扩展一个类, 可使用该模式。
桥接建议将每个维度抽取为独立的类层次。 初始类将相关工作委派给属于对应类层次的对象, 无需自己完成所有工作。
如果你需要在运行时切换不同实现方法, 可使用桥接模式。
当然并不是说一定要实现这一点, 桥接模式可替换抽象部分中的实现对象, 具体操作就和给成员变量赋新值一样简单。
原理
将抽象部分与它的实现部分分离,使它们都可以独立地变化。考虑装操作系统,有多种配置的计算机,同样也有多款操作系统。如何运用桥接模式呢?可以将操作系统和计算机分别抽象出来,让它们各自发展,减少它们的耦合度。当然了,两者之间有标准的接口。这样设计,不论是对于计算机,还是操作系统都是非常有利的。下面给出这种设计的UML图,其实就是桥接模式的UML图。
c++实现
定义了一个名为 OS 的操作系统基类,其中包含了一个纯虚函数 InstallOS_Imp,用于表示安装操作系统的具体实现。
然后,通过继承 OS 类,分别定义了 WindowOS、LinuxOS 和 UnixOS 三个派生类,它们分别实现了在不同操作系统下的具体安装操作。
接下来,定义了一个名为 Computer 的计算机基类,其中包含了一个虚函数 InstallOS,用于表示安装操作系统的方法。
然后,通过继承 Computer 类,分别定义了 DellComputer、AppleComputer 和 HPComputer 三个派生类,它们分别实现了在不同品牌计算机上的具体安装操作系统方法。
在 InstallOS 方法中,通过传入 OS 对象的指针,调用其 InstallOS_Imp 方法来实现具体的操作系统安装。
//操作系统
class OS
{
public:
virtual void InstallOS_Imp() {
}
};
class WindowOS: public OS
{
public:
void InstallOS_Imp() {
cout<<"安装Window操作系统"<<endl; }
};
class LinuxOS: public OS
{
public:
void InstallOS_Imp() {
cout<<"安装Linux操作系统"<<endl; }
};
class UnixOS: public OS
{
public:
void InstallOS_Imp() {
cout<<"安装Unix操作系统"<<endl; }
};
//计算机
class Computer
{
public:
virtual void InstallOS(OS *os) {
}
};
class DellComputer: public Computer
{
public:
void InstallOS(OS *os) {
os->InstallOS_Imp(); }
};
class AppleComputer: public Computer
{
public:
void InstallOS(OS *os) {
os->InstallOS_Imp(); }
};
class HPComputer: public Computer
{
public:
void InstallOS(OS *os) {
os->InstallOS_Imp