软件设计模式 - 结构型模式

2017年6月6日

软件设计模式 - 结构型模式

结构型模式介绍类和对象的组装方式,同时保持较大的灵活性和高效性。

结构型模式包括:适配器,桥接,组合,装饰,外观,享元,代理。

适配器模式(Adapter)

适配器模式是为了解决不同接口之间的适配问题。

适配器通常分为两类:对象适配器,类适配器。

对象适配器

对象适配器是通过实现接口来做到适配的,如下:

Object Adapter

示例代码:

cpp
class DrawService {
    // 绘图服务
public:
    void paint(Graphics&) {
        // 使用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

实例代码:

cpp
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

示例代码:

cpp
// 画笔风格

class Style {
public:
    void draw(Canvas&) = 0;
};

class Pencil : public Style {
public:
    void draw(Canvas&) {
        // TODO: 铅笔绘制实现
    }
};

class Brush : public Style {
public:
    void draw(Canvas&) {
        // TODO: 画刷绘制实现
    }
};

class Pastel : public Style {
public:
    void draw(Canvas&) {
        // TODO: 蜡笔绘制实现
    }
};

// 形状
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

示例代码:

cpp
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

cpp
class Shape {
public:
    virtual draw() = 0;
};

class Circle : public {
public:
    void draw() {
        // TODO: 绘制
    }
private:
    float radius;
};

// 装饰器
class BrushDecorator : public Shape {
public:
    BrushDecorator(Shape& s) {
        // TODO: 设置画刷风格
    }
    void draw() {
        // 绘制
        Shape::draw();
    }
};

class PastelDecorator : public Shape {
public:
    PastelDecorator(Shape& s) {
        // TODO: 设置蜡笔风格
    }
    void draw() {
        // 绘制
        Shape::draw();
    }
};

使用装饰器模式,如下:

cpp
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

示例代码:

cpp
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

示例代码:

cpp
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中。

cpp
// 初始化享元工厂
auto particle1 = new Flyweight(1); // 静态粒子1
auto particle2 = new Flyweight(2); // 静态粒子2
// ...

FlyweightFactory::getInstance().addFlyweight(particle1);
FlyweightFactory::getInstance().addFlyweight(particle2);

auto particle = FlyweightFactory::getInstance().getFlyweight(-1); // 普通动态粒子

代理模式(Proxy)

代理模式提供对对象访问的占位,控制对原始对象的访问,可以做到在外界对对象请求操作前后添加额外处理。

还是以绘图案例为例,这里绘制的是图片,这些图片以tag为唯一名称存储在数据库中,每次绘制需要从数据库获取相应的图片,再进行绘制,但是,如果绘制的图形复杂,包含的图片较多的时候,绘制效率会很低,绘制界面就会出现绘制延迟的现象,根本原因是从数据库获取图片的累加时间太长了。所以解决这种问题,就要用到代理器的模式,如下:

Proxy

这里的代理器在获取到图片的时候,会对已经获取的图片做缓存,这样下次再次获取该图片,就可以立即返回该图片,从而大大减少绘制时间,使绘制效率变高。

案例代码:

cpp
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();
        // 数据库查询返回所有的图片的tag列表
    };
    std::vector<uint8_t> getImage(std::string&) {
        // 返回对应tag的图片数据
    };
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;
};

实际使用时,直接调用代理器即可:

cpp
DBImage dbi("mongo://xxxx");
CachedImage ci(dbi);

auto result = ci.listImages();
auto image = ci.getImage(result[0]); // 第一个tag的图片

CacheImage

CacheImage中使用的Cache,根据实际应用场景,可以是中间件存储,例如:Memcached,redis等。

MVC模式(MVC)

MVC分别代表:Model-View-Controller,这种方式应用于程序的分层开发,减少数据,视图界面,和逻辑控制之间的耦合,使开发维护更加清晰。

这种模式在传统的MFC开发框架中广泛使用。下面演示一个富文本编辑器的MVC设计。

MVC

代码如下:

cpp
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时如下:

cpp
RichEditCtrl ctrl;
RichEditView view(&ctrl);
Document doc(&ctrl);
ctrl.setView(&view);
ctrl.setModel(&doc);

// 视图的更新通知控制器
ctrl.notify();
// 控制器更新数据模型
doc.update();
// 模型更新完后刷新视图
view.refresh();
© 2013 – 2025 陈祥