装饰器模式:用咖啡店的加料逻辑理解动态功能扩展

62 阅读8分钟

一、故事解说:星巴克如何制作花式咖啡

假设你去星巴克点咖啡:

  1. 基础咖啡:点一杯美式咖啡(基础饮品);

  2. 添加装饰

    • 加奶泡(装饰 1):美式变拿铁;
    • 加糖浆(装饰 2):拿铁变香草拿铁;
    • 加奶油(装饰 3):香草拿铁变顶级香草拿铁;
  3. 核心逻辑:每加一种料(装饰),就给咖啡增加一种新特性,且可以随时添加或移除,不影响基础咖啡的制作流程。

装饰器模式核心:动态地给对象添加新功能,就像给咖啡添加配料一样,通过包装(装饰器)包裹原始对象,在不修改原始代码的情况下扩展功能。

二、装饰器模式核心结构(咖啡案例)

java

// 1. 抽象组件:咖啡
interface Coffee {
    String getDescription(); // 获取描述
    double getPrice(); // 获取价格
}

// 2. 具体组件:美式咖啡(基础咖啡)
class Americano implements Coffee {
    @Override
    public String getDescription() {
        return "美式咖啡";
    }
    
    @Override
    public double getPrice() {
        return 25.0;
    }
}

// 3. 抽象装饰器:咖啡装饰基类
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee; // 被装饰的咖啡
    
    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }
    
    // 委托给被装饰的咖啡
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }
    
    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice();
    }
}

// 4. 具体装饰器:加奶泡
class MilkFoamDecorator extends CoffeeDecorator {
    public MilkFoamDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + " + 奶泡";
    }
    
    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice() + 5.0;
    }
}

// 5. 具体装饰器:加糖浆
class SyrupDecorator extends CoffeeDecorator {
    private String syrupType;
    
    public SyrupDecorator(Coffee decoratedCoffee, String syrupType) {
        super(decoratedCoffee);
        this.syrupType = syrupType;
    }
    
    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription() + " + " + syrupType + "糖浆";
    }
    
    @Override
    public double getPrice() {
        return decoratedCoffee.getPrice() + 8.0;
    }
}

// 6. 客户端:组合装饰
public class CoffeeShop {
    public static void main(String[] args) {
        // 基础美式咖啡
        Coffee coffee = new Americano();
        System.out.println(coffee.getDescription() + " - 价格: ¥" + coffee.getPrice());
        
        // 美式 + 奶泡
        coffee = new MilkFoamDecorator(coffee);
        System.out.println(coffee.getDescription() + " - 价格: ¥" + coffee.getPrice());
        
        // 美式 + 奶泡 + 香草糖浆
        coffee = new SyrupDecorator(coffee, "香草");
        System.out.println(coffee.getDescription() + " - 价格: ¥" + coffee.getPrice());
    }
}

三、Android 常用装饰器模式案例与实现

案例 1:TextView 样式装饰(动态添加样式)

java

// 1. 抽象组件:文本视图
interface TextView {
    void setText(String text);
    String getText();
    void setTextSize(float size);
    float getTextSize();
}

// 2. 具体组件:基础TextView
class BaseTextView implements TextView {
    private String text;
    private float textSize = 16f; // 默认16sp
    
    @Override
    public void setText(String text) {
        this.text = text;
    }
    
    @Override
    public String getText() {
        return text;
    }
    
    @Override
    public void setTextSize(float size) {
        this.textSize = size;
    }
    
    @Override
    public float getTextSize() {
        return textSize;
    }
}

// 3. 抽象装饰器:TextView装饰基类
abstract class TextViewDecorator implements TextView {
    protected TextView decoratedTextView;
    
    public TextViewDecorator(TextView decoratedTextView) {
        this.decoratedTextView = decoratedTextView;
    }
    
    // 委托给被装饰的TextView
    @Override
    public void setText(String text) {
        decoratedTextView.setText(text);
    }
    
    @Override
    public String getText() {
        return decoratedTextView.getText();
    }
    
    @Override
    public void setTextSize(float size) {
        decoratedTextView.setTextSize(size);
    }
    
    @Override
    public float getTextSize() {
        return decoratedTextView.getTextSize();
    }
}

