比较对象时,哪种设计模式比较合适

时间:2019-06-09 10:46:25

标签: c#

我最近了解了设计模式,并想更改我必须制作的一款小游戏的代码。该游戏称为SpaceTaxi。我做了一个解析器,解析一个带有ascii内容的.txt文件,生成了4种不同的实体列表:滑行,障碍物,出口和平台。在游戏循环内部,我使用这些实体检测它们是否发生碰撞来调用一种大碰撞方法。这真是丑陋的代码。有没有可以用于碰撞方法的好的设计模式?因此,与其使用大型方法,不如使用较小的类?当前看起来像这样:

/// <summary>
///     Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck() {
    var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities,
        false);
    var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities,
        false);
    var exitCollision = Player.CollisionObstacle(Parser.ExitEntities,
        false);

    // Landing on platform
    if (platformCollision.Item1 && obstacleCollision) {
        // Stand still on platform
        if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
            < Constants.COLLISION_DISTANCE) {
            Player.Shape.Direction.Y = 0;
            Player.Shape.Direction.X = 0;
            Player.OnPlatform = true;
            // Explode because of too much speed
        } else {
            AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
                0.1f, 0.1f);
        }

        // Be rewarded in case player transports a customer
        if (Player.HasCostumer) {
            foreach (var customer in pickedUpCustomers) {
                if (CorrectDestination(platformCollision.Item2,
                    customer.DestinationPlatform)) {
                    score.AddPoint(CurrentCustomer.RewardPoints);
                    customer.CanRemove = true;
                    Player.HasCostumer = false;
                }
            }
        }

        // Exit map
    } else if (exitCollision) {
        // Switch from one map to another
        if (GameRunning.CurrentMap == "the-beach.txt") {
            GameRunning.CurrentMap = "short-n-sweet.txt";


            Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
                Constants.PLAYER_ENTRYPOSITION_Y);
            Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
            Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;

            // Switch from one map to another
        } else {
            GameRunning.CurrentMap = "the-beach.txt";
            Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
                Constants.PLAYER_ENTRYPOSITION_Y);
            Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
            Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
        }

        GameRunning.Timer.Restart();
        Parser.Load(GameRunning.CurrentMap);

        allCustomersInMap = new List<Customer>();
        foreach (var c in Parser.Customer) {
            allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
                c.Value.Item2, c.Value.Item3, c.Value.Item4,
                c.Value.Item5));
        }

        // Collision with obstacle. Add explosion
    } else if (obstacleCollision) {
        AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
            Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
        TaxiBus.GetBus()
            .RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
                GameEventType.GameStateEvent, this, "CHANGE_STATE",
                "MAIN_MENU", ""));
    }

    // Collision with taxi and customer
    // CollisionCustomer returns a bool (item1) and null/customer (item2)
    if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer) {
        var customer = Player.CollisionCustomer(allCustomersInMap).Item2;

        TaxiMeterTimer = new Stopwatch();
        TaxiMeterTimer.Start();

        CurrentCustomer = customer;
        pickedUpCustomers.Add(customer);
        allCustomersInMap.Remove(customer);

        CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
        Player.HasCostumer = true;
    }
}

1 个答案:

答案 0 :(得分:0)

  

请注意,类似这样的问题更适合CodeReview,因为对于SO来说,这几乎是不重要的,因为您没有要解决的特定问题或例外。

出于各种原因,在所有应用程序中将决策逻辑与动作逻辑分开是一种很好的做法:

  • 从复杂的逻辑树中删除动作的实现,可以更轻松地可视化决策过程,前提是您认为这些动作正常工作并分别对其进行调试
  • 创建单一目的的操作方法可促进程序中不同决策逻辑树的代码重用
  • 您可以轻松地测试和调试单个动作或逻辑分支,而不会干扰或分散更大的画面
  • 您可以使用Doc Comments方法更清楚地记录您的意图

只需将您定义的动作分离出来,结果将类似于以下代码:

/// <summary>
/// Checks for collision with obstacles, platforms and exits.
/// </summary>
private void CollisonCheck()
{
    var platformCollision = Player.CollisionPlatform(Parser.PlatformEntities, false);
    var obstacleCollision = Player.CollisionObstacle(Parser.ObstacleEntities, false);
    var exitCollision = Player.CollisionObstacle(Parser.ExitEntities, false);

    // Landing on platform
    if (platformCollision.Item1 && obstacleCollision)
        DockAtPlatform(); // Stand still on platform
    else if (exitCollision)
        ExitMap(); // Exit map         
    else if (obstacleCollision)
        CollideWithObject(); // Collision with obstacle. Add explosion

    // ??? Player collision can occur at a platform or in general regions in the map
    if (Player.CollisionCustomer(allCustomersInMap).Item1 && !Player.HasCostumer)
        PickupCustomer();
}

