我目前正在尝试学习SOLID设计原则以及行为驱动开发,但我很难理解单一责任原则。我试图找到一个使用测试驱动开发的c#的好教程,但是却找不到任何有价值的东西。无论如何,在花了几天阅读之后,我决定最好的学习方法是通过经验,所以我开始创建一个小应用程序,尽我所能使用这些原则。
这是一个简单的保龄球计分器。我认为最好的方法是从最简单的部分开始工作,所以我开始在球(或投掷)水平。现在我已经创建了测试以及用于处理球(投掷)分数的界面和类,这确保它们不是无效的,即。 <10
或>0
。实现之后,我意识到球类基本上只是一个可以为空的整数,所以也许我甚至不需要它...但是现在它就在那里。
接下来,我决定添加下一个合乎逻辑的东西是框架界面和类。这是我遇到困难的地方。这是我在的地方:
namespace BowlingCalc.Frames
{
public interface IFrame : BowlingCalc.Generic.ICheckValid
{
void AddThrow(int? score);
int? GetThrow(int throw_number);
}
}
namespace BowlingCalc.Frames
{
public class Frame : IFrame
{
private List<Balls.IBall> balls;
private Balls.IBall ball;
private int frame_number;
public Frame(Balls.IBall ball, int frame_number)
{
this.ball = ball;
this.frame_number = frame_number;
balls = new List<Balls.IBall>();
check_valid();
}
public void AddThrow(int? score)
{
var current_ball = ball;
current_ball.Score = score;
balls.Add(current_ball);
}
public int? GetThrow(int throw_number)
{
return balls[throw_number].Score;
}
public void check_valid()
{
if (frame_number < 0 || frame_number > 10)
{
throw (new Exception("InvalidFrameNumberException"));
}
}
}
}
该框架通过依赖注入使用我之前实现的球类,将球得分添加到框架中。它还实现了一种返回帧中任何给定球的得分的方法。
我想做什么,以及我被困在哪里,我想添加一种方法来确保帧分数有效。 (目前只是第1-9帧的简单情况,两个球的组合分数必须为10或更小。我将在10个案例之后转向更复杂的框架。)
问题是我不知道如何以SOLID方式实现它。我正在考虑将逻辑添加到课堂中,但这似乎违背了单一责任原则。然后我想将它添加为一个单独的接口/类,然后在其得分更新时在每个帧上调用该类。我还想过创建一个单独的接口IValidator
或类似的东西,并在Frame类中实现它。不幸的是,我不知道哪些是最好的/最常用的做法。
那么,实现一种验证帧分数的方法会有什么好的SOLID方法呢?
注意:对我的代码的任何批评都是非常受欢迎的。我很高兴学会创造更好的代码,并乐意接受任何帮助。
答案 0 :(得分:3)
当我认为SRP时,我倾向于强调责任方面。反过来,该类的名称应理想地描述其责任。对于某些类,这是关于类应该“成为”的内容(如果缺少行为并且仅代表状态,框架可能是一个很好的例子),但是当你有行为责任时,名称是关于类的假设'做'。
计算分数本身是一个相当小的责任,所以让我们考虑一些稍微大一些,更自然可分解的东西。以下是保龄球比赛的一个可能细分,其中包含简单的责任和适当的偏执封装(我们都是这里的朋友,但没有人希望任何人有人作弊。)
//My job is to keep score; I don't interpret the scores
public interface IScoreKeeper : IScoreReporter
{
void SetScore(int bowlerIndex, int frameIndex, int throwIndex, int score);
}
//My job is to report scores to those who want to know the score, but shouldn't be allowed to change it
public interface IScoreReporter
{
int? GetScore(int bowlerIndex, int frameIndex, int throwIndex);
}
//My job is to play the game when told that it's my turn
public interface IBowler
{
//I'm given access to the ScoreReporter at some point, so I can use that to strategize
//(more realisically, to either gloat or despair as applicable)
//Throw one ball in the lane, however I choose to do so
void Bowl(IBowlingLane lane);
}
//My job is to keep track of the pins and provide score feedback when they are knocked down
//I can be reset to allow bowling to continue
public interface IBowlingLane
{
int? GetLastScore();
void ResetLane();
}
//My job is to coordinate a game of bowling with multiple players
//I tell the Bowlers to Bowl, retrieve scores from the BowlingLane and keep
//the scores with the ScoreKeeper.
public interface IBowlingGameCoordinator
{
//In reality, I would probably have other service dependencies, like a way to send feedback to a monitor
//Basically anything that gets too complicated and can be encapsulated, I offload to some other service to deal with it
//I'm lazy, so all I want to do is tell everybody else what to do.
void PlayGame(IScoreKeeper gameScore, IEnumerable<IBowler> bowlers, IBowlingLane lane);
}
请注意,如果您想使用此模型简单地计算得分(不玩真实游戏),您可以拥有一个存根保龄球(谁什么也不做)和一个产生一系列得分值的MockBowlingLane。 BowlingGameCoordinator负责当前的投球手,投篮和投球,因此积分得以累积。
答案 1 :(得分:2)
ICheckValid
界面的目的是什么?你在其他地方打电话给check_valid
吗?在我看来,由于frame_number
似乎实际上是Frame
的只读属性,为什么在构造函数中验证其一致性而没有任何其他接口是错误的呢? (构造函数应该生成一致的对象,因此可以随意验证传入的参数。)
但是,不应该问如何正确验证此字段,最好问一下为什么您需要frame_number
中的Frame
属性?看起来这是某个数组中此项的索引 - 您可能只是使用索引,为什么要将它存储在Frame
?您可能希望稍后编写一些if / else逻辑,例如:
if (frame_number == 10) {
// some rules
} else {
// other rules
}
但是,这不太可能是SOLID方法,因为您可能最终会在Frame
的许多部分中编写此if / else语句。相反,您可以创建基类FrameBase
,定义大部分逻辑以及在OrdinaryFrame
和TenthFrame
中实现的一些抽象方法,您可以在其中定义不同的规则。这样您就可以完全避免frame_number
- 您只需创建九个OrdinaryFrames
和一个TenthFrame
。
至于批评:你的代码似乎是抽象的球和框架,但由于某种原因忽略'抛出'或'滚动'。考虑需要添加每个卷的轨迹信息,您需要更改IFrame
界面,添加类似void SetThrowTrajectory(int throwNumber, IThrowTrajectory trajectory)
的内容。但是,如果你在例如IBallRoll
,与轨迹相关的功能很容易适合那里(以及一些布尔计算属性,例如IsStrike
,IsSpare
)。