0

I'm trying to implement a hierarchical structure of classes/subclasses objects such as:

|-- Class1                  # mainClass
|   |-- SubClassA           # subClass
|   `-- SubClassB           # subClass
`-- Class2                  # mainClass
    |-- SubClassA           # subClass
    `-- SubClassB           # subClass

The main point here is to be able to declare different subclasses having the same name in each main classes (and have their variables independant).

(Note: I'm talking here about hierarchy of classes objects created during execution, not about heritage of different classes.)

Quite not very familiar with some advanced aspects specific to Tcl (namespace, scope...), I tried the following code:

package require Itcl

itcl::class subClass {
  variable InternalVariable
  constructor {} {
    puts "($this)     Current namespace      : [namespace current]"
    puts "($this)     InternalVariable scope : [itcl::scope InternalVariable]"
  }
}

itcl::class mainClass {
  variable SubClassesList
  constructor {} {
    set SubClassesList {}
    puts "($this)     current namespace      : [namespace current]"
    puts "($this)     SubClassesList scope   : [itcl::scope SubClassesList]"
  }
  method newSubClass {argName} {
    lappend SubClassesList [subClass $argName]
    puts "($this)     SubClassesList         : {$SubClassesList}"
  }
}

# Create the two main classes
mainClass Class1
mainClass Class2

# Add some subclasses to Class1 and Class2
Class1 newSubClass SubClassA
Class1 newSubClass SubClassB
Class2 newSubClass SubClassC
Class2 newSubClass SubClassB

which produces an error when creating the second occurence of SubClassB:

(::Class1)     current namespace      : ::mainClass
(::Class1)     SubClassesList scope   : @itcl ::Class1 ::mainClass::SubClassesList
(::Class2)     current namespace      : ::mainClass
(::Class2)     SubClassesList scope   : @itcl ::Class2 ::mainClass::SubClassesList
(::mainClass::SubClassA)     Current namespace      : ::subClass
(::mainClass::SubClassA)     InternalVariable scope : @itcl ::mainClass::SubClassA ::subClass::InternalVariable
(::Class1)     SubClassesList         : {SubClassA}
(::mainClass::SubClassB)     Current namespace      : ::subClass
(::mainClass::SubClassB)     InternalVariable scope : @itcl ::mainClass::SubClassB ::subClass::InternalVariable
(::Class1)     SubClassesList         : {SubClassA SubClassB}
(::mainClass::SubClassC)     Current namespace      : ::subClass
(::mainClass::SubClassC)     InternalVariable scope : @itcl ::mainClass::SubClassC ::subClass::InternalVariable
(::Class2)     SubClassesList         : {SubClassC}
command "SubClassB" already exists in namespace "::mainClass"

I'm probably missing a point about classes namespaces because I do not understand how the variable SubClassesList can have two different scopes but the "same" namespace/name (from debug output).

I tried to create a new namespace in the newSubClass method but it did not resolve the problem and/or add some inextricable variables namespaces errors...

method newSubClass {argName} {
  set SubClassName "[namespace current]::[namespace tail $this]"
  puts "($this)     SubClassName           : $SubClassName"
  namespace eval $SubClassName "lappend SubClassesList [subClass $argName]"
  puts "($this)     SubClassesList         : {$SubClassesList}"
}

Any idea to perform such a thing ?

P-S: I used [incr Tcl] to implement classes in my project for compatibility reasons with existing environment but if someone thinks that an other OO implementation would be better/easier, please let me know...


EDIT :

Found a solution by using namespace eval + namespace inscope to create subClass objects in a new namespace corresponding to mainClass objects name:

itcl::class mainClass {
  variable SubClassesList
  constructor {} {
    set SubClassesList {}
    puts "($this)     current namespace      : [namespace current]"
    puts "($this)     SubClassesList scope   : [itcl::scope SubClassesList]"
    # Create a new namespace corresponding to class name
    namespace eval $this {}
  }
  method newSubClass {argName} {
    # Create the subClass object in the $this namespace
    lappend SubClassesList [namespace inscope $this subClass $argName]
    puts "($this)     SubClassesList         : {$SubClassesList}"
  }
}
Mousstix
  • 303
  • 1
  • 3
  • 8
  • I you need portability, I suggest using snit or stooop. They are both written in pure Tcl and should run everywhere where Tcl runs. – Johannes Kuhn Jul 09 '13 at 22:55

1 Answers1

2

I think you are confused between a class and an object: Class is a blue print, or template, for creating objects. In your example:

  • mainClass and subClass are classes
  • Class1, Class2, SubClassA, SubClassB, and SubClassC are objects, AKA instances of classes

In Itcl, each class has its own namespace, for example, class mainClass owns a namespace called ::mainClass -- you see this evidence in the output your code:

(::Class1)     current namespace      : ::mainClass
^^^^^^^^^^

Furthermore, each object has its own namespace: ::::. For example, the SubClassB object owns the namespace ::mainClass:SubClass:

(::mainClass::SubClassB)     InternalVariable scope : @itcl ::mainClass::SubClassB ::subClass::InternalVariable
^^^^^^^^^^^^^^^^^^^^^^^^

That means, you cannot have have two objects that share the same name--that's the error you are getting. If you still want to example to work, use #auto for object name:

method newSubClass {argName} {
    lappend SubClassesList [subClass #auto] ;# <=== Use automatic naming
    puts "($this)     SubClassesList         : {$SubClassesList}"
}
Hai Vu
  • 37,849
  • 11
  • 66
  • 93
  • Thanks for answer. I think I understand the point and the problem. – Mousstix Jul 10 '13 at 14:56
  • The `#auto` solution works but is a problem because I must know each `subClass` objects names before execution (too long to explain why here...). A question then: Is it possible to create the object `SubClassA` in a new namespace called for example `::Class1` and how? – Mousstix Jul 10 '13 at 15:04
  • OK, found an alternative solution with `namespace eval` + `namespace inscope` (post edited). Don't know if it is a very clean solution but it seems to fits to my needs. Thanks again for your explanations. – Mousstix Jul 10 '13 at 15:34