/// <summary>
/// Dock Player with platform as long as they are not approaching too fast.
/// Collect reward if carrying a passenger
/// </summary>
/// <remarks>If too fast, player will explode!</remarks>
private void DockAtPlatform()
{
    if (Math.Abs(Player.Entity.Shape.AsDynamicShape().Direction.Y)
        < Constants.COLLISION_DISTANCE)
    {
        Player.Shape.Direction.Y = 0;
        Player.Shape.Direction.X = 0;
        Player.OnPlatform = true;
        // Explode because of too much speed
    }
    else
    {
        AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
            0.1f, 0.1f);
    }

    // Be rewarded in case player transports a customer
    if (Player.HasCostumer)
    {
        foreach (var customer in pickedUpCustomers)
        {
            if (CorrectDestination(platformCollision.Item2,
                customer.DestinationPlatform))
            {
                score.AddPoint(CurrentCustomer.RewardPoints);
                customer.CanRemove = true;
                Player.HasCostumer = false;
            }
        }
    }

}

/// <summary>
/// Switch between Maps
/// </summary>
private void ExitMap()
{
    // Switch from one map to another
    if (GameRunning.CurrentMap == "the-beach.txt")
    {
        GameRunning.CurrentMap = "short-n-sweet.txt";


        Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
            Constants.PLAYER_ENTRYPOSITION_Y);
        Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
        Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
    }
    else
    {
        // Switch the reverse way around
        GameRunning.CurrentMap = "the-beach.txt";
        Player.SetPosition(Constants.PLAYER_ENTRYPOSITION_X,
            Constants.PLAYER_ENTRYPOSITION_Y);
        Player.Entity.Shape.AsDynamicShape().Direction.Y = Constants.STILL;
        Player.Entity.Shape.AsDynamicShape().Direction.X = Constants.STILL;
    }

    GameRunning.Timer.Restart();
    Parser.Load(GameRunning.CurrentMap);

    allCustomersInMap = new List<Customer>();
    foreach (var c in Parser.Customer)
    {
        allCustomersInMap.Add(new Customer(c.Key, c.Value.Item1,
            c.Value.Item2, c.Value.Item3, c.Value.Item4,
            c.Value.Item5));
    }
}

/// <summary>
/// Show explosion because player has collided with an object, then return to the main menu
/// </summary>
private void CollideWithObject()
{
    AddExplosion(Player.Shape.Position.X, Player.Shape.Position.Y,
        Constants.EXPLOSION_WIDTH, Constants.EXPLOSION_HEIGHT);
    TaxiBus.GetBus()
        .RegisterEvent(GameEventFactory<object>.CreateGameEventForAllProcessors(
            GameEventType.GameStateEvent, this, "CHANGE_STATE",
            "MAIN_MENU", ""));
}

/// <summary>
/// Pickup a new customer, start the meter running and remove the customer from the map
/// </summary>
private void PickupCustomer()
{
    var customer = Player.CollisionCustomer(allCustomersInMap).Item2;

    TaxiMeterTimer = new Stopwatch();
    TaxiMeterTimer.Start();

    CurrentCustomer = customer;
    pickedUpCustomers.Add(customer);
    allCustomersInMap.Remove(customer);

    CurrentCustomer.SetPosition(Constants.HIDEPOS_X, Constants.HIDEPOS_Y);
    Player.HasCostumer = true;
}

现在,您可以专注于单个操作并对其进行检查,以查看是否存在更干净的方法来对其过程进行编码。

一些注意事项:

  • “添加爆炸”应该接受一个点对象,而不是通过X,Y坐标传递,这使代码更易于阅读,并且是传递始终在一起的坐标的自然方法。

  • 您应该创建特定的数据模型类以用作冲突函数的返回值,而不是使用Tuples,这听起来像是过度设计,但它有助于代码的文档编制和理解意图。

    • 花费很少的时间来定义一个类来保存碰撞响应,并且只需很少的文档,而不是将注释.Item1.Item2隐含在数据类型和逻辑含义内
    • 是的,在迅速淘汰代码解决方案方面,元组很棒,但是您必须始终查看创建值的代码以了解或识别值的含义,因此通常是第一个被重构的代码我想认真考虑解决方案时的原型代码。
  • 您可以采用以下方式:将下一个if块的注释放在上一个分支代码的内部,将注释移到受影响的if分支内部,或紧接在其上方
    • 或将分支移到其自己的方法中,然后可以使用文档注释。
  • 如果Player对象本身拥有计费表变量,则您可以接受多名乘客(最多达到车辆的容量)并在交付时收取多笔车费,您在平台上送客的动作已经假设有多名乘客。
    • 您可能已经以不同的方式满足了这一需求
  

最终注释(个人)
  尝试将方法逻辑更改为更多因果样式,如果在OO逻辑中采用更多的函数式编程样式,则可以在整体 SDLC 中获得很多好处。您可以在哪里,尤其是对于最小的工作单元,传入将作为方法的参数操作的对象,而不是引用全局对象,这样,您将发现建立单元测试和代码审查更容易个别方法。

     

即使在这个项目中您可能没有实施单元测试,但这也是一个很好的习惯,它会使您以后发布在SO上的代码示例更容易被我们其他人调试,并使您成为团队或社区发展项目中更高效的贡献者。