// 4. 具体装饰器:加粗装饰
class BoldDecorator extends TextViewDecorator {
    public BoldDecorator(TextView decoratedTextView) {
        super(decoratedTextView);
    }
    
    @Override
    public void setText(String text) {
        // 先调用原始方法设置文本
        super.setText(text);
        // 添加加粗装饰逻辑
        applyBold();
    }
    
    private void applyBold() {
        System.out.println("应用加粗样式");
        // 实际中通过SpannableString设置加粗
    }
}

// 5. 具体装饰器:斜体装饰
class ItalicDecorator extends TextViewDecorator {
    public ItalicDecorator(TextView decoratedTextView) {
        super(decoratedTextView);
    }
    
    @Override
    public void setText(String text) {
        super.setText(text);
        applyItalic();
    }
    
    private void applyItalic() {
        System.out.println("应用斜体样式");
        // 实际中通过SpannableString设置斜体
    }
}

// 6. 在Activity中使用
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 创建基础TextView
        TextView textView = new BaseTextView();
        textView.setText("装饰器模式示例");
        textView.setTextSize(18);
        
        // 添加加粗装饰
        textView = new BoldDecorator(textView);
        
        // 添加斜体装饰
        textView = new ItalicDecorator(textView);
        
        // 最终效果
        System.out.println("文本: " + textView.getText());
        System.out.println("字号: " + textView.getTextSize() + "sp");
        // 输出:
        // 应用加粗样式
        // 应用斜体样式
        // 文本: 装饰器模式示例
        // 字号: 18.0sp
    }
}

优点

  • 灵活扩展:可动态添加任意数量的样式装饰(加粗、斜体、下划线等),无需修改原始 TextView 代码;

  • 功能解耦:每种样式(加粗、斜体)独立实现,代码更易维护;

  • 符合开闭原则:新增样式装饰只需添加新类,不影响现有代码。

缺点

  • 多层包装影响性能:过多装饰器会导致方法调用栈加深,可能影响性能;
  • 调试困难:多层装饰器包裹后,调试时难以追踪原始方法调用路径。

案例 2:网络请求装饰(添加请求头、日志)

java

// 1. 抽象组件:网络请求
interface NetworkRequest {
    void send(String url);
    String getRequestHeader(String key);
}

// 2. 具体组件:基础网络请求
class BaseNetworkRequest implements NetworkRequest {
    private Map<String, String> headers = new HashMap<>();
    
    public BaseNetworkRequest() {
        // 设置基础请求头
        headers.put("Content-Type", "application/json");
    }
    
    @Override
    public void send(String url) {
        System.out.println("发送请求到: " + url);
        System.out.println("请求头: " + headers);
    }
    
    @Override
    public String getRequestHeader(String key) {
        return headers.get(key);
    }
}

// 3. 抽象装饰器:网络请求装饰基类
abstract class NetworkRequestDecorator implements NetworkRequest {
    protected NetworkRequest decoratedRequest;
    
    public NetworkRequestDecorator(NetworkRequest decoratedRequest) {
        this.decoratedRequest = decoratedRequest;
    }
    
    @Override
    public void send(String url) {
        decoratedRequest.send(url);
    }
    
    @Override
    public String getRequestHeader(String key) {
        return decoratedRequest.getRequestHeader(key);
    }
}

// 4. 具体装饰器:添加Token请求头
class TokenDecorator extends NetworkRequestDecorator {
    private String token;
    
    public TokenDecorator(NetworkRequest decoratedRequest, String token) {
        super(decoratedRequest);
        this.token = token;
    }
    
    @Override
    public void send(String url) {
        // 添加Token到请求头
        decoratedRequest.getRequestHeaderMap().put("Authorization", "Bearer " + token);
        super.send(url);
    }
    
    // 扩展方法:获取请求头Map(实际中通过接口暴露)
    protected Map<String, String> getRequestHeaderMap() {
        // 实际中通过反射或接口暴露
        return new HashMap<>();
    }
}

// 5. 具体装饰器:添加日志记录
class LoggingDecorator extends NetworkRequestDecorator {
    public LoggingDecorator(NetworkRequest decoratedRequest) {
        super(decoratedRequest);
    }
    
