From Canadian Mind Products’ Java Glossary:
abstract class
A class that is missing definitions for one or more methods. You can’t thus create an object of that class. You must first create a subclass and provide definitions for the abstract methods. Unlike interfaces, abstract classes may implement some of the methods. Though you can’t instantiate an abstract class, you can invoke its static methods.
For a comparison of abstract classes and interfaces, and what they’re good for, have a look at Java Glossary: Interface vs Abstract Class, just about all of which (except for the stuff about "static final constants") is applicable to ActionScript as well, once we implement abstract classes.
ActionScript 2.0 doesn’t natively support abstract classes, although the abstract keyword is reserved for future use. I’ve seen people simulate abstract methods by throwing an error in each one to complain at run time that it hasn’t been overriden, but there’s a better way. We can make a real abstract class that implements none, some, or all of an interface’s methods, and enforces that subclasses implement any missing methods.
In C++, abstract methods are called pure virtual functions, and they’re specified in the class definition by setting the function body to zero (actually the null pointer):
virtual void someFunction() = 0;
In C++ the presence of a pure virtual function makes the class uninstantiable, and specifies that the function must be implemented in any instantiable subclass. (As for regular virtual functions, if you’re not familiar with C++, let’s just say that all methods in AS2 are virtual anyway.)
A pure virtual function can actually have an implementation in C++ — you define it outside the class body — without relieving the derived class of the obligation to override it. We can accomplish this in ActionScript too.
But why?
This could be useful in the Window example. Window could insist that its derived classes provide their own version of clear but could still provide basic clear functionality common to all window types. For example, Window::clear might position the cursor to the middle of the screen when invoked as the final action of a derived class clear member function.
(James Coplien, Advanced C++, p. 133)
Maybe not the coolest example, but you get the idea.
But how?
It looks like a real problem: In ActionScript we can only avoid implementing a method by omitting the method declaration from the class; there’s no way to specify it as abstract.
Suppose we create an interface IAbstract and a class Abstract. The class cannot declare that it implements IAbstract; otherwise it couldn’t omit any method definitions. But it must ensure that its subclasses supply any missing definitions of IAbstract’s methods. Andrew Simmons showed a neat solution to this in an article at ActionScript.org. I’ve taken his example and expanded on it somewhat so I can understand it better, and so I can show how to do an abstract method with a body in ActionScript.
If you’d like to see some demonstration tests, save the four files I’ll show here, open up Flash, make a FLA in the same directory, put Extending.demo(); in Frame 1’s Actions, and then Test Movie.
First I’ll create the interface:
interface IAbstract
{
function methodOne():Void;
function methodTwo():Void;
function methodThree():Void;
function methodFour():Void;
}
And now here’s the Abstract class:
class Abstract
{
private var sub:IAbstract;
private function Abstract()
{
trace("\tcreating Abstract object");
sub = IAbstract(this);
// Flash Player 7 and above:
if (sub == null)
{
throw new Error("Classes extending Abstract must implement IAbstract");
}
// end Flash Player 7 and above
/* for Flash Player 6 use this runtime type check
if (!(sub instanceof IAbstract))
{
sub = null;
trace("Classes extending Abstract must implement IAbstract");
}
*/
else
{
trace("\tAbstract object created without error");
}
}
public function toString():String
{
return "Abstract instance";
}
function methodTwo():Void
{
trace("\t\tmethodTwo defined in Abstract");
}
function methodFour():Void
{
trace("\tmethodFour defined in Abstract invokes:");
sub.methodOne();
methodTwo();
sub.methodThree();
}
// Use a static initializer to install an implementation for methodThree:
private static var init:Boolean = initMethodThree();
private static function initMethodThree():Boolean
{
Abstract.prototype.methodThree = function()
{
trace("\t\tmethodThree added to Abstract, executed by " + this);
};
return true;
}
}
We’ve defined methodFour to call methodOne, methodTwo, and methodThree, though we haven’t defined methodOne, and at compile time we haven’t yet defined methodThree. To quote Simmons:
Here is the foundation of this example. We store a reference to the current instance of the class extending Abstract in the variable sub.
sub = IAbstract(this);
This allows us to invoke the abstract method [methodOne or methodThree] without a compiler error. If the extending class does not extend the interface, the cast returns null and we throw an exception. This forces the extending class to implement the interface IAbstract. The result is an uninstantiable class which contains some implementation and enforces specific and complete implementation by all deriving classes.
Then we use a static initializer to install an implementation for methodThree. methodThree will be our pure virtual with a body. Unlike in C++, the only way we could implement Abstract’s methodThree at compile time would be to define it in the class body, but then it would no longer be abstract, and a derived class could get away without overriding it. So we do the next best thing, and define it as soon as possible at run time. And we have to do it the old-fashioned way, using the class prototype.
Before I show the Extending class, I want to throw in a little class we’ll use for testing — Phony, which extends Abstract but does not implement IAbstract:
class Phony extends Abstract
{
public function Phony()
{
trace("\nPhony object created");
}
}
Now the Extending class is going to override Abstract’s default implementation of methodTwo and Abstract’s pure virtual function methodThree. Each of these two functions in Extending will do its own thing and then invoke Abstract’s version.
Abstract’s version of methodThree doesn’t exist until run time. This has two consequences for Extending: as we’ve said, if Extending didn’t override it, it couldn’t claim to implement IAbstract; in addition, Extending can only invoke it through the class prototype.
OK. Here’s Extending. It’s a little long because it contains all the tests:
class Extending extends Abstract implements IAbstract
{
function Extending()
{
trace("\tExtending object created");
}
public function toString():String
{
return "Extending object";
}
function methodOne():Void
{
trace("\tmethodOne defined in Extending");
}
public function methodTwo():Void
{
trace("\tmethodTwo defined in Extending");
trace("\t\tinvoking Abstract's methodTwo:");
// Invoke the Abstract class' methodTwo on this object:
super.methodTwo();
}
public function methodThree():Void
{
trace("\tmethodThree defined in Extending");
trace("\t\tinvoking Abstract's methodThree:");
// Invoke the Abstract class' methodThree on this object
// We can't say super.methodThree(); because it's not defined at compile time:
Abstract.prototype.methodThree.apply(this, arguments);
}
public static function demo()
{
trace("");
trace("Test 1: create an Abstract.");
trace("This test wouldn't even compile if we were not running it");
trace("from within a class that extends Abstract,")
trace("since Abstract's constructor is private:");
trace("");
var ab:Abstract;
try
{
ab = new Abstract();
}
catch (e:Error)
{
trace("\tError: " + e);
}
trace("\tThe Abstract is " + ab);
trace("");
trace("We just can't instantiate an Abstract.");
trace("Good. Abstract is an abstract class.");
trace("");
trace("Test 2: create an Extending, which extends Abstract and,");
trace("with the help of methodFour inherited from its parent");
trace("Abstract, implements IAbstract:");
trace("");
var theExt:Extending;
try
{
theExt = new Extending();
}
catch (e:Error)
{
trace("\tError: " + e);
return;
}
trace("");
trace("Test 3: create a Phony, which extends Abstract");
trace("but does not implement IAbstract:");
trace("");
var thePhony:Phony;
try
{
thePhony = new Phony();
}
catch (e:Error)
{
trace("\tError: " + e);
}
trace("");
trace("OK, so Abstract demands that its subclasses implement IAbstract");
trace("in order to be instantiated.");
trace("");
trace("Does class Abstract directly implement methodOne? " +
(Abstract.prototype.hasOwnProperty("methodOne") ? "yes" : "no"));
trace("Does class Abstract directly implement methodTwo? " +
(Abstract.prototype.hasOwnProperty("methodTwo") ? "yes" : "no"));
trace("Does class Abstract directly implement methodThree? " +
(Abstract.prototype.hasOwnProperty("methodThree") ? "yes" : "no"));
trace("");
trace("Does class Extending directly implement methodOne? " +
(Extending.prototype.hasOwnProperty("methodOne") ? "yes" : "no"));
trace("Does class Extending directly implement methodTwo? " +
(Extending.prototype.hasOwnProperty("methodTwo") ? "yes" : "no"));
trace("Does class Extending directly implement methodThree? " +
(Extending.prototype.hasOwnProperty("methodThree") ? "yes" : "no"));
trace("");
trace("Test 4: let the Extending object execute methodFour:");
trace("");
theExt.methodFour();
trace("");
trace("Extending's methodThree, after tracing out its own message,");
trace("successfully invokes Abstract's methodThree.");
trace("methodThree in Abstract is indeed a pure virtual function");
trace("in that it must be overridden in a derived class if the derived");
trace("class is to have any instances, but it can be given a body, with the cost");
trace("that it must be both defined and called with slightly indirect syntax.");
}
}