博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
TDD by example (7) -- 组合
阅读量:5119 次
发布时间:2019-06-13

本文共 7960 字,大约阅读时间需要 26 分钟。

前面已经完成了各个模块(类)的开发,是时候将之组合起来,形成可执行的程序了。首先需要一个GameEngine来驱动整个游戏的流程。
ContractedBlock.gif
ExpandedBlockStart.gif
GameEngine
public class GameEngine
{
    
private readonly Game game;
    
private readonly InputValidator validator;
    
private Action onExit;
    
private bool stop;
    
public GameEngine(Game game, InputValidator validator)
    {
        
this.game = game;
        
this.validator = validator;
        game.GameClear 
+= game_GameClear;
        game.GameOver 
+= game_GameOver;
    }
    
private void game_GameOver()
    {
        Exit(() 
=> Console.WriteLine("Game Over! The answer is {0}!", game.Answer));
    }
    
private void game_GameClear()
    {
        Exit(() 
=> Console.WriteLine("Bingo! You win the game!"));
    }
    
private void Exit(Action onExitAction)
    {
        stop 
= true;
        onExit 
= onExitAction;
    }
    
public void Run()
    {
        
while (!stop)
        {
            
string input = Console.ReadLine();
            
if (!validator.Validate(input))
            {
                Console.WriteLine(
"Unknown input format. Please input numbers like this: 1 2 3 4");
                
continue;
            }
            
int[] guessNum = GetGuessNums(input);
            Console.WriteLine(game.Guess(guessNum));
            PrintGuessHistory();
        }
        onExit();
    }
    
private void PrintGuessHistory()
    {
        Console.WriteLine(
"---------------Guess Attempts-------------------");
        Console.Write(game.GetGuessHistory());
        Console.WriteLine(
"------------------------------------------------");
        Console.WriteLine();
    }
    
private static int[] GetGuessNums(string input)
    {
        
string[] numbers = input.Split(new[] {
' '}, StringSplitOptions.RemoveEmptyEntries);
        var result 
= new int[numbers.Length];
        
for (int i = 0; i < numbers.Length; i++)
        {
            result[i] 
= Convert.ToInt32(numbers[i]);
        }
        
return result;
    }
}
GameEngine的主要作用是协调输入输出,调用Game的逻辑,驱动整个游戏运行.
主程序:
ContractedBlock.gif
ExpandedBlockStart.gif
Program
internal class Program
{
    
private static readonly ContainerBuilder builder = new ContainerBuilder();
    
private static void Main(string[] args)
    {
        SetupDependencies();
        PrintUsage();
        StartGame();
    }
    
private static void SetupDependencies()
    {
        builder.Register
<AnswerGenerator>().As<IAnswerGenerator>();
        builder.Register
<GameHistory>().As<IGameHistory>();
        builder.Register
<RandomIntGenerator>().As<IRandomIntGenerator>();
        builder.Register
<InputValidator>();
        builder.Register
<Game>();
        builder.Register
<GameEngine>();
    }
    
private static void PrintUsage()
    {
        Console.WriteLine(
"Guess number v1.0");
    }
    
private static void StartGame()
    {
        
using(var container = builder.Build())
        {
            var engine 
= container.Resolve<GameEngine>();
            engine.Run();
        }
    }
}
在SetupDependencies中,将接口与实现注册到Container当中,这样在Resolve的时候,Container就会自动寻找依赖,创建出正确的对象关系. 如果想要替换某一个组件,例如IGameHistory,只需要添加该接口的一个新的实现,然后修改注册代码,即可实现组建替换.如果将注册代码移至配置文件中,则可以在不重新编译的情况下,替换组建.
至此,我们的功能已经全部完成,但是……这还没完,我们还需要再审视一下设计,看看有没有可以改进的地方。
--------------------===============------------------
我们可以看到,Game类不仅要判断猜测的结果,还要记录,还要判断是否游戏结束,严重违反了SRP。于是,将记录历史记录和判断游戏结束的逻辑提取出来,形成GameController类,同时也需要将TestGame中的相关测试提取到TestGameController中,并作适当的修改。
ContractedBlock.gif
ExpandedBlockStart.gif
Test
[TestFixture]
public class TestGame
{
    [SetUp]
    
public void Setup()
    {
        var mockAnswerGenerator 
= new Mock<IAnswerGenerator>();
        mockAnswerGenerator.Setup(generator 
=> generator.Generate()).Returns(new[] {
1234});
        game 
= new Game(mockAnswerGenerator.Object);
    }
    
private Game game;
   
