软件设计模式 - 结构型模式
结构型模式介绍类和对象的组装方式,同时保持较大的灵活性和高效性。
结构型模式包括:适配器,桥接,组合,装饰,外观,享元,代理。
适配器模式(Adapter)
适配器模式是为了解决不同接口之间的适配问题。
适配器通常分为两类:对象适配器,类适配器。
对象适配器
对象适配器是通过实现接口来做到适配的,如下:
Object Adapter
示例代码:
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 DrawService { public : void paint (Graphics&) { } Graphics& getCanvasGraphics (canvas&) { } }; class DrawInterface { protected : void draw (canvas&) = 0 ; }; class DrawAdapter : public DrawInterface { protected : void draw (canvas& c) { auto g = service.getCanvasGraphics (c); service.paint (g); } private : DrawService service; };
类适配器
类适配器是通过面相对象编程的继承达到的,用于多继承方式实现。
如图所示:
Class Adapter
实例代码:
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 DrawGraphics { public : void draw (Graphics&) { } }; class DrawService { protected : void paint (Graphics&) { } Graphics& getCanvasGraphics (canvas&) { } }; class DrawAdapter : public DrawGraphics, public DrawService { public : void draw (canvas& c) { auto g = getCanvasGraphics (c); paint (g); } };
桥接模式(Bridge)
桥接模式是将抽象和实现分为两个独立的层次,将原本不同维度的继承方式改成组合方式。
如下图所示:
Bridge
示例代码:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Style {public : void draw (Canvas&) = 0 ; }; class Pencil : public Style {public : void draw (Canvas&) { } }; class Brush : public Style {public : void draw (Canvas&) { } }; class Pastel : public Style {public : void draw (Canvas&) { } }; class Square {public : void drawWithPen (Style& pen) { pen.draw (this ->getCanvas ()); } }; class Circle {public : void drawWithPen (Style& pen) { pen.draw (this ->getCanvas ()); } };
形状类只是抽象绘制层,可以绘制不同风格的图形,但是对于具体的绘制实现其实由不同风格的画笔来实现。
组合模式(Composite)
组合模式用于将对象组合成树状结构,通常这种模式的实现方式多见为“盒子”(Box)与“产品”(Item)的交互方式,盒子本身也是产品,盒子可以存放产品,从而产生递归嵌套的结构。
结构如下:
Composite
示例代码:
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 29 30 31 32 33 34 class Shape {public : void draw () = 0 ; protected : float x, y; }; class Circle : public Shape {public : void draw () { } private : float radius; }; class Square : public Shape {public : void draw () { } private : float width, height; }; class CompoundShape : public Shape {public : void draw () { } private : std::vector<Shape> childs; };
装饰模式(Decorator)
装饰器模式允许将对象放入包含某种行为的特殊对象,从而为原来的对象绑定特殊对象的新行为。
结构如下:
Decorator
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 29 30 31 32 33 34 35 36 class Shape {public : virtual draw () = 0 ; }; class Circle : public {public : void draw () { } private : float radius; }; class BrushDecorator : public Shape {public : BrushDecorator (Shape& s) { } void draw () { Shape::draw (); } }; class PastelDecorator : public Shape {public : PastelDecorator (Shape& s) { } void draw () { Shape::draw (); } };
使用装饰器模式,如下:
1 2 3 4 5 6 7 8 9 Circle circle; auto circleWithBrush = new BrushDecorator (circle);auto circleWithPastel = new PastelDecorator (circle);auto circleWithBrushPastel = new PastelDecorator (circleWithBrush); circleWithBrush.draw (); circleWithPastel.draw (); circleWithBrushPastel.draw ();
外观模式(Facade)
外观模式是为了给库/框架设计一套简单的接口,这样对外减少细节暴露,从而减少系统的耦合度。
如下示例,设计了一种统一风格的徽章绘制模式:
Facade
示例代码:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Shape {public : void draw (Canvas&) = 0 ; }; class CompoundShape : public Shape {public : void addChild (Shape& c) { } void draw (Canvas&) { } private : std::vector<Shape> childs; }; class Brush {public : void setPen (Canvas&) ; }; class Background {public : void fill (Canvas&) ; }; class Badget {public : void drawBadget (Shape& s) { auto canvas = this ->getCanvas (); assert (canvas != nullptr ); br.setPen (*canvas); bg.fill (*canvas); s.draw (*canvas); } private : Brush br; Background bg; CompundShape cshape; };
对于外部调用者来说,只需调用drawBadget()
,将徽章复杂图形变量作为形式参数传入函数,即可完成各种徽章的绘制。
享元模式(Flyweight)
享元模式是通过共享对象状态,使得对象不必保存所有的状态,从而达到在内存中载入更多的对象。
常见应用场景是游戏制作场景,游戏中最常见的元素是粒子系统,每一个粒子都有自己的坐标,颜色,状态等信息,如果每一个粒子对象都存储所有的信息,那么在实际游戏场景中将会对内存需求巨大,几十万个粒子就会导致系统内存被用完,从而导致OOM,为了解决这个问题,可以从对象的类来分析,提取其中的常量和变量,对于常量使用共享对象存储。
如下:
Flyweight
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Background {};class Color {};class Flyweight {public : void draw () { } private : Background bg; Color cr; }; class FlyweightFactory {public : Flyweight* getFlyweight (state s) { auto f = cache.find (s); if (f != nullptr ) return f; return new Flyweight (); } private : Cache cache; };
享元模式使用前还需要进行初始化,以便将常量对象存储到享元工厂的cache中。
1 2 3 4 5 6 7 8 9 auto particle1 = new Flyweight (1 ); auto particle2 = new Flyweight (2 ); FlyweightFactory::getInstance ().addFlyweight (particle1); FlyweightFactory::getInstance ().addFlyweight (particle2); auto particle = FlyweightFactory::getInstance ().getFlyweight (-1 );
代理模式(Proxy)
代理模式提供对对象访问的占位,控制对原始对象的访问,可以做到在外界对对象请求操作前后添加额外处理。
还是以绘图案例为例,这里绘制的是图片,这些图片以tag为唯一名称存储在数据库中,每次绘制需要从数据库获取相应的图片,再进行绘制,但是,如果绘制的图形复杂,包含的图片较多的时候,绘制效率会很低,绘制界面就会出现绘制延迟的现象,根本原因是从数据库获取图片的累加时间太长了。所以解决这种问题,就要用到代理器的模式,如下:
Proxy
这里的代理器在获取到图片的时候,会对已经获取的图片做缓存,这样下次再次获取该图片,就可以立即返回该图片,从而大大减少绘制时间,使绘制效率变高。
案例代码:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class Image {public : std::vector<std::string> listImages () = 0 ; std::vector<uint8_t > getImage (std::string&) = 0 ; }; class DBImage : public Image {public : std::vector<std::string> listImages () { auto result = db.findAll (); }; std::vector<uint8_t > getImage (std::string&) { }; private : DB db; }; class CachedImage : public Image {public : CachedImage (DBImage& dbi) {} std::vector<std::string> listImages () { auto result = cache.findAll (); if (!result.empty ()) { return result; } result = service.findAll (); cache.store (result); return result; }; std::vector<uint8_t > getImage (std::string& tag) { auto img = cache.find (tag); if (!img.empty ()) { return img; } img = service.find (tag); cache.store (tag, img); return img; }; private : DBImage service; Cache cache; };
实际使用时,直接调用代理器即可:
1 2 3 4 5 DBImage dbi ("mongo://xxxx" ) ;CachedImage ci (dbi) ;auto result = ci.listImages ();auto image = ci.getImage (result[0 ]);
CacheImage >
CacheImage中使用的Cache,根据实际应用场景,可以是中间件存储,例如:Memcached,redis等。
MVC模式(MVC)
MVC分别代表:Model-View-Controller,这种方式应用于程序的分层开发,减少数据,视图界面,和逻辑控制之间的耦合,使开发维护更加清晰。
这种模式在传统的MFC开发框架中广泛使用。下面演示一个富文本编辑器的MVC设计。
MVC
代码如下:
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 29 30 31 32 33 34 35 36 37 38 39 40 class Model {};class View {};class Controller {public : void setView () = 0 ; void setModel () = 0 ; }; class Document : public Model {public : Document (Controller* ctrl) { this ->ctrl = ctrl; } private : Controller* ctrl; }; class RichEditCtrl : public Controller {public : void setView (View* v) { this ->view = v; } void setModel (Model* m) { this ->model = m; } private : View* view; Model* model; }; class RichEditView : public View {public : View (Controller* ctrl) { this ->ctrl = ctrl; } private : Controller* ctrl; };
构造MVC时如下:
1 2 3 4 5 6 7 8 9 10 11 12 RichEditCtrl ctrl; RichEditView view (&ctrl) ;Document doc (&ctrl) ;ctrl.setView (&view); ctrl.setModel (&doc); ctrl.notify (); doc.update (); view.refresh ();