阅读 246

装饰模式:Decorator Pattern

装饰模式简介

装饰模式(Decorator Pattern)也被称为包装模式(Wrapper Pattern),是结构型设计模式之一,可以在不改变一个对象本身功能的基础上给对象增加额外的新行为,以对客户端透明的方式来动态扩展对象的功能(注意,并不是改变对象本质),同时也是一种很好的替代继承关系的方案。在现实生活中也可以看到很多装饰模式的例子。

生活中的例子

穿衣服是使用装饰的一个例子。 觉得冷时, 你可以穿一件毛衣。 如果穿毛衣还觉得冷, 你可以再套上一件夹克。 如果遇到下雨, 你还可以再穿一件雨衣。 所有这些衣物都 “扩展” 了你的基本行为, 但它们并不是你的一部分, 如果你不再需要某件衣物, 可以方便地随时脱掉。

代码实现

人总是要穿衣服的,将人定义为抽象类,穿衣服的行为定义为一个抽象方法:

/**
* 需要被装饰类的基类
*/
public abstract class Person {
  
  public abstract void dressed();
}

/**
* 具体需要被装饰的"对象"
*/
public class Boy extends Person {
  
  @Override
  public void dressed(){
    System.out.println("穿了内衣");
  }
}
复制代码

Boy 类继承自 Person,该类仅对 Person 中的 dressed 方法作了具体实现,Boy 类则是我们所要装饰的具体“对象”,现在需要一个装饰者来装饰 Boy 对象。

/**
* 装饰器基类
*/
public abstract class PersonCloth extends Person {
  
  protected Person mPerson;// 保持一个对 Person 对象的引用
  
  public PersonCloth(Person mPerson) {
    this.mPerson = mPerson;
  }
  
  @Override
  public void dressed(){
    mPerson.dressed();
  }
}

/**
* 装饰器一
*/
public class ExpensiveCloth extends PersonCloth {
  
  public ExpensiveCloth(Person person) {
    super(person);
  }
  
  @Override
  public void dressed(){
    super.dressed();
    dressLeather();
    dressJean();
  }
  
  private void dressLeather() {
    System.out.println("穿件皮衣");
  }
  
  private void dressJean() {
    System.out.println("穿条牛仔裤");
  }
}

/**
* 装饰器二
*/
public class CheapCloth extends PersonCloth {
  
  public CheapCloth(Person person) {
    super(person);
  }
  
  @Override
  public void dressed(){
    super.dressed();
    dressShorts();
  }
  
  private void dressShorts() {
    System.out.println("穿条短裤");
  }
}
复制代码

客户端调用:

public class Client {
  
  public static void main(String[] args) {
    Person person = new Boy();
    PersonCloth clothCheap = new CheapCloth(person);
    // clothCheap.dressed();
    PersonCloth clothExpensive = new ExpensiveCloth(clothCheap);
    // PersonCloth clothExpensive = new ExpensiveCloth(new CheapCloth(person));
    clothExpensive.dressed();
  }
}
复制代码

输出:

穿了内裤
穿条短裤
穿件皮衣
穿条牛仔裤
复制代码

装饰模式结构

  • Component 声明封装器和被封装对象的公用接口;
  • Concrete Component 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为;
  • Base Decorator 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象;
  • Concrete Decorators 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为;
  • Client 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。

装饰模式适用性

  • 如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
  • 装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。
  • 如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以考虑该模式。
  • 许多编程语言使用 final关键字来限制对某个类的进一步扩展。 复用最终类已有行为的好方法通常就是使用装饰模式: 用封装器对其进行封装。

装饰模式优缺点

  • 优点
    • 无需创建新子类即可扩展对象的行为;
    • 可以在运行时添加或删除对象的功能;
    • 可以用多个装饰封装对象来组合几种行为;
    • 较好的遵循单一职责原则。 可以将实现了许多不同行为的一个大类拆分为多个较小的类。
  • 缺点
    • 在封装器栈中删除特定封装器比较困难;
    • 实现行为不受装饰栈顺序影响的装饰比较困难;

与其他设计模式比较

  • 组合模式

    • 组合和装饰的结构图很相似, 两者都依赖递归组合来组织无限数量的对象;
    • 装饰只有一个子组件。 此外,装饰为被封装对象添加了额外的职责, 组合对其子节点的结果进行了 “求和”。
  • 责任链模式

    • 两者也是依赖递归组合将需要执行的操作传递给一系列对象;
    • 责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。但装饰无法中断请求的传递。
  • 策略模式

    • 装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。

真假装饰模式的讨论

“在一个真正的装饰模式中,每一个装饰者都不允许修改被装饰对象的行为,只能扩展其功能。从代码的角度讲,每一个装饰者在重写的某个方法中都要直接或间接调用到被装饰对象的实现方法,否则不能称之为装饰模式。”

实例

Android 中 Context、Contextwrapper、Activity 等结构就使用了装饰模式

/**
 * Interface to global information about an application environment.  This is
 * an abstract class whose implementation is provided by the Android system. 
 * ...
 */
public abstract class Context {
  
  public abstract Resources getResources();
  ...
}

/**
 * Common implementation of Context API, which provides the base
 * context object for Activity and other application components.
 */
class ContextImpl extends Context {
  
  	@Override
    public Resources getResources() {
        return mResources;
    }
    ...
}

/**
 * ... Can be subclassed to modify behavior without changing
 * the original Context.
 */
public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
  
    @Override
    public Resources getResources() {
        return mBase.getResources();
    }
    ...
}

/**
 * A context wrapper that allows you to modify or replace the theme of the
 * wrapped context.
 */
public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater;
    private Configuration mOverrideConfiguration;
    private Resources mResources;
  
    @Override
    public Resources getResources() {
        if (mResources == null) {
            if (mOverrideConfiguration == null) {
                mResources = super.getResources();
            } else {
                final Context resContext =    createConfigurationContext(mOverrideConfiguration);
                mResources = resContext.getResources();
            }
        }
        return mResources;
    }
}

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
        ...  
}
复制代码

参考

Article by Panxc