    @Override
    public void send(String url) {
        // 记录请求开始
        log("请求开始: " + url);
        
        // 调用原始请求
        super.send(url);
        
        // 记录请求结束
        log("请求结束");
    }
    
    private void log(String message) {
        System.out.println("[网络日志] " + message);
    }
}

// 6. 在Activity中使用
public class NetworkActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 创建基础请求
        NetworkRequest request = new BaseNetworkRequest();
        
        // 添加Token装饰
        request = new TokenDecorator(request, "user_token_123");
        
        // 添加日志装饰
        request = new LoggingDecorator(request);
        
        // 发送请求
        request.send("https://api.example.com/data");
        // 输出:
        // [网络日志] 请求开始: https://api.example.com/data
        // 发送请求到: https://api.example.com/data
        // 请求头: {Content-Type=application/json, Authorization=Bearer user_token_123}
        // [网络日志] 请求结束
    }
}

优点

  • 请求功能模块化:Token 管理、日志记录等功能独立封装,便于维护;

  • 动态添加拦截逻辑:可在运行时决定是否添加 Token、日志等装饰;

  • 与 OkHttp 拦截器兼容:本质与 OkHttp 的 Interceptor 原理一致,易于理解。

缺点

  • 装饰顺序影响结果:装饰器顺序不同可能影响请求(如先日志后 Token,或反之);
  • 状态管理复杂:装饰器之间可能需要共享状态(如 Token 装饰器需要知道日志装饰器的记录结果)。

案例 3:Drawable 装饰(动态修改图片样式)

java

// 1. 抽象组件:Drawable
interface Drawable {
    void draw(Canvas canvas);
    int getWidth();
    int getHeight();
}

// 2. 具体组件:基础Drawable(简化示例)
class BaseDrawable implements Drawable {
    private Bitmap bitmap;
    
    public BaseDrawable(Bitmap bitmap) {
        this.bitmap = bitmap;
    }
    
    @Override
    public void draw(Canvas canvas) {
        canvas.drawBitmap(bitmap, 0, 0, null);
    }
    
    @Override
    public int getWidth() {
        return bitmap.getWidth();
    }
    
    @Override
    public int getHeight() {
        return bitmap.getHeight();
    }
}

// 3. 抽象装饰器:Drawable装饰基类
abstract class DrawableDecorator implements Drawable {
    protected Drawable decoratedDrawable;
    
    public DrawableDecorator(Drawable decoratedDrawable) {
        this.decoratedDrawable = decoratedDrawable;
    }
    
    @Override
    public void draw(Canvas canvas) {
        decoratedDrawable.draw(canvas);
    }
    
    @Override
    public int getWidth() {
        return decoratedDrawable.getWidth();
    }
    
    @Override
    public int getHeight() {
        return decoratedDrawable.getHeight();
    }
}

// 4. 具体装饰器:圆角装饰
class RoundedCornersDecorator extends DrawableDecorator {
    private float cornerRadius;
    
    public RoundedCornersDecorator(Drawable decoratedDrawable, float cornerRadius) {
        super(decoratedDrawable);
        this.cornerRadius = cornerRadius;
    }
    
    @Override
    public void draw(Canvas canvas) {
        // 创建圆角矩形路径
        RectF rect = new RectF(0, 0, getWidth(), getHeight());
        Path path = new Path();
        path.addRoundRect(rect, cornerRadius, cornerRadius, Path.Direction.CW);
        
        // 裁剪画布
        canvas.save();
        canvas.clipPath(path);
        
        // 绘制原始图片
        super.draw(canvas);
        
        canvas.restore();
    }
}

// 5. 具体装饰器:边框装饰
class BorderDecorator extends DrawableDecorator {
    private int borderWidth;
    private int borderColor;
    
    public BorderDecorator(Drawable decoratedDrawable, int borderWidth, int borderColor) {
        super(decoratedDrawable);
        this.borderWidth = borderWidth;
        this.borderColor = borderColor;
    }
    
