Sealed classes and methods: Difference between revisions

m
→‎{{header|Wren}}: Changed to Wren S/H
(Added Go)
m (→‎{{header|Wren}}: Changed to Wren S/H)
 
(8 intermediate revisions by 2 users not shown)
Line 6:
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
Line 21:
* [[Inheritance/Single]]
* [[Inheritance/Multiple]]
 
=={{header|C}}==
C isn't an object oriented language though it can and has been used to create other languages which are.
 
It has structs rather than classes which are just a collection of fields. Methods on a struct can be simulated by functions whose first argument is a pointer to the struct.
 
To simulate inheritance, one can embed a 'parent' field ''first'' in the 'child' struct and then pass the address of that field to 'parent' methods.
 
However, there is no way that the method can tell whether it's receiving a pointer to a 'parent' instance or a pointer to a 'child' field. To use the Wren technique for simulating sealed methods, we therefore need to pass a further parameter, namely a type identifier, as the following code illustrates.
<syntaxhighlight lang="c">#include <stdio.h>
 
typedef enum {
PARENT,
CHILD
} typeid;
 
typedef struct {
const char* name;
int age;
} parent;
 
typedef struct {
parent p;
} child;
 
void watchMovie(parent *p, typeid id) {
if (id == CHILD && p->age < 15) {
printf("Sorry, %s, you are too young to watch the movie.\n", p->name);
} else {
printf("%s is watching the movie...\n", p->name);
}
}
int main() {
parent p = { "Donald", 42 };
child c1 = { "Lisa", 18 };
child c2 = { "Fred", 10 };
watchMovie(&p, PARENT);
watchMovie(&c1.p, CHILD);
watchMovie(&c2.p, CHILD);
return 0;
}</syntaxhighlight>
 
{{out}}
<pre>
Donald is watching the movie...
Lisa is watching the movie...
Sorry, Fred, you are too young to watch the movie.
</pre>
 
=={{header|C++}}==
Classes and functions can be sealed in C++ by using the '''final''' keyword.
<syntaxhighlight lang="cpp">#include <iostream>
#include <memory>
#include <string>
#include <vector>
 
class MovieWatcher // A base class for movie watchers
{
protected:
std::string m_name;
 
public:
explicit MovieWatcher(std::string_view name) : m_name{name}{}
virtual void WatchMovie()
{
std::cout << m_name << " is watching the movie\n";
}
virtual void EatPopcorn()
{
std::cout << m_name << " is enjoying the popcorn\n";
}
virtual ~MovieWatcher() = default;
};
 
// ParentMovieWatcher cannot be inherited from because it is 'final'
class ParentMovieWatcher final : public MovieWatcher
{
public:
explicit ParentMovieWatcher(std::string_view name) : MovieWatcher{name} {}
};
 
// ChildMovieWatcher can be inherited from
class ChildMovieWatcher : public MovieWatcher
{
public:
explicit ChildMovieWatcher(std::string_view name)
: MovieWatcher{name}{}
// EatPopcorn() cannot be overridden because it is 'final'
void EatPopcorn() final override
{
std::cout << m_name << " is eating too much popcorn\n";
}
};
 
class YoungChildMovieWatcher : public ChildMovieWatcher
{
public:
explicit YoungChildMovieWatcher(std::string_view name)
: ChildMovieWatcher{name}{}
// WatchMovie() cannot be overridden because it is 'final'
void WatchMovie() final override
{
std::cout << "Sorry, " << m_name <<
", you are too young to watch the movie.\n";
}
};
 
int main()
{
// A container for the MovieWatcher base class objects
std::vector<std::unique_ptr<MovieWatcher>> movieWatchers;
// Add some movie wathcers
movieWatchers.emplace_back(new ParentMovieWatcher("Donald"));
movieWatchers.emplace_back(new ChildMovieWatcher("Lisa"));
movieWatchers.emplace_back(new YoungChildMovieWatcher("Fred"));
 
// Send them to the movies
std::for_each(movieWatchers.begin(), movieWatchers.end(), [](auto& watcher)
{
watcher->WatchMovie();
});
std::for_each(movieWatchers.begin(), movieWatchers.end(), [](auto& watcher)
{
watcher->EatPopcorn();
});
}
</syntaxhighlight>
{{out}}
<pre>
Donald is watching the movie
Lisa is watching the movie
Sorry, Fred, you are too young to watch the movie.
Donald is enjoying the popcorn
Lisa is eating too much popcorn
Fred is eating too much popcorn
</pre>
 
=={{header|FreeBASIC}}==
Line 75 ⟶ 216:
Consequently, a Go struct and its methods are effectively sealed unless the struct is embedded in another one. However, the only way to prevent embedding from outside the package would be to make the struct private to its package which may not be an acceptable solution unless it and/or its methods could be exposed indirectly.
 
Fortunately, as the following example shows, the Wren technique for sealing methods can still be used provided we pass a further parameter (a type identifier) to the method so that it knows whether its being called with a pointer to a 'parent' or to a 'child' instance. This information is needed because the type system is such that the runtime type of the receiver will always be 'parent'.
The Wren approach isn't feasible because, as the following example shows, the parent method always thinks its receiver is a parent even though it may be a child!
 
We can still prevent 'Fred' from watching the movie on age grounds though the code needed to do this would prevent an under age parent from watching the movie as well.
<syntaxhighlight lang="go">package main
 
import "fmt"
 
type typeid int
 
const (
PARENT typeid = iota
CHILD
)
 
type parent struct {
Line 91 ⟶ 237:
}
 
func (p *parent) watchMovie(id typeid) {
fmt.Printf("Theif typeid of== %sCHILD is %T\n",&& p.name,age p)< 15 {
if p.age < 15 {
fmt.Printf("Sorry, %s, you are too young to watch the movie.\n", p.name)
} else {
Line 101 ⟶ 246:
 
func main() {
p := &parent{"Donald", 42}
p.watchMovie(PARENT)
c1 := &child{parent{"Lisa", 18}}
c2 := &child{parent{"Fred", 10}}
c1.watchMovie(CHILD)
c2.watchMovie(CHILD)
}</syntaxhighlight>
 
{{out}}
<pre>
The type of Donald is main.parent
Donald is watching the movie...
The type of Lisa is main.parent
Lisa is watching the movie...
The type of Fred is main.parent
Sorry, Fred, you are too young to watch the movie.
 
</pre>
 
Line 240 ⟶ 383:
 
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!
<syntaxhighlight lang="ecmascriptwren">class Parent {
construct new(name, age) {
_name = name
9,476

edits