Bridge (Мост)
Проблема
У вас есть два измерения изменчивости: например, тип фигуры (круг, квадрат) и способ отрисовки (vector, raster). Без Bridge количество комбинаций растёт мультипликативно: VectorCircle, RasterCircle, VectorSquare, RasterSquare...
Bridge разделяет абстракцию (что делать) от реализации (как делать), позволяя менять их независимо.
Диаграмма
Без Bridge (комбинаторный взрыв):
VectorCircle, VectorSquare, VectorTriangle
RasterCircle, RasterSquare, RasterTriangle
= 6 типов (3 x 2)
С Bridge:
+----------+ +-----------+
| Shape |------>| Renderer | <-- interface
+----------+ +-----------+
| renderer | | RenderCircle() |
| Draw() | | RenderRect() |
+----------+ +-----------+
^ ^
+--+--+ +---+---+
| | | |
Circle Square Vector Raster
= 5 типов (3 + 2)
Реализация
Интерфейс реализации (Renderer)
package drawing
import "fmt"
// Renderer defines how shapes are drawn.
// This is the "implementation" side of the bridge.
type Renderer interface {
RenderCircle(x, y, radius float64) string
RenderRect(x, y, width, height float64) string
RenderLine(x1, y1, x2, y2 float64) string
}
Конкретные реализации
// VectorRenderer draws shapes using vector graphics.
type VectorRenderer struct{}
func (v *VectorRenderer) RenderCircle(x, y, radius float64) string {
return fmt.Sprintf("<circle cx=%.1f cy=%.1f r=%.1f />", x, y, radius)
}
func (v *VectorRenderer) RenderRect(x, y, width, height float64) string {
return fmt.Sprintf("<rect x=%.1f y=%.1f w=%.1f h=%.1f />", x, y, width, height)
}
func (v *VectorRenderer) RenderLine(x1, y1, x2, y2 float64) string {
return fmt.Sprintf("<line x1=%.1f y1=%.1f x2=%.1f y2=%.1f />", x1, y1, x2, y2)
}
// RasterRenderer draws shapes using pixels.
type RasterRenderer struct {
DPI int
}
func (r *RasterRenderer) RenderCircle(x, y, radius float64) string {
return fmt.Sprintf("Drawing %d pixels for circle at (%.1f,%.1f) r=%.1f @%dDPI",
int(radius*float64(r.DPI)), x, y, radius, r.DPI)
}
func (r *RasterRenderer) RenderRect(x, y, width, height float64) string {
return fmt.Sprintf("Drawing %d pixels for rect at (%.1f,%.1f) %1.fx%.1f @%dDPI",
int(width*height*float64(r.DPI)), x, y, width, height, r.DPI)
}
func (r *RasterRenderer) RenderLine(x1, y1, x2, y2 float64) string {
return fmt.Sprintf("Drawing pixel line (%.1f,%.1f)-(%.1f,%.1f) @%dDPI",
x1, y1, x2, y2, r.DPI)
}
Абстракция (Shape)
// Shape is the abstraction side of the bridge.
type Shape interface {
Draw() string
Resize(factor float64)
}
// --- Circle ---
type Circle struct {
renderer Renderer // bridge to implementation
x, y float64
radius float64
}
func NewCircle(renderer Renderer, x, y, radius float64) *Circle {
return &Circle{
renderer: renderer,
x: x,
y: y,
radius: radius,
}
}
func (c *Circle) Draw() string {
return c.renderer.RenderCircle(c.x, c.y, c.radius)
}
func (c *Circle) Resize(factor float64) {
c.radius *= factor
}
// --- Rectangle ---
type Rectangle struct {
renderer Renderer
x, y float64
width, height float64
}
func NewRectangle(renderer Renderer, x, y, w, h float64) *Rectangle {
return &Rectangle{
renderer: renderer,
x: x,
y: y,
width: w,
height: h,
}
}
func (r *Rectangle) Draw() string {
return r.renderer.RenderRect(r.x, r.y, r.width, r.height)
}
func (r *Rectangle) Resize(factor float64) {
r.width *= factor
r.height *= factor
}
Использование
func main() {
vector := &drawing.VectorRenderer{}
raster := &drawing.RasterRenderer{DPI: 96}
// Same shapes, different renderers
shapes := []drawing.Shape{
drawing.NewCircle(vector, 10, 20, 5),
drawing.NewCircle(raster, 10, 20, 5),
drawing.NewRectangle(vector, 0, 0, 100, 50),
drawing.NewRectangle(raster, 0, 0, 100, 50),
}
for _, s := range shapes {
fmt.Println(s.Draw())
}
// Output:
// <circle cx=10.0 cy=20.0 r=5.0 />
// Drawing 480 pixels for circle at (10.0,20.0) r=5.0 @96DPI
// <rect x=0.0 y=0.0 w=100.0 h=50.0 />
// Drawing 480000 pixels for rect at (0.0,0.0) 100.0x50.0 @96DPI
}
Реальный пример: система логирования
package logger
import (
"context"
"fmt"
"io"
"time"
)
// Formatter defines how log entries are formatted (implementation).
type Formatter interface {
Format(level, msg string, fields map[string]any) string
}
// Writer defines where log entries are sent (another implementation dimension).
type Writer interface {
Write(ctx context.Context, formatted string) error
}
// Logger is the abstraction that bridges Formatter and Writer.
type Logger struct {
formatter Formatter
writer Writer
level string
}
// NewLogger creates a Logger with the given formatter and writer.
func NewLogger(f Formatter, w Writer) *Logger {
return &Logger{formatter: f, writer: w, level: "info"}
}
func (l *Logger) Info(ctx context.Context, msg string, fields map[string]any) error {
formatted := l.formatter.Format("INFO", msg, fields)
return l.writer.Write(ctx, formatted)
}
// --- Formatters ---
type JSONFormatter struct{}
func (f *JSONFormatter) Format(level, msg string, fields map[string]any) string {
return fmt.Sprintf(`{"level":"%s","msg":"%s","time":"%s"}`,
level, msg, time.Now().Format(time.RFC3339))
}
type TextFormatter struct{}
func (f *TextFormatter) Format(level, msg string, fields map[string]any) string {
return fmt.Sprintf("[%s] %s %s", level, time.Now().Format("15:04:05"), msg)
}
// --- Writers ---
type FileWriter struct {
w io.Writer
}
func NewFileWriter(w io.Writer) *FileWriter {
return &FileWriter{w: w}
}
func (fw *FileWriter) Write(_ context.Context, formatted string) error {
_, err := fmt.Fprintln(fw.w, formatted)
return err
}
type RemoteWriter struct {
endpoint string
}
func (rw *RemoteWriter) Write(ctx context.Context, formatted string) error {
// Send to remote logging service...
return nil
}
Использование:
// Any combination of formatter + writer
localJSON := logger.NewLogger(&logger.JSONFormatter{}, logger.NewFileWriter(os.Stdout))
remoteText := logger.NewLogger(&logger.TextFormatter{}, &logger.RemoteWriter{endpoint: "https://logs.example.com"})
Когда использовать
Используйте, когда:
- Два или более измерения изменчивости
- Нужно менять реализацию в runtime
- Комбинаторный взрыв классов
- Кросс-платформенная разработка
Не используйте, когда:
- Одно измерение изменчивости (достаточно простого интерфейса)
- Реализация не меняется (лишняя абстракция)
- Связь между абстракцией и реализацией тривиальна
Сравнение с похожими паттернами
| Паттерн | Отличие |
|---|---|
| Adapter | Адаптирует существующий несовместимый интерфейс; Bridge проектируется заранее |
| Strategy | Меняет алгоритм; Bridge разделяет абстракцию и реализацию на двух уровнях |
| Abstract Factory | Создаёт семейства объектов; Bridge соединяет абстракцию с реализацией |