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





5.00/5 (24投票s)
一个如何使用装饰者模式、依赖注入和组合根模式来扩展基础游戏功能的示例。
引言
装饰者模式是软件开发中的一项基本模式。它与依赖注入和组合根模式配合良好。这个小项目就是其使用方式的展示。
背景
我不是游戏开发者,我编写企业级应用程序。我被期望交付可扩展且可重用的代码。我曾经为此奋斗了一段时间,但自从我开始考虑依赖注入和装饰者模式来编写代码以来,情况已经大为改善。
有很多关于依赖注入、组合根和装饰者模式的入门文章,所以如果你不熟悉这些概念,我建议你先阅读它们。我是一个“给我看代码”的人,所以我将直接深入代码,并在讲解过程中解释概念。
Using the Code
为什么是吃豆人游戏克隆?我写了一篇关于井字棋游戏单元测试的CodeProject文章,并且我在某处读到吃豆人应该是第二个新手游戏开发项目。
我设定的项目目标是
- 保持代码尽可能简单
- 使用依赖注入和面向抽象的代码实现可扩展的游戏基础
- 在一个地方组合一切(组合根)
- 在组合根中装饰游戏
为了使游戏尽可能简单,没有使用任何第三方工具。所有游戏基础逻辑都放在一个类库项目MazeLib
中,而GUI则实现在一个Windows Forms项目Maze中。
当使用依赖注入概念时,可以在依赖项被发送到需要它们的类之前对其进行装饰。利用这个概念,可以实现基础游戏GUI窗体,并在不修改窗体的情况下添加功能。
基础游戏看起来是这样的
通过扩展玩家对象,它可以根据移动方向改变外观。
幽灵也是如此。
所有添加游戏功能的选项都可以通过主游戏窗体顶部的选项来访问
剩余的选项使道具能够使幽灵易受攻击,暂时冻结它们,或者吃掉它们可以增加玩家得分。吃掉幽灵也是如此。
那么如何实现呢?在更传统的方法中,主窗体可能包含所有不同的代码变体,这取决于用户的选项偏好。然后就是类装饰。
装饰者模式的高层概念是这样的
装饰者模式有四个部分。第一部分,组件定义了具有所有基本方法和属性的abstract
类,没有任何实现。游戏中可以装饰的类有四类——AbstractBorder
、AbstractGhost
、AbstractPickup
、AbstractPlayer
。AbstractShape
是其他类的基类。它是一个泛型类,其最重要的属性是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> player
是Ghost<PictureBox>
类的实例List<AbstractBorder<PictureBox> borders
是Border<Ghost>
对象的列表List<AbstractPickup<PictureBox>> pickups
是Pickup<PictureBox>
对象的列表List<AbstractGhost<PictureBox>> ghosts
是Ghost<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种组合
- 仅
Ghost
易受攻击 - 仅
Ghost
临时冻结 - 仅
Score
跟踪 Ghost
易受攻击和临时冻结Ghost
易受攻击和score
跟踪Ghost
临时冻结和score
跟踪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:更多代码示例,讨论简单的类继承与类装饰