简体   繁体   English

比较对象时适合的设计模式

[英]What design pattern would be appropriate when comparing objects

I recently learned about design patterns and wanted to change my code for a small game I had to make. 我最近了解了设计模式,并希望为我必须制作的小游戏更改我的代码。 The game is called SpaceTaxi . 这个游戏叫做SpaceTaxi I've made a parser, parsing a .txt file with ascii content resulting in 4 different lists of entities: taxi, obstacle, exit and platform. 我做了一个解析器,解析了一个带有ascii内容的.txt文件,产生了4个不同的实体列表:出租车,障碍物,出口和平台。 Inside the gameloop I call a big collision-method using these entities detecting whether they collide. 在游戏循环中,我使用这些实体调用一个大碰撞方法来检测它们是否发生碰撞。 It's really ugly code. 这真是丑陋的代码。 Is there a good design pattern I could use for my collision method? 有没有一个好的设计模式我可以用于我的碰撞方法? So instead of having a big method then have smaller classes? 那么不是有一个大方法,而是有更小的类? Currently it looks like this: 目前它看起来像这样:

/// <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;
    }
}

Please note that questions like this are better suited for CodeReview as this is pretty much off topic for SO because you do not have a specific problem or exception that you are trying to resolve. 请注意,像这样的问题更适合CodeReview,因为这几乎不是SO的主题,因为您没有遇到要解决的特定问题或异常。

It is good practise in all applications to separate out decision logic from action logic, for a number of reasons: 在所有应用程序中,将决策逻辑与动作逻辑分离是一种很好的做法,原因如下:

  • Removing the implementation of actions from complex logic trees makes it easier to visualise the decision process, you assume that the actions work correctly and debug them separately 从复杂的逻辑树中删除操作的实现使得更容易可视化决策过程,您假设操作正常工作并单独调试它们
  • Creating single purpose action methods promotes code re-use from different decision logic trees in your program 创建单一用途的操作方法可以促进程序中不同决策逻辑树的代码重用
  • You can easily test and debug individual actions or logic branches without disrupting or being distracted by the bigger picture . 您可以轻松地测试和调试单个操作或逻辑分支,而不会中断或被大局分散注意力。
  • You can more clearly document your intent with method Doc Comments 您可以使用Doc Comments方法更清楚地记录您的意图

Just separating out the actions you have defined results in code similar to this: 只是将您已定义的操作分离出来会产生与此类似的代码:

/// <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;
}

Now you can focus on the individual actions and review them to see if there are cleaner ways to code their process. 现在,您可以专注于各个操作并查看它们,看看是否有更简洁的方法来编写其流程。

A few notes to consider: 需要考虑的几点注意事项:

  • Add Explosion should accept a point object instead of passing through the X,Y coordinates, it makes the code easier to read and is a natural way to pass coordinates that always go together. Add Explosion应该接受一个点对象而不是通过X,Y坐标,它使代码更容易阅读,并且是一种自然的方式来传递始终在一起的坐标。

  • Instead of using Tuples, you should create specific data model classes to use as return values from the collision functions, it feels like over-engineering, but it helps both with documentation of your code and to understand the intent. 您应该创建特定的数据模型类作为碰撞函数的返回值,而不是使用元组,它感觉就像过度工程,但它既有助于文档编写,也有助于理解意图。

    • Takes little time to define a class to hold the collision response, with minimal documentation, instead of burying inside comments what .Item1 and .Item2 might be in terms of data type and logical meaning 花费很少的时间来定义一个类来保存碰撞响应,只需要很少的文档,而不是在注释中嵌入.Item1.Item2可能在数据类型和逻辑含义方面
    • Yes Tuples are awesome, in terms of rapidly smashing code solutions out, but you always have to review the code that creates the values to understand or identify the meaning behind the values, so these are usually the first to be refactored out of my prototyping code when I want to get serious about a solution. 是的,在快速粉碎代码解决方案方面,元组非常棒,但是你总是需要查看创建值的代码来理解或识别值背后的含义,所以这些通常是我的原型代码中首先重构的代码当我想认真对待解决方案时。
  • You have a style of placing the comments for the next if block inside the previous branch code, move the comments to inside the affected if branch, or immediately above it 您可以将样式放在上一个分支代码中的下一个if块中,将注释移动到受影响的if分支内部,或者在其上方
    • Or move the branch into its own method and you can use Doc comments. 或者将分支移动到自己的方法中,您可以使用Doc注释。
  • If the Player object itself holds the meter variables, you could accept multiple passengers (up to the capacity of the vehicle) and collect multiple fares on delivery, your action for dropping off a customer at a platform already assumes multiple passengers. 如果玩家对象本身拥有计量器变量,您可以接受多个乘客(达到车辆的容量)并在交付时收取多个票价,您在平台上放下客户的行为已经假定多个乘客。
    • You may have already catered for this in different ways 您可能已经以不同的方式迎合了这一点

Final Note (a personal one) 最后的笔记 (个人笔记
Try to change your method logic into more cause and effect style, you gain a lot of benefits in the overall SDLC if you adopt more functional programming styles into your OO logic. 尝试将方法逻辑更改为更多因果关系样式,如果在OO逻辑中采用更多函数式编程样式,则可以在整个SDLC中获得很多好处。 Where you can, and especially for the smallest units of work, pass in the objects that will be operated on as arguments for your methods rather than referencing global objects, in this way you will find it easier to establish unit testing and code review of your individual methods. 尽可能地,特别是对于最小的工作单元,传入将作为方法参数操作的对象而不是引用全局对象,这样您将发现更容易建立单元测试和代码审查个别方法。

Even though in this project you may not implement unit tests, it is a very good habit to evolve, it will make future code examples that you post on SO easier for the rest of us to debug and will allow you to be a more efficient contributor in a team or community development project. 即使在这个项目中你可能没有实现单元测试,这是一个很好的发展习惯,它会使你发布的未来代码示例更容易让我们其他人调试,并使你成为一个更有效的贡献者在团队或社区发展项目中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM