65.9K
CodeProject 正在变化。 阅读更多。
Home

在简化的 Pacman 游戏中使用装饰器模式

starIconstarIconstarIconstarIconstarIcon

5.00/5 (24投票s)

2013年11月30日

CPOL

11分钟阅读

viewsIcon

50663

downloadIcon

917

一个如何使用装饰者模式、依赖注入和组合根模式来扩展基础游戏功能的示例。

引言

装饰者模式是软件开发中的一项基本模式。它与依赖注入和组合根模式配合良好。这个小项目就是其使用方式的展示。

背景

我不是游戏开发者,我编写企业级应用程序。我被期望交付可扩展且可重用的代码。我曾经为此奋斗了一段时间,但自从我开始考虑依赖注入和装饰者模式来编写代码以来,情况已经大为改善。

有很多关于依赖注入、组合根和装饰者模式的入门文章,所以如果你不熟悉这些概念,我建议你先阅读它们。我是一个“给我看代码”的人,所以我将直接深入代码,并在讲解过程中解释概念。

Using the Code

为什么是吃豆人游戏克隆?我写了一篇关于井字棋游戏单元测试的CodeProject文章,并且我在某处读到吃豆人应该是第二个新手游戏开发项目。

我设定的项目目标是

  • 保持代码尽可能简单
  • 使用依赖注入和面向抽象的代码实现可扩展的游戏基础
  • 在一个地方组合一切(组合根)
  • 在组合根中装饰游戏

为了使游戏尽可能简单,没有使用任何第三方工具。所有游戏基础逻辑都放在一个类库项目MazeLib中,而GUI则实现在一个Windows Forms项目Maze中。

当使用依赖注入概念时,可以在依赖项被发送到需要它们的类之前对其进行装饰。利用这个概念,可以实现基础游戏GUI窗体,并在不修改窗体的情况下添加功能。

基础游戏看起来是这样的

通过扩展玩家对象,它可以根据移动方向改变外观。

幽灵也是如此。

所有添加游戏功能的选项都可以通过主游戏窗体顶部的选项来访问

剩余的选项使道具能够使幽灵易受攻击,暂时冻结它们,或者吃掉它们可以增加玩家得分。吃掉幽灵也是如此。

那么如何实现呢?在更传统的方法中,主窗体可能包含所有不同的代码变体,这取决于用户的选项偏好。然后就是类装饰。

装饰者模式的高层概念是这样的

装饰者模式有四个部分。第一部分,组件定义了具有所有基本方法和属性的abstract类,没有任何实现。游戏中可以装饰的类有四类——AbstractBorderAbstractGhostAbstractPickupAbstractPlayerAbstractShape是其他类的基类。它是一个泛型类,其最重要的属性是Display。它返回一个泛型对象,用于在窗体上显示形状。对于Windows Forms GUI项目,我使用了PictureBox作为泛型参数在屏幕上显示形状。类层次结构如下

第二部分模式是具体组件。这是一个非abstract类,只有基本实现。它必须继承自组件类。所以具体组件类是

装饰者模式部分是事情开始变得有趣的地方。正如模式所说,装饰者类是继承自component类的类。它们还通过构造函数获取component类的实例,并将其保存为protected变量。最后但同样重要的是,它们是abstract的。首先,类层次结构

以及GhostDecorator类的部分代码

public abstract class GhostDecorator<T> : AbstractGhost<T>
{
    protected readonly AbstractGhost<T> _ghostToDecorate;

    public GhostDecorator(AbstractGhost<T> ghostToDecorate)
    {
        if (ghostToDecorate == null)
        {
            throw new ArgumentNullException("ghostToDecorate");
        }

        _ghostToDecorate = ghostToDecorate;
    }

    public override void ResetToStartLocation()
    {
        _ghostToDecorate.ResetToStartLocation();
    }

    // some code omitted
    public override Point Location
    {
        get
        {         
            return _ghostToDecorate.Location;
        }
    }

要关注的Decorator类的要点是

  • 它是一个abstract
  • 它继承自Component
  • 它通过构造函数接收一个Component类实例,并将其保存在一个protected成员变量中
  • 所有重写的方法和属性都调用从构造函数参数接收的protected成员类实例的方法

装饰者类唯一的目的就是调用它通过构造函数接收的类的所有方法。当我们来看具体装饰者时,就会明白。

可重用的MazeLib库中还有一个重要的类是AbstractLevel。它接收玩家对象、边框、道具和幽灵对象集合,并处理对象之间的碰撞检测。当游戏结束时,它还会触发一个LevelCompleted事件。

public abstract class AbstractLevel<T>
{
    protected int _stepSize;
    protected AbstractPlayer<T> _player;
    protected List<AbstractBorder<T>> _borders;
    protected List<AbstractPickup<T>> _pickups;
    protected List<AbstractGhost<T>> _ghosts;

    public event EventHandler<AbstractLevel<T>> LevelCompleted;

    public AbstractLevel(int stepSize, AbstractPlayer<T> player, 
             List<AbstractBorder<T>> borders,
                 List<AbstractPickup<T>> pickups, 
             List<AbstractGhost<T>> ghosts)
    {
    // rest of the code omitted
} 

这些就是游戏所需的所有逻辑,因此它被分离到可重用的MazeLib库中,与GUI分开。

解决方案中的另一个项目是GUI项目Maze。它调用库方法来实现基础游戏功能,并根据需要装饰类。高层游戏逻辑是,第一部分发生在库中,其余部分由GUI项目负责。

在代码中,这是通过在一个地方组合类结构来实现的——组合根。在Windows Forms项目中,它是Program类中的Main()方法。在这里,我们为游戏创建一个类依赖图。主窗体需要一个关卡才能运行——关卡是主窗体的依赖项

static void Main()
{
    FormMain mainForm = new FormMain(level);
    mainForm.GameRestarted += (sender, e) =>
    {
            level = ComposeLevel(e.Value);
            mainForm.Level = level;
    };
 
    Application.Run(mainForm);
}

public static AbstractLevel<PictureBox> ComposeLevel(ConfigurationOptions configurationOptions)
{
    AbstractPlayer<PictureBox> player = null;
        List<AbstractBorder<PictureBox>> borders = null;
        List<AbstractPickup<PictureBox>> pickups = null;
        List<AbstractGhost<PictureBox>> ghosts = null;

        var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
        levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

        player = new PlayerPictureBoxDecorator(player);

        if (configurationOptions.PlayerFaces))
        {
            player = new PlayerPictureBoxFacesDecorator(player);
        }

        for (int i = 0; i < ghosts.Count; i++)
        {
            AbstractGhost<PictureBox> ghost = new GhostPictureBoxDecorator(ghosts[i]);

            if (configurationOptions.GhostFaces))
            {
                ghost = new GhostPictureBoxFacesDecorator(ghost);
            }
            if (configurationOptions.GhostTrackScore))
            {
                ghost = new GhostTrackScoreDecorator(ghost);
            }

            ghosts[i] = ghost;
        }

        for (int i = 0; i < pickups.Count; i++)
        {
            AbstractPickup<PictureBox> pickup = new PickupPictureBoxDecorator(pickups[i]);
                
            if (configurationOptions.PickupGhostVurneability))
            {
                pickup = new PickupGhostVulnerabilityDecorator(pickup, ghosts);
            }
            if (configurationOptions.PickupGhostTempFreeze))
            {
                pickup = new PickupGhostsTempFreezeDecorator(pickup, ghosts);
            }
            if (configurationOptions.PickupTrackScore))
            {
                pickup = new PickupTrackScoreDecorator(pickup);
            }

            pickups[i] = pickup;
        }

        return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts);
    }
}

我将把代码分解成高层概念部分

AbstractPlayer<PictureBox> player = null;
List<AbstractBorder<PictureBox>> borders = null;
List<AbstractPickup<PictureBox>> pickups = null;
List<AbstractGhost<PictureBox>> ghosts = null;
 
var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts); 

首先创建关卡边框、幽灵和玩家对象。请注意,所有对象都是abstract类。由于对象是abstract的,因此可以在将它们作为依赖项发送到关卡对象之前对其进行装饰。

这里要关注的重要事项是集合的类类型。集合最初被定义为abstract类的集合。每个集合都可以包含继承的对象。因此,在调用之后

levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

我们得到以下结果

  • AbstractPlayer<PictureBox> playerGhost<PictureBox>类的实例
  • List<AbstractBorder<PictureBox> bordersBorder<Ghost>对象的列表
  • List<AbstractPickup<PictureBox>> pickupsPickup<PictureBox>对象的列表
  • List<AbstractGhost<PictureBox>> ghostsGhost<PictureBox>对象的列表

虽然变量被定义为abstract类,但它们必须包含具体对象的实例。声明变量是启用抽象的类装饰。用装饰者模式的话来说,我们将一个变量声明为组件类型(抽象),并将其具体组件实例赋给它。

如果我们删除ComposeLevel方法中创建关卡对象依赖项之后的所有代码并返回它

public static AbstractLevel<PictureBox> ComposeLevel(ConfigurationOptions configurationOptions)
{
    AbstractPlayer<PictureBox> player = null;
    List<AbstractBorder<PictureBox>> borders = null;
    List<AbstractPickup<PictureBox>> pickups = null;
    List<AbstractGhost<PictureBox>> ghosts = null;

    var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
    levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

    // code deleted

    return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts);
}

代码会编译,但我们会得到一个空白的窗体。发生了什么?所有游戏逻辑都在那里,代码也编译通过,但窗体需要信息来知道如何绘制用于形状类中Display属性的PictureBox对象。有几种方法可以解决这个问题,一种是在将类发送到关卡类之前直接设置picture box的属性。如果我们只是在将类发送到关卡对象之前添加PictureBox属性

public static AbstractLevel<PictureBox> ComposeLevel(ConfigurationOptions configurationOptions)
{     
     AbstractPlayer<PictureBox> player = null;        
     List<AbstractBorder<PictureBox>> borders = null; 
     List<AbstractPickup<PictureBox>> pickups = null;
     List<AbstractGhost<PictureBox>> ghosts = null;
     var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
     levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

     // temp code for example

     var playerPictureBox = new PictureBox();
     playerPictureBox.BackColor = Color.Blue;
     playerPictureBox.Location = player.Location;
     playerPictureBox.Size = player.OccupiedSpace.Size;
     player.Display = playerPictureBox;

     foreach (var border in borders)
     {
         var borderPictureBox = new PictureBox();
         borderPictureBox.BackColor = Color.Green;
         borderPictureBox.Location = border.Location;
         borderPictureBox.Size = border.OccupiedSpace.Size;
         border.Display = borderPictureBox;
     }

     foreach (var pickup in pickups)
     {
         var pickupPictureBox = new PictureBox();
         pickupPictureBox.BackColor =  Color.CornflowerBlue;
         pickupPictureBox.Location = pickup.Location;
         pickupPictureBox.Size = pickup.OccupiedSpace.Size;
         pickup.Display = pickupPictureBox;
     }

     foreach (var ghost in ghosts)
     {
     	 var ghostPictureBox = new PictureBox();
         ghostPictureBox.BackColor = Color.Red;
         ghostPictureBox.Location = ghost.Location;
         ghostPictureBox.Size = ghost.OccupiedSpace.Size;
         ghost.Display = ghostPictureBox;
     }

     // temp code for example
     // code deleted

     return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts);
}   

这将按预期显示窗体和对象

但是当我们开始游戏时,幽灵和玩家似乎都没有移动。虽然它们在代码中确实在移动,但我们需要重写基类方法以包含PictureBox在幽灵和玩家移动时在屏幕上的移动。所以我们可以选择两条路:继承ghost类并在那里添加所有功能,或者添加Decorator类。

player = new PlayerPictureBoxDecorator(player);

if (configurationOptions.PlayerFaces))
{
    player = new PlayerPictureBoxFacesDecorator(player);
}

for (int i = 0; i < ghosts.Count; i++)
{
    AbstractGhost<PictureBox> ghost = new GhostPictureBoxDecorator(ghosts[i]);

        if (configurationOptions.GhostFaces))
        {
            ghost = new GhostPictureBoxFacesDecorator(ghost);
        }
        if (configurationOptions.GhostTrackScore))
        {
               ghost = new GhostTrackScoreDecorator(ghost);
        }

    ghosts[i] = ghost;
}

for (int i = 0; i < pickups.Count; i++)
{
    AbstractPickup<PictureBox> pickup = new PickupPictureBoxDecorator(pickups[i]);
               
    if (configurationOptions.PickupGhostVurneability))
    {
            pickup = new PickupGhostVulnerabilityDecorator(pickup, ghosts);
    }
    if (configurationOptions.PickupGhostTempFreeze))
    {
            pickup = new PickupGhostsTempFreezeDecorator(pickup, ghosts);
    }
    if (configurationOptions.PickupTrackScore))
    {
            pickup = new PickupTrackScoreDecorator(pickup);
    }

    pickups[i] = pickup;
}  

这就是所有装饰发生的地方。在玩家对象或幽灵、边框和道具集合被发送到关卡实例之前,它们被装饰了。正如我们之前看到的,对于PictureBox实现,我们至少需要扩展基础游戏以包含PictureBox移动的逻辑。因此,我们创建第一个具体装饰者组件,GhostPictureBoxDecorator

internal class GhostPictureBoxDecorator : GhostDecorator<PictureBox>
{
     private PictureBox _display;

     public GhostPictureBoxDecorator(AbstractGhost<PictureBox> ghostToDecorate)
            : base(ghostToDecorate)
     {
          ghostToDecorate.GhostMoved += (sender, e) =>
          {
               Display.Location = e.Location;
          };
     }

     public override PictureBox Display
     {
          get
          {
               if (_display == null)
               {
                    _display = new PictureBox()
                    {
                       Location = Location,
                       Size = OccupiedSpace.Size,
                       BackColor = Color.Red
                    };
               }
               
               return _display;
          }
          set
          {
               base.Display = value;
          }
     }

     public override void MoveLeft()
     {
          _ghostToDecorate.MoveLeft();
          OnGhostMoved();
     } 

     // some code omitted
}  

使PictureBox在屏幕上移动是一个分两步的过程——在Display属性中设置PictureBox实例

internal class GhostPictureBoxDecorator : GhostDecorator<PictureBox>
{

    // some code omitted    

    public override PictureBox Display
    {
         get
         {
              if (_display == null)
              {
                   _display = new PictureBox()
                   {
                       Location = Location,
                       Size = OccupiedSpace.Size,
                       BackColor = Color.Red
                   };
              }     
         }
} 

并在幽灵移动时通过订阅GhostMoved事件来更新PictureBox.Location属性。

public GhostPictureBoxDecorator(AbstractGhost<PictureBox> ghostToDecorate)
            : base(ghostToDecorate)
{
     ghostToDecorate.GhostMoved += (sender, e) =>
     {
         Display.Location = e.Location;
     };
} 

边框、玩家和道具的第一个具体装饰者类具有相同的职责——
设置picture box的属性,以便它们可以在窗体上显示。

拥有一个功能齐全的PictureBox游戏的最基本组合可以这样设置

public static AbstractLevel<PictureBox> ComposeLevel()
{
     AbstractPlayer<PictureBox> player = null;
     List<AbstractBorder<PictureBox>> borders = null;
     List<AbstractPickup<PictureBox>> pickups = null;
     List<AbstractGhost<PictureBox>> ghosts = null;

     var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
     levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

     player = new PlayerPictureBoxDecorator(player);

     for (int i = 0; i < borders.Count; i++)
     {
          borders[i] = new BorderPictureBoxDecorator(borders[i]);
     }
     for (int i = 0; i < ghosts.Count; i++)
     {
          ghosts[i] = new GhostPictureBoxDecorator(ghosts[i]);
     }
     for (int i = 0; i < pickups.Count; i++)
     {
          pickups[i] = new PickupPictureBoxDecorator(pickups[i]);
     }

     return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts); 
}

游戏将能正常运行——库中实现的所有逻辑都将正常工作,玩家可以吃道具但不能吃幽灵,分数未被跟踪,道具也没有额外属性。但这是装饰者概念开始闪光的地方——为现有、功能齐全的系统添加功能。让我们添加一个简单的装饰者,GhostPictureBoxFaces,它将根据幽灵的移动方向改变幽灵对象的PictureBox.Image

public class GhostPictureBoxFacesDecorator : GhostDecorator<PictureBox>
{
     public GhostPictureBoxFacesDecorator(AbstractGhost<PictureBox> ghostToDecorate)
            : base(ghostToDecorate)
     {
     }

     public override DIRECTION MoveAutomatically(IEnumerable<DIRECTION> availableDirections)
     {
          DIRECTION result = _ghostToDecorate.MoveAutomatically(availableDirections);

          if (result == DIRECTION.LEFT)
          {
               if (_ghostToDecorate.Display.BackColor == Color.Red)
               {
                    _ghostToDecorate.Display.Image = Properties.Resources.IMG_GHOST_MOVE_LEFT;
               }

               OnGhostMoved();
          }
          else if (result == DIRECTION.UP)
          {
               if (_ghostToDecorate.Display.BackColor == Color.Red)
               {
                   _ghostToDecorate.Display.Image = Properties.Resources.IMG_GHOST_MOVE_UP;
               }

               OnGhostMoved();
          }
          else if (result == DIRECTION.RIGHT)
          {
               if (_ghostToDecorate.Display.BackColor == Color.Red)
               {
                   _ghostToDecorate.Display.Image = Properties.Resources.IMG_GHOST_MOVE_RIGHT;
               }

              OnGhostMoved();
          }
          else if (result == DIRECTION.DOWN)
          {
               if (_ghostToDecorate.Display.BackColor == Color.Red)
               {
                   _ghostToDecorate.Display.Image = Properties.Resources.IMG_GHOST_MOVE_DOWN;
               }

               OnGhostMoved();
          }

          return result;
        } 
} 

