The Godot Q&A is currently undergoing maintenance!

Your ability to ask and answer questions is temporarily disabled. You can browse existing threads in read-only mode.

We are working on bringing this community platform back to its full functionality, stay tuned for updates.

godotengine.org | Twitter

0 votes

First of all, I'm quite new in GDscript and godot itself so even unrelated hints are welcome.

I'm trying to implement specification pattern in GDScript. Simplified, not-working implementation:

SpecificationInterface.gd:

extends Resource
class_name SpecificationInterface

# abstract
func is_satisfied(candidate) -> bool:
    push_error("need to be implemented")
    return false

BaseSpecification.gd:

extends SpecificationInterface
class_name BaseSpecification


class AndSpecification:
    extends BaseSpecification  # <-- cyclic dependency

    var first: SpecificationInterface
    var second: SpecificationInterface

    func is_satisfied(candidate) -> bool:
        return self.first.is_satisfied(candidate) and self.second.is_satisfied(candidate)


func and_(other: SpecificationInterface):
    var and_specification := AndSpecification.new()
    and_specification.first = self
    and_specification.second = other

    return and_specification

The problem is: AndSpecification can't extend BaseSpecification from script where it's defined. It can't be defined in separated script either because BaseSpecification use AndSpecification.

I tired some dirty hack where BaseSpecification call FuncRef of AndSpecification.is_satisfied but it's also don't work:

extends SpecificationInterface
class_name BaseSpecification

var _validation_method: FuncRef # (candidate) -> bool

func is_safisfied(candidate) -> bool:
    if self._validation_method != null:
        return self._validation_method.call_func(candidate)

    push_error("need to be implemented")
    return false


class AndSpecification:
    var first: SpecificationInterface
    var second: SpecificationInterface

    func is_satisfied(candidate) -> bool:
        return self.first.is_satisfied(candidate) and self.second.is_satisfied(candidate)


func and_(other: SpecificationInterface): # -> LogicSpecificationInterface:
    var and_specification := AndSpecification.new()
    and_specification.first = self
    and_specification.second = other

    # using own name is not allowed - cyclic dependency again 
    # var new_sepc: = BaseSpecification.new()

    # this, surprisingly, is allowed, however it duplicates 
    # my extension of  `BaseSpecification` and not pure 
    # `BaseSpecification` itself. So `is_satisfied` is already overwritten
    # and my dirty hack doesn't work.
    var new_spec: = (self as BaseSpecification).duplicate()   
    new_spec._validation_method = funcref(new_spec, "is_satisfied")
    return and_specification
Godot version v3.4.4.stable.arch_linux
in Engine by (12 points)

This is a very interesting design pattern I've never heard of until now. Can I ask what some high-level goals you have for the pattern are?
Also, this isn't really an answer, but godot supports mingling of GDScript, C#, and GDNative scripts among others. It might not be ideal, but have you considered lifting the C# example from the Wiki page you linked for your implementation? I haven't tried using C# in my projects so I'm not sure if there'd still be odd linking issues or not.

I'm working on a game where a player can build some machine from parts. There're couple kinds of parts like engine, manipulator, life-support. Each kind has it's own general rules, for example a manipulator can't be placed directly near an engine. Besides that, each particular part can have special rules, like require at least 5 energy or be connected with life-support.

On top of that, I decided that parts (and their rules) are defined as json file and loaded during gameplay. It'd make game extension/modification trivial, even for non-developers.

I can define atomic rules in code and mention them with logic operators, in json:

{
    "rule": {
        "or": [
            {"and": [{"connected_with": "ENGINE"}, {"min_energy": 5}]},
            {"connected_with": "LIFE_SUPPORT"}
        ]
    }
}

Just writing this example, it made me thinking that I.. probably don't need implementation of specification pattern after all.. I've just wrote them with normal Polish notation so it should be feasible to create combined rules in the code.
With specification pattern I could write in code:

rule = (
    ConnectedWith("ENGINE").and(MinEngery(5))
).or(Connected_with("LIFE_SUPPORT")

Which is more readable but I have no idea how wanted to parse it to json..

Oh my! Thank you! I should mention my goals in initial post to avoid xy problem.

Also, for some reason, it didn't even cross my mind that I can use multiple languages at the same time (thanks for that also!). As you said, it isn't answer I was looking for, however it should work. With small drawback: cross language inheritance is forbidden.

Ah nice, glad you figured it out! I've had lots of moments like that where, in the middle of asking for help or describing my problem again, a solution will strike me. Sounds like you know exactly what you need and how to get it.

I was not aware that cross-language inheritance was forbidden, that would definitely throw a wrench into things here or there. Makes sense, though.

Please log in or register to answer this question.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.