What’s a good way to allow overwriting files within a child theme if I want the same folder structure?

Because you’re using Composer, none of the normal methods of providing child theme support apply.

For a theme that doesn’t use composer, you can allow a child theme to replace a file by including it using the get_theme_file_path() function. So in functions.php, instead of:

require_once 'Core\Utilities\Gallery.php';

You would use:

require_once get_theme_file_path( 'Core\Utilities\Gallery.php' );

get_theme_file_path() will return the full path to the given file in the child theme, if it exists, otherwise it will give the path for it in the parent theme. This means that any file included in this way can be replaced by placing a file with that same name and path in the child theme.

For a theme that use Composer to autoload class files, this doesn’t apply because you’re not manually including PHP files by their path. Instead you’re probably using Composer’s autoloader by adding something like this in your composer.json file:

"autoload": {
    "psr-4": {
        "Product\\Theme\\Core\\": "Core/"
    }
},

So a class like Product\Theme\Core\Utilities\Gallery will be loaded from Core\Utilities\Gallery.php. Since the directory is relative to the parent theme’s directory, and Composer is doing the actual including, you can’t use get_theme_file_path().*

One way to allow child themes to replace classes is to add the child theme folder to your namespace’s autoloader in composer.json:

"autoload": {
    "psr-4": {
        "Product\\Theme\\Core\\": [ "../child-theme/Core", "Core/" ]
    }
},

That will work, but the obvious problem is that it requires the child theme’s name to be hardcoded into the parent theme. So it’s not really a workable solution.

However, if a child theme uses composer itself, it can register one of its directories as a source for files in the parent theme’s namespace. For example, if the child theme used composer and added this to its composer.json file:

"autoload": {
    "psr-4": {
        "Product\\Theme\\Core\\": "Core/"
    }
},

Then it could replace class files in the parent theme by placing copies in its own Core/ directory, because composer would look inside the child theme for the class file too.

For this to work though you need to make sure that the child theme’s vendor/autoload.php file loads after the parent theme’s, or PHP will look in the parent theme first, because the parent theme’s autoloader was registered last. This is because the child theme’s functions.php file loads before the parent’s. The solution to this is for the child theme to load its autoloader inside the after_setup_theme hook:

add_action(
    'after_setup_theme',
    function() {
        require_once 'vendor/autoload.php';
    }
);

You’ll probably notice that with this solution the parent theme doesn’t actually do anything. This is the effort a child theme could go to to replace a class, and could be done even if the parent theme didn’t intend it.

* For all I know there’s a way to configure Composer so that this is possible, but I’m not aware of it.