Home > Software engineering >  Design patterns to override attributes of object and validate them
Design patterns to override attributes of object and validate them

Time:08-02

GOAL:

I want to build a "market validation control engine" that validates players' actions and limits on the market.

SUMMARY:

Consider that we have a Character object that can buy only a given number of Arrow objects. The number depends on Race, Tire and Area where a lower layout of hierarchy overrides the upper; if the limit isn't specified lower layout inherits it from the upper:

enter image description here

For example:

  • Pro ELF in Forest can buy up to 2000 arrows
  • Basic Ork in Forest and Basic Ork in Desert can buy 50 arrows.

Additionally, the Character object can Buy and Sell a specific amount of arrows on the market. The amount and action depend on the Area. Otherway to say is every node might have its validation rules. For example:

  • Basic Elf in Desert can sell 50% arrows
  • Basic Orc in Desert can sell 100% of the arrows.

Lastly, based on the Tier players can Exchange arrows to another item. For example :

  • Pro ELF can exchange 100% of its arrows

STRUGGLES:

I am almost sure that I need multiple different patterns here. Currently, I'm thinking of the Builder pattern for building hierarchy object with all the limits and the Chain of Responsibility for the rule validation. However, I am not sure how well these patterns will work together in the long ran.

CodePudding user response:

There's quite a few different ways to implement this so I'm assuming you're not really sure where to start.

I've written up some example code using classes. In this, there's 2 ways that we could store the item (arrow) data.

Nested Array - This allows for a more "structured" approach. It also allows you to easily add more attributes to each "child" array if necessary.

Serialized Map - This has a much smaller code footprint and is generally quicker.

NOTE: This code is not setup to be well-performing (in reference to speed) but as a way to see how you could setup this system.

There are 3 files here index.php, Character.class.php, Market.class.php.

There are 2 main methods on the Market class: getNestedLimit() and getSerializedLimit(). Each one shows how you would get the limit based on those example structures.

<?php
// index.php

require_once __DIR__ . "/Character.class.php";
require_once __DIR__ . "/Market.class.php";

$market = new Market;

$orc_a = new Character("orc", "basic", "desert");
$orc_b = new Character("orc", "pro", "forest");

$market->getNestedLimit($orc_a, "arrow"); // 50
$market->getNestedLimit($orc_b, "arrow"); // 150

$elf_a = new Character("elf", "pro", "forest");
$elf_b = new Character("elf", "pro", "desert");

$market->getSerializedLimit($elf_a, "arrow"); // 2000
$market->getSerializedLimit($elf_b, "arrow"); // 50

<?php 
// Character.class.php
class Character {
    public string $race;
    public ?string $tier = null;
    public ?string $area = null;

    public function __construct(
        string $race,
        ?string $tier = null,
        ?string $area = null,
    ) {
        $this->race = $race;
        $this->tier = $tier;
        $this->area = $area;
    }
}
// Market.class.php
class Market {
    public $nested_items = [
        "arrow" => [
            "limit" => [
                "_base" => 0,
                "race" => [
                    "orc" => [
                        "_base" => 50,
                        "tier" => [
                            "basic" => [
                                "area" => [],
                            ],
                            "pro" => [
                                "area" => [
                                    "desert" => 50,
                                    "forest" => 150,
                                ],
                            ],
                        ],
                    ],
                    "elf" => [
                        "_base" => 1000,
                        "tier" => [
                            "basic" => [
                                "area" => [
                                    "desert" => 150,
                                    "forest" => 1000,
                                ],
                            ],
                            "pro" => [
                                "area" => [
                                    "desert" => 50,
                                    "forest" => 2000,
                                ],
                            ],
                        ],
                    ],
                ],
            ],
        ],
    ];

    public function getNestedLimit(Character $character, string $item_id) {
        $item  = $this->nested_items[$item_id];
        $item_limit = $item["limit"];

        $actual_limit = $item_limit["_base"];

        // check race
        if (isset($item_limit["race"][$character->race])) {
            $race_limit = $item_limit["race"][$character->race];
            if (isset($race_limit["_base"])) {
                // value exists - use this
                $actual_limit = $race_limit["_base"];
            }

            // check tier
            if (isset($race_limit["tier"][$character->tier])) {
                $tier_limit = $race_limit["tier"][$character->tier];
                if (isset($tier_limit["_base"])) {
                    // value exists - use this
                    $actual_limit = $tier_limit["_base"];
                }

                // check area
                if (isset($tier_limit["area"][$character->area])) {
                    $actual_limit = $tier_limit["area"][$character->area];
                }
            }
        }

        return $actual_limit;
    }

    /** Serialize syntax: 
     * {race}[-{tier}[-{area}]]
     * e.g.
     *     "Pro tier orc in forest" -> "o-p-f"
     *     "Basic tier elf" -> "e-b"
     *     "Orc" -> "o"
     *
     * When determining the limit, pick the most specific first.
     */
    public $serialized_items = [
        "arrow" => [
            "limit" => [
                "_base" => 0,
                // orc
                "o" => 50,
                "o-p-d" => 50,
                "o-p-f" => 150,

                // elf
                "e" => 1000,
                "e-b-d" => 150,
                "e-b-f" => 1000,
                "e-p-d" => 50,
                "e-p-f" => 2000,
            ]
        ]
    ];

    /** This "map" is purely for example - you may end up using ids 
     *  or something more consistent.
     *  I would recommend using a different system, such as enums or constants.
     */
    public $serial_keys = [
        "orc"    => "o",
        "elf"    => "e",
        "basic"  => "b",
        "pro"    => "p",
        "desert" => "d",
        "forest" => "f",
    ];

    public function getSerializedLimit(Character $character, string $item_id) {
        $item = $this->serialized_items[$item_id];
        $item_limit = $item["limit"];

        $actual_limit = $item_limit["_base"];
        
        // serialize character
        // this could be done in various ways - this is purely for example

        // start with most specific
        $serial = $this->serial_keys[$character->race] . "-" . $this->serial_keys[$character->tier] . "-" . $this->serial_keys[$character->area];

        while ($serial !== "") {
            if (isset($item_limit[$serial])) {
                $actual_limit = $item_limit[$serial];
                break;
            }

            // remove most specific section
            // here we are expected each section to be a single character
            // be removing 2 characters, we remove the hyphen as well
            // NOTE: this hyphen is only here to help visualize
            //       you could very easily use 1 character without
            //       the hyphen and change this to `-1`
            $serial = substr($serial, -2);
        }

        return $actual_limit;
    }
}

CodePudding user response:

There are two questions actually.

  1. How to build the character hierarchy with design pattern
  2. How to rule the market with validation

This is my modeling suggestion enter image description here

To the first problems. There are different properties to define a Character. Race and Tier. I am not sure if Area is the location of the character position. Or it is the biome of the character. If latter, then the character properties become three. I suggest to model the hierarchy with composition over inheritance. Otherwise, using traditional inheritance would result in many combination of classes (default Orc, Basic Orc, Pro Orc etc). Not to mention if more Race or Tier to come in the future.

To the second problem, I would not recommend chain of responsibility. Because with CoR, you need to define a long chain. Each processing object handle a variation of Character. You will need many processing object similar to the first problem.

I suggest to have a data structure registered what kind of Character could trade how many items. In my suggestion this is the responsibility of MarketAuthority. Imagine it has a map-like structure inside, and each entry it has a record call MarketRegulation. MarketRegulation define what kind of Character can buy or sell how many items. When trade happens, pass the character to MarketAuthority and find which MarketRegulation match the character. From the MarketRegulation result you know how many the character can trade.

  • Related