    [Test]
    
public void should_get_the_answer_string()
    {
        Assert.That(game.Answer, Is.EqualTo(
"1 2 3 4"));
    }
  
    [Test]
    
public void should_return_0A0B_when_no_number_is_correct()
    {
        Assert.That(game.Guess(
new[] {
5678}), Is.EqualTo("0A0B"));
    }
    [Test]
    
public void should_return_0A2B_when_two_numbers_are_correct_but_positions_are_not_correct()
    {
        Assert.That(game.Guess(
new[] {
3456}), Is.EqualTo("0A2B"));
    }
    [Test]
    
public void should_return_1A0B_when_one_number_is_correct_and_position_is_correct_too()
    {
        Assert.That(game.Guess(
new[] {
1567}), Is.EqualTo("1A0B"));
    }
    [Test]
    
public void should_return_2A2B_when_two_numbers_are_pisition_correct_and_two_are_nunmber_correct()
    {
        Assert.That(game.Guess(
new[] {
1243}), Is.EqualTo("2A2B"));
    }
    [Test]
    
public void should_return_4A0B_when_all_numbers_are_pisition_correct()
    {
        Assert.That(game.Guess(
new[] {
1234}), Is.EqualTo("4A0B"));
    }
}
[TestFixture]
public class TestGameController
{
    
public interface IGameObserver
    {
        
void GameOver();
        
void GameClear();
    }
    
private Mock<IGameHistory> mockHistory;
    
private Mock<IGame> mockGame;
    
private GameController controller;
    [SetUp]
    
public void Setup()
    {
        mockHistory 
= new Mock<IGameHistory>();
        mockGame 
= new Mock<IGame>();
        controller 
= new GameController(mockGame.Object, mockHistory.Object);
     
    }
    [Test]
    
public void should_record_guess_history()
    {
        mockGame.Setup(game 
=> game.Guess(It.IsAny<int[]>())).Returns("0A0B");
       
        controller.Guess(
new[] { 5678 });
        
        mockHistory.Verify(h 
=> h.Add(new[] { 5678 }, "0A0B"));
    }
    [Test]
    
public void should_fail_game_when_guess_six_times_and_still_wrong()
    {
        var wrongGuess 
= new[] { 5678 };
        var mockObserver 
= new Mock<IGameObserver>();
        mockGame.Setup(game 
=> game.Guess(wrongGuess)).Returns("0A0B");
        controller.GameOver 
+= mockObserver.Object.GameOver;
     
        
for (int i = 0; i < 5; i++)
        {
            controller.Guess(wrongGuess);
        }
        
        mockObserver.Verify(m 
=> m.GameOver(), Times.Never());
        
        controller.Guess(wrongGuess);
        
        mockObserver.Verify(m 
=> m.GameOver(), Times.Exactly(1));
        mockGame.Verify(m
=>m.Guess(wrongGuess), Times.Exactly(6));
    }
    [Test]
    
public void should_win_when_guess_correct()
    {
        var correctAnswer 
= new[] { 1234 };
        var mockObserver 
= new Mock<IGameObserver>();
        mockGame.Setup(game 
=> game.Guess(correctAnswer)).Returns("4A0B");
        controller.GameClear 
+= mockObserver.Object.GameClear;
        controller.Guess(correctAnswer);
        
        mockObserver.Verify(m 
=> m.GameClear(), Times.Exactly(1));
        mockGame.Verify(m 
=> m.Guess(correctAnswer), Times.Exactly(1));
    }
}
ContractedBlock.gif
ExpandedBlockStart.gif
Code
public interface IGame
    {
        
string Guess(int[] guess);
        
string Answer { get; }
    }
