Sealed classes and methods

Revision as of 12:35, 7 June 2023 by Rdm (talk | contribs) (J)

In object-oriented programming, a sealed or final class is one which cannot be inherited from.

Sealed classes and methods is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
Definition

Classes are sometimes made non-subclasssable in this way if the author feels that it would not be useful or even undesirable for subclasses to be created from them. Moreover, in a compiled language, knowing that a class cannot be subclassed, may enable optimizations to be made.

Rather than sealing the entire class, it may be possible to just seal certain 'public' methods for the same reasons and with the same potential benefits. 'private' methods should be effectively sealed anyway.

Task

If your language supports the object oriented paradigm, explain what support it has for sealed classes and/or methods and, if there is no support, what if anything can be done to simulate them. If possible illustrate your answer with an example.

If your language does not support the object oriented paradigm (or its inheritance aspect), you may either simply say so, omit the task altogether or describe any equivalent structure(s) and restrictions on use which the language does have.

Reference
Related task

FreeBASIC

Type Parent
    nombre As ZString * 7
    edad As Byte
    Declare Operator Cast () As String
End Type

Operator Parent.cast As String
    Return this.nombre & " is watching the movie..."
End Operator

Type Child Extends Parent
    Declare Operator Cast As String
End Type

Operator Child.cast As String
    If this.edad < 15 Then
        Return "Sorry, " & this.nombre & ", you are too young to watch the movie."
    Else
        Return this.nombre & " is watching the movie..."
    End If
End Operator

Dim As Parent p1, p2
p1.nombre = "Donald" : p1.edad = 42
p2.nombre = "Dougal" : p2.edad = 12
Print p1
Print p2

Dim As Child c1, c2
c1.nombre = "Lisa" : c1.edad = 18
c2.nombre = "Fred" : c2.edad = 10   
Print c1
Print c2

Sleep
Output:
Donald is watching the movie...
Dougal is watching the movie...
Lisa is watching the movie...
Sorry, Fred, you are too young to watch the movie.

J

No J compilers have been released (though some people have claimed to be working on such things). That said, J does provide a locked script mechanism which might be thought of as compiled code (which depends on libj).

J, by default, does not support sealed classes (nor methods). However, sealed classes could be implemented by altering J's coinsert (which implements inheritance) to omit sealed classes.

For example, we could say that a class which contained any implementation of final is a sealed class:

coinsert=: {{
 l=. (#~{{0>nc<'final__y'}}"0);: :: ] y
 p=. ; (, 18!:2) @ < each l
 p=. ~. (18!:2 coname''), p
 (p /: p = <,'z') 18!:2 coname''
}}

J does not provide a mechanism to seal individual methods.

Phix

No support, though it would probably not be particularly difficult to add a "final" keyword if ever needed.
Phix supports object orientation for the die-hards (desktop/Phix only), but does not require it be used at all.
In Phix, "private" determines availability from outside [sub-]class code, but there is nothing at all to prevent Child from having a public method that (internally) invokes a private method of the Parent, and of course/not unlike it would make no difference were name and age made private below.
The Phix compiler can make exactly the same optimisations were it to spot at EOF that a method was not overidden as it could were it told up-front.

Translation of: Wren

Extended to include an under-age parent

without javascript_semantics -- no classes under p2js, sorry
include builtins\structs.e -- (needed for get_struct_name)

class Parent
    string name
    integer age
    procedure watch_movie()
        if get_struct_name(this)!="Parent" and age<15 then
            printf(1,"Sorry, %s, you are too young to watch the movie.\n",{name})
        else
            printf(1,"%s is watching the movie...\n",{name})
        end if
    end procedure
end class

class Child extends Parent
end class

Parent p1 = new({"Donald", 42}),
       p2 = new({"Dougal", 12})
p1.watch_movie()
p2.watch_movie()

Child c1 = new({"Lisa", 18}),
      c2 = new({"Fred", 10})
c1.watch_movie()
c2.watch_movie()
Output:
Donald is watching the movie...
Dougal is watching the movie...
Lisa is watching the movie...
Sorry, Fred, you are too young to watch the movie.

Of course you would get the same output from and it is usually much more sensible to do something like this:

class Parent
    private string name
    private integer age
    procedure watch_movie()
        printf(1,"%s is watching the movie...\n",{name})
    end procedure
end class

class Child extends Parent
    procedure watch_movie()
        if age<15 then
            printf(1,"Sorry, %s, you are too young to watch the movie.\n",{name})
        else
            printf(1,"%s is watching the movie...\n",{name})
        end if
    end procedure
end class

Note however in Phix there is no possibility of invoking parent.watch_movie() from the else branch, because you have completely replaced that routine in the child instance. Should you want to share code in that kind of fashion you would need to give it a different and completely unambiguous name.

Wren

Although Wren is an object-oriented language, it does not support sealed classes or methods. Nor does it have 'private' methods. All instance methods (though not constructors or static methods) are automatically inherited by subclasses.

It might be argued that such a restriction would be out of place anyway in a simple embedded scripting language, particularly when libraries can only be distributed in source code form at the present time.

Nevertheless, it is possible to simulate sealed methods by simply stopping them from executing normally at runtime unless they are being accessed by an object of the same class. In fact, as the following example shows, this technique allows methods to be conditionally (rather than absolutely) sealed where the circumstances warrant this.

Note that:

1. To seal the entire class one would need to apply the same technique to all instance methods though this would be very tedious in practice for classes having a lot of methods.

2. With the exception of Object and Sequence, it is not possible to inherit from Wren's built-in classes anyway for technical reasons. Nor is it possible to inherit from 'foreign' classes i.e. classes which are instantiated from C rather than Wren.

3. Using the 'is' operator (i.e. a is C) to detect the type of 'a' wouldn't work here as this would return 'true' if 'a' were either a 'C' object or an object of a subclass of 'C'. It is possible to spoof the 'is' operator by overriding its normal behavior, though this is definitely not recommended!

class Parent {
    construct new(name, age) {
        _name = name
        _age  = age
    }

    watchMovie() {
        if (this.type != Parent && _age < 15) {
            System.print("Sorry, %(_name), you are too young to watch the movie.")
        } else {
            System.print("%(_name) is watching the movie...")
        }
    }
}

class Child is Parent {
    construct new(name, age) {
        super(name, age)
    }
}

var p = Parent.new("Donald", 42)
p.watchMovie()

var c1 = Child.new("Lisa", 18)
var c2 = Child.new("Fred", 10)
c1.watchMovie()
c2.watchMovie()
Output:
Donald is watching the movie...
Lisa is watching the movie...
Sorry, Fred, you are too young to watch the movie.