MoveAutomatically是一个生成随机幽灵移动的方法。在调用了基础实现后,我们得到幽灵移动的方向,然后可以相应地更新图像。
为了将此功能添加到游戏中,我们需要在组合根中用GhostPictureBoxFaces装饰者包装GhostPictureBoxDecorator实例

public static AbstractLevel<PictureBox> ComposeLevel()
{
     AbstractPlayer<PictureBox> player = null;
     List<AbstractBorder<PictureBox>> borders = null;
     List<AbstractPickup<PictureBox>> pickups = null;
     List<AbstractGhost<PictureBox>> ghosts = null;

     var levelDataCreator = new LevelDataCreator<PictureBox>(_stepSize);
     levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts);

     player = new PlayerPictureBoxDecorator(player);

     for (int i = 0; i < borders.Count; i++)
     {
          borders[i] = new BorderPictureBoxDecorator(borders[i]);
     }
     
     for (int i = 0; i < ghosts.Count; i++)
     {
          ghosts[i] = new GhostPictureBoxFacesDecorator(new GhostPictureBoxDecorator(ghosts[i]));
     }

     for (int i = 0; i < pickups.Count; i++)
     {
          pickups[i] = new PickupPictureBoxDecorator(pickups[i]);
     }

     return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts); 
} 

这是文章中最重要的部分——到目前为止被装饰的幽灵对象的类层次结构。

我们将一步一步来

 levelDataCreator.LevelOneData(out player, out borders, out pickups, out ghosts); 

在此行之后,ghosts对象是类库中Ghost对象的实例,它们包含所有基础功能。调用之后

ghosts[i] = new GhostPictureBoxFacesDecorator(new GhostPictureBoxDecorator(ghosts[i]));

每个幽灵对象首先成为GhostPictureBoxDecorator实例,这是一个实现了Display属性并处理了移动动画的装饰者类。换句话说,Ghost对象实例被GhostPictureBoxDecorator装饰。然后,该对象被GhostPictureBoxFacesDecorator进一步装饰。

ghosts[i] = new GhostPictureBoxFacesDecorator(new GhostPictureBoxDecorator(ghosts[i])); 

添加的装饰者为现有对象增加了更多功能,在本例中,它根据幽灵的移动方向改变了显示图像。除了必需的基础PictureBox装饰者之外,所有具体的装饰者都可以通过游戏菜单获得。当选中“不同的移动面孔”选项时

玩家对象将在这一行被装饰

if (configurationOptions.PlayerFaces))
{
    player = new PlayerPictureBoxFacesDecorator(player);
}

AbstractPlayer对象实例player被装饰之后,该被装饰的实例作为依赖项被发送到关卡对象。关卡对象只是使用player对象,无论它是否被装饰。

我经常收到一个问题——你为什么不使用简单的对象继承,为什么用你花哨的装饰者来让未来的维护开发人员感到困惑?这是一个公平的问题,所有OO开发者都理解对象继承,所以有时避免装饰者是更明智的。毕竟,装饰者只是对象继承的一种变体。但是看看选项菜单。为什么我不直接扩展ghost类并在其中实现Display属性和移动呢?不需要PictureBoxDecorator类,对吧?是的,那是正确的。之后,我可以在同一个类中实现得分跟踪吗?当然。在这种情况下,对象图将更简单

但是对于更复杂的选项系统怎么办?如果没有装饰者模式,我需要一个单独的类来处理每种选项组合。这个游戏仍然实现少量选项,但随着我们向系统中添加选项,问题就会显现出来。由于player对象只有一个可选功能被添加,使用装饰者似乎有点大材小用。

但是,当两个选项添加到基本功能时,装饰者与简单继承的讨论就开始变得有意义了。

注意这里,我们需要一个单独的类来包含幽灵移动面孔和得分跟踪功能。我猜另一种方法是继承GhostFaces并实现得分跟踪的类,以及类似地从GhostScore继承并实现面孔功能的类。这样,我们就不会得到一个,而是得到两个额外的类。无论哪种情况,代码都会重复。

