Search code examples
oopgodotgdscript

Clean code: preventing unexpected behavior when caching mutable instances


Suppose I have the following 2 classes:

extends Resource
class_name MenuItem

export var food: Texture = preload("Burger.png")
export var drink: Texture = preload("Soda.png")

extends Node
class_name Meal

# "Public" variable
var menu_item: MenuItem setget _set_menu_item

# "Private" variables
onready var _food: Sprite = $Food
onready var _drink: Sprite = $Drink


func _set_menu_item(value: MenuItem) -> void:
    menu_item = value
    _food.texture = menu_item.food
    _drink.texture = menu_item.drink
    

The problem is that I bypass the mutator if I cache the menu_item property:

extends Node

onready var meal: Meal = $Meal
onready var menu_item: MenuItem = meal.menu_item


func _ready() -> void:
    # Will trigger `_set_menu_item` and update `_food.texture`
    meal.menu_item.food = load("Salad.png")
    
    # Won't trigger `_set_menu_item` and won't update `_food.texture`
    menu_item.food = load("Soup.png")

While keeping both classes mutable, how would you ensure that Meal always behaves as expected and updates its sprites when menu_item is changed?


Solution

  • I would do that using custom signal, which I would emit from MenuItem's member mutator and connect to a callback that updates the private Meal's value.

    extends Resource
    class_name MenuItem
    
    # I use floating point "weight" instead of your Texture variables "food" and "drink"
    # But everything should be same for Textures
    export var weight: float = 0.0  setget _set_weight
    signal weight_changed
    
    func _set_weight(value: float) -> void:
        if weight != value:
            weight = value
            emit_signal("weight_changed")
    

    extends Node
    class_name Meal
    
    # "Public" variable
    var menu_item: MenuItem = MenuItem.new()
    
    # "Private" variables
    var _weigth: float = 0.0
    
    func _ready():
        menu_item.connect("weight_changed", self, "_set_weight")
    
    func _set_weight():
        _weigth = menu_item.weight
    

    extends Node
    
    onready var meal: Meal = $Meal
    onready var menu_item: MenuItem = meal.menu_item
    
    func _ready():
        # Will trigger `_set_weight` and update `_weight`
        meal.menu_item.weight = 2.0
        # Will do that also
        menu_item.weight = -2.0