Aside from the above points, there is also a conceptual breakdown of Smalltalk vs. Simula.
Conceptually, "Smalltalk-style" typically indicates that the method run when a message is called is determined at run time, aiding polymorphism.
"Simula-style", on the other hand, usually seems to indicate where all method calls are really just a convenient way of writing overloaded function calls--no runtime polymorphism. (Please correct me if I'm wrong.)
In the middle, we have Java: all methods virtual by default, but statically typed and has compile-time type dispatch.
Example:
// C++
class Base {
void doSomething() {
cout << "Base::doSomething() called!\n";
}
}
class Derived : Base {
void doSomething() {
cout << "Derived::doSomething() called!\n";
}
}
int main() {
Base* b = new Base();
Derived* d = new Derived();
b->doSomething(); // prints "Base::doSomething() called!"
d->doSomething(); // prints "Derived::doSomething() called!"
Base* d2 = d; // OK; Liskov substitution principle.
d2->doSomething(); // prints "Base::doSomething called!" (!)
delete b;
delete d;
return 0;
}
VS:
// Objective-C
//Base.h
@interface Base
{
}
-(void)doSomething
@end
//Base.m
#import "Base.h"
@implementation Base
-(void) doSomething {
printf("doSomething sent to Base!");
}
@end
//Derived.h
#import "Base.h"
#import "Base.m"
@interface Derived : Base
{
}
@end
//Derived.m
#import "Derived.h"
@implementation Derived
-(void) doSomething {
printf("doSomething sent to Derived!")
}
@end
//Main.m
#import "Base.h"
#import "Base.m"
#import "Derived.h"
#import "Derived.m"
int main() {
Base* b = [[Base alloc] init];
Derived* d = [[Derived alloc] init];
[b doSomething]; // prints "doSomething sent to Base!"
[d doSomething]; // prints "doSomething sent to Derived!"
Base* d2 = d;
[d2 doSomething]; // prints "doSomething sent to Derived!"
[b release];
[d release];
return 0;
}