装饰模式与外观模式

一、装饰模式

1.1 概念

装饰模式是一种用于替代继承的技术,它通过无须定义子类的方式来给对象动态的增加职责,使用对象之间的关联关系取代类之间的继承关系。装饰模式降低了系统的耦合度,可以动态增加或删除对象的职责。

定义:动态地给一个对象增加一些额外的职责,就扩展功能而言,装饰模式提供了一种比使用子类更加灵活的替代方案。

1.2 模式结构

  1. Component(抽象组件):具体组件和抽象装饰类的共同父类,声明了在具体构件中实现的业务方法,定义了可以动态增加任务对象的接口

  2. ConcreteComponent(具体组件):抽象组件的子类,用于定义具体组件对象,实现了抽象组件中声明的方法,装饰类可以给他增加额外的职责。

  3. Decorator(抽象装饰类):也是抽象组件的子类,用于给具体组件增加职责,但是具体职责在其子类中实现。它维护一个只想抽象组件对象的引用,通过该引用可以调用装饰之前组件对象的方法,并通过其子类扩展该方法,达到装饰的目的。

  4. ConcreteDecorator(具体装饰类):它是抽象装饰类的子类,负责向组件添加新的职责。每个具体装饰类都定义了一些新的行为,可以调用在抽象装饰类中定义的方法,并可以增加新的方法用于扩充对象的行为。

    image-20240701225111942

  • 核心代码:

    public abstract class Component
    {
      public abstract void operation();
    }
    
    public class ConcreteComponent : Component
    {
      public override void operation()
      {
          // 基本功能实现
          Console.WriteLine("基本功能实现");
      }
    }
    
    public class Decorator : Component
    {
      private Component component;
    
      public Decorator(Component component)
      {
          this.component = component;
      }
    
      public override void operation()
      {
          component.operation();
      }
    }
    
    public class ConcreteDecorator : Decorator
    {
      public ConcreteDecorator(Component component) : base(component)
      {
      }
      public override void operation()
      {
          base.operation();
          addBehavior();
      }
    
      public void addBehavior()
      {
          Console.WriteLine("进行扩展");
      }
    }
    public class Client
    {
      public void Main()
      {
          Component simple = new ConcreteComponent();
          ConcreteDecorator decorator = new ConcreteDecorator(simple);
          decorator.operation();
      }
    
      /*
       * 基本功能实现
       * 进行扩展
       */
    }

1.3 应用实例

为了让系统具有更好的灵活性和可扩展性,克服继承复用所带来的问题,公司使用装饰模式来重构图形界面构件库的设计。

  • Component类:抽象组件

      public abstract class Component
      {
          public abstract void display();
      }
  • ConcreteComponent类:具体实现组件

    public class Window : Component
    {
      public override void display()
      {
          Console.WriteLine("显示窗体");
      }
    }
    
    public class TextBox : Component
    {
    
      public override void display()
      {
          Console.WriteLine("显示文本框");
      }
    }
    
    public class ListBox : Component
    {
      public override void display()
      {
          Console.WriteLine("显示列表框");
      }
    }
  • ComponentDecorator类:抽象装饰类

    public class ComponentDecorator : Component
    {
      private Component component;
      public ComponentDecorator(Component component)
      {
          this.component = component;
      }
    
      public override void display()
      {
          component.display();
      }
    }
  • ConcreteDecorator类:具体装饰类

    public class ScrollBarDecorator : ComponentDecorator
    {
      public ScrollBarDecorator(Component component) : base(component)
      {
      }
    
      public override void display()
      {
          AddScrollBar();
          base.display();
      }
    
      public void AddScrollBar()
      {
          Console.WriteLine("为组件增加滚动条");
      }
    }
    
    public class BlackBorderDecorator : ComponentDecorator
    {
      public BlackBorderDecorator(Component component) : base(component)
      {
      }
      public override void display()
      {
          AddBlackBorder();
          base.display();
      }
    
      public void AddBlackBorder()
      {
          Console.WriteLine("为组件增加黑色边框");
      }
    }
  • Client类:

    Component window = new Window();
    Component blackWindow = new BlackBorderDecorator(window);
    blackWindow.display();
    Component blackScrollWindow = new ScrollBarDecorator(blackWindow);
    blackScrollWindow.display();
    /*
    * 为组件增加黑色边框
    * 显示窗体
    * 为组件增加滚动条
    * 为组件增加黑色边框
    * 显示窗体
    */