当有3个选项时,问题就变得非常明显了,例如对于pickup

我们有3个选项需要覆盖,因此总共有7种组合

  1. Ghost易受攻击
  2. Ghost临时冻结
  3. Score跟踪
  4. Ghost易受攻击和临时冻结
  5. Ghost易受攻击和score跟踪
  6. Ghost临时冻结和score跟踪
  7. Ghost易受攻击、ghost临时冻结和score跟踪(全部)

底线是——我们向现有系统添加的选项越多,装饰者概念就越强大。通过使用装饰者,我在组合根中实现了简单的类实例化逻辑。而不是在组合根中比较单个选项来装饰类,我会有一个逻辑来比较选项组合并创建具体类的实例。

// pseudo code

if (PickupGhostVulneability && !PickupGhostTempFreeze && !PickupTrackScore)
{
     // only PickupGhostVurneability is true, therefore
     // instantiate PickupGhostVurneable class
     // this is option combination 1 from previous list
}
else if (!PickupGhostVulneability && PickupGhostTempFreeze && !PickupTrackScore)
{
   // only PickupGhostTempFreeze is true, therefore
   // instantiate PickupFreezeGhost class
   // this is option combination 2 from previous list
}
else if (!PickupGhostVulneability && !PickupGhostTempFreeze && PickupTrackScore)
{
   // only PickupTrackScore is true, therefore
   // instantiate PickupTrackScore class
   // this is option combination 3 from previous list
}
else if (PickupGhostVulneability && PickupGhostTempFreeze && !PickupTrackScore)
{
   // PickupGhostVurneability and PickupGhostTempFreeze are both true, therefore
   // instantiate PickupGhostVulnerableFreeze class
   // this is option combination 4 from previous list
}
else if (PickupGhostVulneability && !PickupGhostTempFreeze && PickupTrackScore)
{
   // PickupGhostVulneability and PickupTrackScore are both true, therefore
   // instantiate PickupGhostVurneableTrackScore class
   // this is option combination 5 from previous list
}
else if (!PickupGhostVulneability && PickupGhostTempFreeze && PickupTrackScore)
{
   // PickupGhostTempFreeze and PickupTrackScore are both true, therefore
   // instantiate PickupFreezeGhostsTrackScore class
   // this is option combination 6 from previous list
} 
else if (PickupGhostVurneability && PickupGhostTempFreeze && PickupTrackScore)
{
   // all parameters are true, therefore
   // create class that includes all additional functionality, PickupVurneableFreezeTrackScore 
   // this is option combination 7. from previous list
}  

相反,使用装饰者,可以以更简单的方式实现相同的目标。

// actual code

if (PickupGhostVulneability)
{
     pickup = new PickupGhostVulnerabilityDecorator(pickup, ghosts);
}
if (PickupGhostTempFreeze)
{
     pickup = new PickupGhostsTempFreezeDecorator(pickup, ghosts);
}
if (PickupTrackScore)
{
     pickup = new PickupTrackScoreDecorator(pickup);
}  

最后但同样重要的是,系统可以扩展以适应未来的扩展。

回到项目代码,在创建类别的依赖项之后,它们通过关卡构造函数传递给关卡对象。

return new LevelPictureBox(_stepSize, player, borders, pickups, ghosts); 

然后显示主窗体

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var level = ComposeLevel();

    FormMain mainForm = new FormMain(level);
    mainForm.GameRestarted += (sender, e) =>
    {
        level = ComposeLevel(e.Value);
            mainForm.Level = level;
    };
 
    Application.Run(mainForm);
}

最后,当游戏重新开始时,游戏选项将作为eventargs发送,以便类可以重新组合和装饰

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var level = ComposeLevel();

    FormMain mainForm = new FormMain(level);      
   
    mainForm.GameRestarted += (sender, e) =>
    {
        level = ComposeLevel(e.Value);
        mainForm.Level = level;
    };

    Application.Run(mainForm);
} 

关注点

类装饰是一个强大的概念,基本上它是类继承的一个变体,但有一个重要的区别——它为每个类保持单一职责,可以减小代码量,并极大地提高整体代码质量。

如果您能对文章提出改进建议,请评分并评论。谢谢!

历史

  • 2013-11-30:初始版本
  • 2013-12-08:更多代码示例,讨论简单的类继承与类装饰
© . All rights reserved.