装饰器模式、包装器模式和适配器模式有什么区别?

31

我觉得我已经用这些模式家族很多次了,但是对于我来说,很难看出它们的区别,因为它们的 定义 很相似。基本上,所有这些模式都与将另一个对象或多个对象 包装 起来以扩展或 包装 其行为的额外内容有关。

举个例子,在 repo 模式上实现缓存机制似乎就是这种情况。下面是我可能会开始使用的快速示例C# 代码。

public interface IRepository {
  IEnumerable<T> GetItems<T>();
}

public class EntityFrameworkRepository : IRepository {
  ...
}

public class CachedRepository : IRepository {

  private IRepository _repository;
  private ICacheProvider _cache;

  public CachedRepository(IRepository repository, ICacheProvider cache) {
    this._repository = repository;
    this._cache = cache;
  }

  public IEnumerable<T> GetItems<T>() {
    ...
  }
}

这种情况适用哪种模式?能否简要澄清理论和实践上的区别?


相关:https://dev59.com/fHA75IYBdhLWcg3wFEsI - jaco0646
2个回答

36
理论上它们是相同的,区别在于“意图”:
装饰器(Decorator):
允许通过使用具有相同接口的类来包装对象来组合/添加功能。
适配器(Adapter):
允许您包装一个没有已知接口实现的对象,以便它遵循一个接口。重点是将一个接口“翻译”成另一个接口。
包装器(Wrapper):
从未听说过这个设计模式,但我认为它只是上述模式的常用名称。
你提到的例子我会归类为装饰器:CacheRepository“装饰”了IRepository以添加缓存功能。

1
好的,我正在删除我的回答。 :) - lexicore
1
我的困惑源于“包装器”作为一个独立的模式,如果我没记错的话,我肯定在某个地方读到过。与此同时,另一个人证实,“包装器”更像是一种编码技术,它允许你实现例如装饰者模式。感谢你们两位的澄清。 - Zoltán Tamási
很好!我从@lexicore的适配器答案中借鉴了一些东西,以便未来的访问者。 - Kenneth
我听说过在函数明确包装时使用"wrapper",比如wrap(myFunction(args), wrapArgs)。而当在顶部添加一行代码时使用"decorator",比如@wrap(wArgs); myFunction(args),第二种方式是对第一种方式的语法糖。 - Juan Perez

14

程序员可能会编写一个类A,这个类A的重点是持有另一个类B的对象。类A将被称为类B的包装器。为什么要让类A包裹在类B周围?是为了装饰或适配它。 装饰器和适配器都是包装器。


想象一下,如果类A是这样编写的,即通过调用其类B对象的方法来实现类B的接口,那么它可以代替类B。这没有其他意义,除了给程序员提供在调用类B对象的方法之前或之后添加一些代码的机会。这个版本的类A被称为类B的装饰器装饰器在保持接口不变的同时添加一些行为。

interface ICatInterface {
  public void wakeUp();
}

class Cat implements ICatInterface {
  public void wakeUp() {
    System.out.println("I came. I saw. I napped.");
  }
}

class YogaCat implements ICatInterface {

  private ICatInterface cat;

  public YogaCat(ICatInterface cat) {
    this.cat = cat;
  }

  public void wakeUp() {
    System.out.println("[Stretch]"); // <- This is the decoration.
    cat.wakeUp();
  }
}

请参见这个例子,展示了一种更复杂的使用装饰器模式的方式,用于在运行时组合具有不同行为的对象。


现在想象一下,类A是这样编写的,它实现了某些接口C,但主要是通过调用其类B对象的方法来实现的。这是一种将类B中可用的方法翻译成接口C的方法的方式。这个版本的类A被称为类B的适配器。就像当你想给手机充电时,有从墙壁或汽车电源转到USB端口的适配器。 适配器改变接口到另一个接口,但不一定添加任何行为。

interface TakeDirectionsInterface {
  public void turnLeft();
  public void turnRight();
  public void go();
  public void stop();
}

class Driver {
  public enum TurnDirection
  { 
    CLOCKWISE, COUNTERCLOCKWISE;
  }

  public enum FootPedal
  { 
    ACCELERATOR, BRAKE, CLUTCH;
  }

  public void turnSteeringWheel(TurnDirection direction) {
    System.out.println("Turning the steering wheel " + direction.toString() + ".");
  }

  public void pressPedal(FootPedal pedal) {
    System.out.println("Pressing the " + pedal.toString() + "pedal.");
  }
}

class DriverAdapter implements TakeDirectionsInterface {

  private Driver driver;

  public DriverAdapter(Driver driver) {
    this.driver = driver;
  }

  public void turnLeft(){
    driver.turnSteeringWheel(Driver.TurnDirection.COUNTERCLOCKWISE);
  }

  public void turnRight(){
    driver.turnSteeringWheel(Driver.TurnDirection.CLOCKWISE);
  }

  public void go(){
    driver.pressPedal(Driver.FootPedal.ACCELERATOR);
  }

  public void stop(){
    driver.pressPedal(Driver.FootPedal.BRAKE);
  }
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接