1.4 优缺点

  • 优点:
    1. 灵活性和扩展性:装饰模式可以在运行时动态地添加或者移除对象的职责,而无须修改原始类,使系统更加灵活且易于扩展。
    2. 避免类爆炸:适用继承来添加新功能会导致类的数量迅速增加,形成类爆炸,装饰模式通过组合而不是继承,会避免类爆炸。
    3. 单一职责和开闭原则:符合单一职责和开闭原则
  • 缺点:
    1. 复杂性:引入很多小的装饰类,可能会增加代码复杂性。
    2. 运行时开销:每次装饰都需要创建一个新的装饰对象。

1.5 适用场景

  1. 功能扩展:当需要在不修改现有的对象,动态地添加功能时,可以适用装饰模式,利用组合的方式去增加功能。
  2. 避免类爆炸: 当需要为多个独立的对象添加相同或类似的功能时,装饰模式可以避免创建大量的子类来实现不同组合的功能。它允许你按需组合功能,而不是静态地继承。

二、外观模式

2.1 概念

外观模式(又称门面模式)是一种使用频率很高的设计模式,它通过引入一个外观角色来简化客户端与子系统之间的交互。为复杂的子系统调用提供了一个统一的入口,使子系统与客户端的耦合度降低。外观类将客户类与子系统的内部复杂性分隔开来,客户类只需要跟外观角色打交道,不需要与子系统内部的很多对象打交道。

定义:外观模式是一种为多个复杂的子系统提供一致的接口,而使这些子系统更容易被访问的模式,该模式对外有一个统一接口,外观角色不用关心内部子系统的具体细节。

2.2 模式结构

  • Facade(外观角色):它是客户端和子系统的中介,封装了子系统的复杂性,并提供了一个接口给客户端使用。

  • SubSystem(子系统角色):实现了子系统的功能,由多个模块组成,可以是一个类或者多个类。外观角色也是子系统角色的客户端类。

    image-20240702223933612

2.3 应用实例

设计一个DVD播放器系统,它由多个子系统组成,如音频、视频、界面等

  • SubSystem:子系统

    public class Audio
    {
      public void On() { Console.WriteLine("Audio On"); }
      public void Off() { Console.WriteLine("Audio Off"); }
    }
    
    public class Video
    {
      public void On() { Console.WriteLine("Video On"); }
      public void Off() { Console.WriteLine("Viedo Off"); }
    }
    
    public class UIPanel
    {
      public void On() { Console.WriteLine("UIPanel On"); }
      public void Off() { Console.WriteLine("UIPanel Off"); }
    }
  • Facade:外观角色

    public class MediaPlayerFacade
    {
      private Audio _audio;
      private Video _video;
      private UIPanel _panel;
    
      public MediaPlayerFacade() 
      {
          _audio = new Audio();
          _video = new Video();
          _panel = new UIPanel();
      }
    
      public void Play()
      {
          _audio.On();
          _video.On();
          _panel.On();
      }
    
      public void Stop()
      {
          _audio.Off();
          _video.Off();
          _panel.Off();
      }
    }
  • Client类:

    public class Client
    {
      public void Main(string[] args)
      {
          MediaPlayerFacade mediaPlayerFacade = new MediaPlayerFacade();
          mediaPlayerFacade.Play();
    
          mediaPlayerFacade.Stop();
    
          /*
           * Audio On
           * Video On
           * UIPanel On
           * Audio Off
           * Viedo Off
           * UIPanel Off
           */
      }
    }

2.4 优缺点

  • 优点:
    1. 符合迪米特法则:外观模式使用户不需要了解子系统内部情况,只与外观模式交互,降低了应用层与子系统之间的耦合度。
    2. 简化调用、层次控制:简化了复杂系统的调用过程,只需要与外观类交互即可,将客户端与子系统分层。
  • 缺点:
    1. 不符合开闭原则:当需要增加新的子系统或移除现有系统时,可能需要修改外观类或者客户端代码,不够灵活。
    2. 可能导致系统过于复杂:过度使用外观模式可能增加系统中类的数量。

2.5 适用场景

  1. 对外提供简化接口:当需要向外部提供一个简化接口并隐藏内部复杂性时,如SDK、API、库等可以使用外观模式。
  2. 层次结构系统:在层次结构复杂的系统中,可以使用外观模式对客户端和子系统进行解耦分层。