-->

TclOO Variable Scope with Inheritance/superclass

2019-06-07 01:24发布

问题:

I stumbled over the variable scope when inheriting a class with TclOO. The member variable nCrumbs below is not visible to the inherited class without repeating the declaration.

Is there a way to avoid replicating all the variable declarations from the superclass?

(I read through all the OO documentation, specifically oo::define and oo::object, also the non-exported stuff, googled for it. There are so many concepts to get around various things, I got lost. I am looking for something that keeps the inherited class as simple as possible. The superclass may have any fancy complicated code in it, though.)

Any help would be greatly appreciated, thanks.

oo::class create toaster {
    variable nCrumbs;                  #declaration

    constructor {} {
        set nCrumbs 0;                 #definition
    }

    method toast {nSlices} {
        if {$nCrumbs > 50} {
            error "== FIRE! FIRE! =="
        }
        set nCrumbs [expr $nCrumbs+4*$nSlices]
    }

    method clean {} {
        set nCrumbs 0
    }
}

oo::class create smartToaster {
    superclass toaster;                #inherit

    variable nCrumbs;                  #<======= have to declare again

    method toast {nSlices} {
        if {$nCrumbs > 40} {
            my clean
        }
        next $nSlices; #call superclass method
    }
}

set clsToaster [smartToaster new]
$clsToaster toast 2

回答1:

The variable is physically located (if you can say that for code) in a private namespace for the object instance. By doing variable in the declaration, you are directing the method bindings to merely make it available without further commands.

But yes, subclasses have to use variable as well to see it by default, or to use one of the standard Tcl variable scope management commands, e.g., upvar or namespace upvar, or even the private variable method.

oo::class create smartToaster {
    superclass toaster
    method toast {nSlices} {
        my variable nCrumbs
        if {$nCrumbs > 40} {
            my clean
        }
        next $nSlices
    }
}
oo::class create smartToaster {
    superclass toaster
    method toast {nSlices} {
        namespace upvar [namespace current] nCrumbs nc
        if {$nc > 40} {
            my clean
        }
        next $nSlices
    }
}

It didn't used to be that way, but it was found to be just too confusing otherwise; the variable declarations for a class only affect the methods of that class, not its subclasses.


[EDIT]: It is possible to make a parent's variables also visible in the child via appropriate metaclass magic:

oo::class create Class {
    superclass oo::class
    constructor args {
        next {*}$args
        set cs [info class superclasses [self]]
        while {[llength $cs]} {
            set cs [concat [lassign $cs c] [info class superclasses $c]]
            oo::define [self] variable -append {*}[info class variables $c]
        }
    }
}

Demonstrating this:

% Class create foo {
    variable x
}
::foo
% Class create bar {
    superclass foo
    variable y
}
::bar
% Class create boo {
    superclass bar
    variable z
}
::boo
% info class variables boo
z y x

While I don't in general recommend this as it makes a subclass much more fragile when a superclass evolves, and it doesn't track any changes to the superclass, it's easy to set up with a little scripting. You just delegate the smartness to your own metaclass and use that to construct all your operational classes (which are purely conventional TclOO classes from there on).



回答2:

Donal, thanks for answering.

So I am assuming there is no mechanism available by default to make all the superclass variables available.

My current solution is to collect all variable names and then declare them with one single call. Yet, I have to repeat this in every method. I would want to put the declareSuperclassVars outside the methods. Is this possible somehow? Perhaps with another approach?

Thanks

oo::class create toaster {
    variable nCrumbs;                    #declaration
    variable toTest;                     #another one nowhere defined

    constructor {} {
        set nCrumbs 0;                   #definition
    }

    method toast {nSlices} {
        if {$nCrumbs > 50} {
            error "== FIRE! FIRE! =="
        }
        set nCrumbs [expr $nCrumbs+4*$nSlices]
    }

    method clean {} {
        set nCrumbs 0
    }

    method declareSuperclassVars {} {
        my variable lSuperclassVars
        set lSuperclassVars [info vars]; #fill variable list
        uplevel 1 {
            my variable lSuperclassVars
            eval "my variable $lSuperclassVars"
        }
    }
}

oo::class create smartToaster {
    superclass toaster;                  #inherit

    #declareSuperclassVars;              #<======= would like to do it here

    method toast {nSlices} {
        my declareSuperclassVars;        #declare all at once

        if {$nCrumbs > 40} {
            my clean
        }
        next $nSlices; #call superclass method
    }
}

set clsToaster [smartToaster new]
$clsToaster toast 2