设计模式学习——桥接模式

这篇笔记介绍了桥接模式,通过分离抽象和实现来减少类的数量,并通过 C++ 示例展示了其应用。

引入

在音乐游戏舞萌DX中有各种不同的音符。比如:粉色的Tap音符、黄色的两条同时出现的Hold音符,还有舞萌DX 2023新加入的绝赞Slide音符。如果我们把他们都看作是不同的类,那么我们可以用继承关系表示表示它们:

image

可以发现,在此过程中我们创建了大量不同的类。若是在之后要加入新的Note形状或颜色,则又要加入大量不同种类的Note。

桥接模式

为了解决这个问题,我们使用桥接模式。将NotePattern属性分离出来,并在Note中引入Pattern对象来管理这一属性。这样,我们就分离了这一接口和实现部分。

image
 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Note {
public:
    explicit Note(Pattern* pattern);
    virtual ~Note();
    virtual void printSelf() = 0;
    void setPattern(Pattern *pattern);
protected:
    Pattern* _pattern;
};

class NoteTap : public Note{
public:
    explicit NoteTap(Pattern* pattern);
    void printSelf() override;

};

class NoteHold : public Note {
public:
    explicit  NoteHold(Pattern* pattern);
    void printSelf() override;
    void setDuration(int);
private:
    int _last_bar;
};
//......

class Pattern {
public:
    Pattern() = default;
    ~Pattern() = default;
    const std::string & getName();

protected:
    std::string _p_name;
};

class PatternPink : public Pattern {
public:
    PatternPink();
};

class PatternBlue : public Pattern {
public:
    PatternBlue();
};
//......
const std::string & Pattern::getName() {
    return _p_name;
}

PatternPink::PatternPink() {
    _p_name = "Pink";
}

PatternBlue::PatternBlue() {
    _p_name = "Blue";
}
//......

在Note类的具体实现中,我们直接调用Pattern的方法,完成桥接:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void Note::setPattern(Pattern *pattern) {
    _pattern = pattern;
}

NoteTap::NoteTap(Pattern *pattern) : Note(pattern){}

void NoteTap::printSelf() {
    std::cout << "A " << _pattern->getName() << " Tap" << std::endl;
}
//......

最后,我们在主函数中创建不同种类的Note和Pattern并将其组合起来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int main() {
    auto pattren1 = new PatternBreak();
    auto tap = new NoteTap(pattren1);
    tap->printSelf();

    auto pattern2 = new PatternPink();
    auto slide = new NoteSlide(pattern2);
    slide->setDuration(4);
    slide->printSelf();

    return 0;
}

得到结果:

1
2
A Break Tap
A Pink Slide with 4 bar

依赖注入与桥接模式

桥接模式将类的某种属性抽象为另一种类,并将这一种类的对象插入到原有的种类中。依赖注入也有类似的特性。若我们使用依赖注入改写上述代码,我们可以将Note中的形状属性独立出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Shape {
public:
    const std::string & getName();
protected:
    std::string _s_name;
};

class ShapeTap : public Shape{
public:
    ShapeTap();
};

class ShapeHold : public Shape {......};
//......

然后在Note类中插入这两个属性:

1
2
3
4
5
6
7
8
9
class Note {
public:
    Note(Pattern *pattern,Shape *shape);
    virtual ~Note();
    virtual void printSelf();
private:
    Shape *_shape;
    Pattern *_pattern;
};

最后在创建Note类时指定具体的形状和样式。

1
2
3
4
5
6
7
8
int main() {
    auto pattern1 = new PatternPink();
    auto shape1 = new ShapeTap();
    auto note1 = new Note(pattern1,shape1);

    note1->printSelf();
    return 0;
}

可以看出,依赖注入将对象的创建和依赖关系的管理全部交给Note类管理,使代码更加松耦合、易于维护和测试。

总结

桥接模式解决了使用继承造成的类爆炸问题。它暗示了一种面向对象设计的基本原则:使用组合而非继承。

实际开发过程中,如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,就可以通过桥接模式可以使它们在抽象层建立一个关联关系,从而分离抽象接口及其实现部分,提供比继承更好的解决方案。