public class Game : IGame
{
    
private readonly int[] answer;
  
    
public Game(IAnswerGenerator answerGenerator)
    {
        answer 
= answerGenerator.Generate();
    }
    
public string Answer
    {
        
get { return string.Join(" ", answer.Select(a => a.ToString()).ToArray()); }
    }
    
#region IGame Members
    
public string Guess(int[] guess)
    {
        
int aCount = 0;
        
int bCount = 0;
        
for (int i = 0; i < guess.Length; i++)
        {
            
if (answer[i] == guess[i])
            {
                aCount
++;
            }
            
else if (answer.Contains(guess[i]))
            {
                bCount
++;
            }
        }
        
return string.Format("{0}A{1}B", aCount, bCount);
    }
    
#endregion
}
public class GameController
{
    
public delegate void GameEventHandler();
    
private const string CorrectGuess = "4A0B";
    
private const int MaxGuessTimes = 6;
    
private readonly IGame game;
    
private readonly IGameHistory history;
    
private int guessTimes;
    
public GameController(IGame game, IGameHistory history)
    {
        
this.game = game;
        
this.history = history;
    }
    
public string Answer
    {
        
get { return game.Answer; }
    }
    
public string Guess(int[] guess)
    {
        guessTimes
++;
        
string result = game.Guess(guess);
        RecordGuess(guess, result);
        
if (IsGameClear(result))
        {
            OnGameClear();
        }
        
else if (CanContinueGuess())
        {
            OnGameOver();
        }
        
return result;
    }
    
public event GameEventHandler GameOver;
    
public event GameEventHandler GameClear;
    
private static bool IsGameClear(string result)
    {
        
return result == CorrectGuess;
    }
    
private bool CanContinueGuess()
    {
        
return guessTimes >= MaxGuessTimes;
    }
    
protected void OnGameClear()
    {
        
if (GameClear != null)
        {
            GameClear();
        }
    }
    
protected void OnGameOver()
    {
        
if (GameOver != null)
        {
            GameOver();
        }
    }
    
public string GetGuessHistory()
    {
        
return history.GetAll();
    }
    
private void RecordGuess(int[] guess, string result)
    {
        
if (history != null)
        {
            history.Add(guess, result);
        }
    }
}
还有别忘了在SetupDependencies中注册GameController
ContractedBlock.gif
ExpandedBlockStart.gif
Program
private static void SetupDependencies()
{
    builder.Register
<AnswerGenerator>().As<IAnswerGenerator>();
    builder.Register
<GameHistory>().As<IGameHistory>();
    builder.Register
<RandomIntGenerator>().As<IRandomIntGenerator>();
    builder.Register
<InputValidator>();
    builder.Register
<Game>().As<IGame>();
    builder.Register
<GameController>();
    builder.Register
<GameEngine>();
}
至此,程序完成。
---------------------------------------------------------------------
其实代码还有很多改进的余地,比如说Game这个类,叫做MagicNumber会不会好一些?GameController改名叫Game是不是更贴切?GameHistory叫做GuessRecord是不是更容易理解?等等等等,就不再继续了。

转载于:https://www.cnblogs.com/wangyh/archive/2009/09/23/TDD-by-example-7.html

你可能感兴趣的文章
window添加右键菜单
查看>>
Window7上搭建symfony开发环境(PEAR)
查看>>
Linux内核态、用户态简介与IntelCPU特权级别--Ring0-3
查看>>
第23月第24天 git命令 .git-credentials git rm --cached git stash clear
查看>>
java SE :标准输入/输出
查看>>
一些方便系统诊断的bash函数
查看>>
jquery中ajax返回值无法传递到上层函数
查看>>
css3之transform-origin
查看>>
Master选举原理
查看>>
[ JAVA编程 ] double类型计算精度丢失问题及解决方法
查看>>
小别离
查看>>
好玩的-记最近玩的几个经典ipad ios游戏
查看>>
PyQt5--EventSender
查看>>
Sql Server 中由数字转换为指定长度的字符串
查看>>
Java 多态 虚方法
查看>>
万能的SQLHelper帮助类
查看>>
tmux的简单快捷键
查看>>
[Swift]LeetCode922.按奇偶排序数组 II | Sort Array By Parity II
查看>>
《绿色·精简·性感·迷你版》易语言,小到不可想象
查看>>
Android打包key密码丢失找回
查看>>