    @Override
    public void draw(Canvas canvas) {
        // 绘制原始图片
        super.draw(canvas);
        
        // 绘制边框
        Paint paint = new Paint();
        paint.setColor(borderColor);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(borderWidth);
        
        Rect rect = new Rect(0, 0, getWidth(), getHeight());
        canvas.drawRect(rect, paint);
    }
}

// 6. 在Activity中使用
public class ImageActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        // 加载原始图片
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.avatar);
        Drawable drawable = new BaseDrawable(bitmap);
        
        // 添加圆角装饰(半径10dp)
        drawable = new RoundedCornersDecorator(drawable, dp2px(10));
        
        // 添加边框装饰(宽度2dp,颜色红色)
        drawable = new BorderDecorator(drawable, dp2px(2), Color.RED);
        
        // 设置到ImageView
        ImageView imageView = findViewById(R.id.avatarImageView);
        imageView.setImageDrawable(new AndroidDrawableWrapper(drawable));
    }
    
    private int dp2px(float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
    }
    
    // 包装自定义Drawable到Android的Drawable
    private class AndroidDrawableWrapper extends Drawable {
        private Drawable wrappedDrawable;
        
        public AndroidDrawableWrapper(Drawable wrappedDrawable) {
            this.wrappedDrawable = wrappedDrawable;
        }
        
        @Override
        public void draw(Canvas canvas) {
            wrappedDrawable.draw(canvas);
        }
        
        @Override
        public void setAlpha(int alpha) {
            // 简化实现
        }
        
        @Override
        public void setColorFilter(ColorFilter cf) {
            // 简化实现
        }
        
        @Override
        public int getOpacity() {
            return PixelFormat.OPAQUE;
        }
        
        @Override
        public int getWidth() {
            return wrappedDrawable.getWidth();
        }
        
        @Override
        public int getHeight() {
            return wrappedDrawable.getHeight();
        }
    }
}

优点

  • 图片样式灵活扩展:可动态添加圆角、边框、滤镜等效果,无需修改图片加载逻辑;

  • 保持原始功能:装饰器不修改原始 Drawable 的绘制逻辑,只添加额外功能;

  • 与 Android Drawable 体系兼容:通过包装类可集成到 Android 原生 Drawable 体系。

缺点

  • 性能开销:每次绘制都需要经过装饰器链,复杂装饰可能影响渲染性能;
  • 状态同步问题:多个装饰器可能需要共享状态(如圆角装饰和边框装饰的尺寸同步)。

四、装饰器模式的适用场景与总结

适用场景

  1. 动态功能添加:需要在运行时给对象添加功能,且不修改其原始代码(如 TextView 样式、网络请求拦截);
  2. 功能组合灵活:功能可自由组合(如同时添加加粗和斜体,或只添加其中一种);
  3. 避免继承膨胀:当子类数量爆炸时(如 TextView 有 10 种样式组合,子类数 2^10=1024),装饰器模式更高效;
  4. 与现有框架兼容:如 OkHttp 的 Interceptor、Android 的 DrawableWrapper 等框架已使用装饰器模式。

核心优点

  • 灵活扩展:通过组合而非继承扩展功能,避免子类爆炸;
  • 解耦功能:每个装饰器专注单一功能,代码更易维护;
  • 动态组合:运行时自由组合装饰器,满足不同场景需求;
  • 符合开闭原则:新增功能只需添加新装饰器,不修改现有代码。

核心缺点

  • 多层包装复杂度:过多装饰器会导致代码难以理解(如装饰链过长);
  • 性能损耗:方法调用经过多层装饰器,可能有微小性能损失;
  • 调试困难:多层包装后,堆栈跟踪和调试变得困难。

Android 中的最佳实践

  • 利用系统装饰器:如 Android 的DrawableWrapper、OkHttp 的 Interceptor,避免重复造轮子;

  • 控制装饰链长度:避免过度使用装饰器,一般不超过 3-4 层;

  • 装饰器职责单一:每个装饰器只负责一个功能(如 Token、日志、缓存);

  • 结合工厂模式:用工厂创建装饰器链,避免客户端直接操作装饰器组合。

装饰器模式是 Android 开发中扩展对象功能的强大工具,尤其适合需要动态添加功能或组合多种功能的场景,理解它有助于更好地使用 OkHttp、自定义 View 等核心模块。