结构型模式

结构型设计模式

在做了一些小项目之后,对设计模式的理解更加深刻,现在在此再次概述之前学习一些模式

桥接模式/Bridge

桥接模式其实也就是将实现和接口分离,然后根据需要选择不同的实现和接口组合,具体做法很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test
{
TestImpl *p;
void method()
{
p->method();
}
};

class TestImpl
{
void method()
{
//do something
}
};

上面这个实现与接口分离的简单例子,桥接模式想要达到的效果是,能够将一个统一的接口和不同的实现进行组合

那么,就可以使用继承的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class TestImpl
{
virtual void method() = 0;
};

void TestImpl::method()
{
//you can set the default implement
}

class Impl_A : public TestImpl
{
void method()
{
//A implement
}
};

class Impl_B : public TestImpl
{
void method()
{
TestImpl::method(); //use the default implement
}
};

上面编写了多个实现,然后可以在接口中决定使用哪个实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test
{
Test()
{
p = new Impl_A();
}
void method()
{
p->method();
}

TestImple *p;
};

这样,Bridge就做好了

组合模式/Composite

组合模式也很简单,组合模式主要是根元素的递归组合,何谓递归组合?

也就是根元素中包含根元素,根元素{根元素, 子元素}

例如JSON

1
2
3
4
5
6
7
{
"root" :
{
"name" : "jack"
},
"age" : 34
}

因此,我们需要判断所组合的元素是否是根元素,因为只有根元素才可以组合其他元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Item
{
virtual bool isRoot() = 0;
};

class Root : public Item
{
bool isRoot()
{
return true;
}

void add();
};

class Node : public Item
{
bool isRoot()
{
return false;
}
}

if (item.isRoot())
{
item.add(other_item);
}

装饰模式/Decorator

装饰器模式,其实非常简单

在Unix中,很多工具具有可配置性,例如某个IDE,你可以为它指定具体的文本编辑器,例如Vim,也可以为其指定不同的编译系统,例如gcc,clang

装饰器模式也如此,强调的是可配置(装饰)性

1
2
3
4
5
6
7
8
9
10
11
12
class IDE
{
void setEditor(Editor editor);
void setCompiler(Compiler compiler);
....
};

class Editor;
class Vim : public Editor;
class Compiler;
class Clang : public Compiler;
class GCC : public Compiler;

享元/FLYWEIGHT

这个模式可以运用共享技术有效地支持大量细粒度的对象

上面这个描述有点难理解,其实,所谓享元,可以理解为“共享的单元”

例如下面的字符串

helloNicegoodnice Blod

上面的字符串,有普通字符x2,有加粗字符x2,有代码字符,有倾斜字符

如果为上面的字符设置字体,那么如果是普通的做法,会新建两次加粗字符

1
2
3
4
5
//api : String(std::string, Font)
String("hello", new Font("normal"));
String("Nice", new Font("blod"));
String("good", new Font("normal"));
....

这样的话就会很繁琐,并且存在多个一样的Font

那么为了节省空间,我们可以使用如下方式

1
2
3
4
5
6
7
8
class FontMap
{
map {"normal" : Font("normal"), "blod" : Font("blod")....}
Font& getFont(std::string font_name);
};

String("hello", fontmap.getFont("normal"));
String("Nice", fontmap.getFont("blod"));

这样的话,每个String都引用对应的字体,而不是尝试去创建它,相同字体的String共享同一种字体,这就节省了很多空间

代理/PROXY

代理就是通过一个间接层控制对于某个对象的访问操作

例如,在创建一个窗口的时候,我们需要往窗口中添加一个图片,这个图片很大,但是图片只有在某种情况下才能看到,例如点击窗口上的某个按钮…

如果我们在创建窗口的时候一并把图片也预先加载了,那么在打开窗口的时候会变得非常慢,因为我们把没显示但是又巨大的图片一并创建了,这时候我们需要代理

1
2
3
4
5
6
7
8
9
class Windows()
{
Winodws()
{
img = new Img(...);
}
private:
Img *img;
};

上述的做法就不是很好,因为这样子的话会花费不必要的时间去创建一个Img,因为用户可能根本不会去点击按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ImgProxy;
class Windows()
{
Windows()
{
imgProxy = new ImgProxy();
}

void ButtonClickEvent()
{
imgProxy->show();
}
private:
ImgProxy *imgProxy;
};

class ImgProxy
{
void show()
{
if (img == nullptr)
{
img = new Img(...);
}
};
private:
Img *img;
}

如果采用了代理,那么只有当用户点击按钮的时候,Img才会被创建