|
Re: Eureka: Forget Interfaces what I really need are Traits!
|
Posted: Jan 5, 2006 5:08 PM
|
|
@Kresimir Cosic
I like traits but rather than defend them in a standard way I was going to give some an example and compare to multiple inheritance. I hope you can find time to say why you prefer multiple inheritance based on the example below.
Lets assume that Java did multiple inheritance and like say Eiffel you can rename a method/field or exclude a method/field and that a convention existed for access to multiple super classes. The example is a university department that has people in it and the people can be students, teachers, or teaching assistants (which are students that are paid to do some teaching - i.e. they are both a Student and a Teacher). Note the example doesn't use renaming but does use exclude and multiple super types. First using multiple inheritance.
class Person {
private final String name;
Person( final String name ) { this.name = name; }
String getName() { return name; }
String toString() { return "Person: " + name; }
}
class Student extends Person {
private final int mark;
Student( final String name, final int mark ) {
super( name );
this.mark = mark;
}
int getMark() { return mark; }
String toString() { return "Student: " + getName() + ", Mark = " + mark; }
}
class Teacher extends Person {
private final float salary;
Teacher( final String name, final float salary ) {
super( name );
this.salary = salary;
}
float getSalary() { return salary; }
String toString() { return "Teacher: " + getName() + ", Salary = " + salary; }
}
class AssistantTeacher extends
Student,
Teacher exclude { name } // attempt at only one name!
{
AssistantTeacher( final String name, final int mark, final float salary ) {
Student.super( name, mark );
Teacher.super( null, salary ); // note name excluded
}
String toString() { return "AssistantTeacher: " + getName() + ", Mark = " + getMark() + ", Salary = " + getSalary(); }
}
The main difference between a Trait and multiple inheritance are the fields. Here in particular the name field. We only want one name field in AssistantTeacher so we exclude it from the Teacher inheritance path and pass null to the Teacher constructor where the name field is. But there are two problems with this:
1. What does Teacher.super( null, salary ) do when it reaches the first line, super( name ), in this constructor. Remember name is excluded in AssistantTeacher but not in Teacher. How can the compiler generate code for Teacher that can be used by AssistantTeacher?
2. How did the writer of AssistantTeacher know that the field to exclude was called name? The field is private in Person!
Traits side step these issues by not allowing fields, e.g. assuming that a Java interface could have a definition and hence would be a Trait then:
interface Person {
String getName();
String toString() { return "Person: " + getName(); }
}
class PersonClass implements Person {
private final String name;
Person( final String name ) { this.name = name; }
String getName() { return name; }
String toString() { return Person.super.toString(); }
}
interface Student extends Person {
int getMark();
String toString() { return "Student: " + getName() + ", Mark = " + getMark(); }
}
class StudentClass extends PersonClass implements Student {
private final int mark;
Student( final String name, final int mark ) {
super( name );
this.mark = mark;
}
int getMark() { return mark; }
String toString() { return Student.super.toString(); }
}
interface Teacher extends Person {
float getSalary();
String toString() { return "Teacher: " + getName() + ", Salary = " + getSalary(); }
}
class TeacherClass extends PersonClass implements Teacher {
private final float salary;
Teacher( final String name, final float salary ) {
super( name );
this.salary = salary;
}
float getSalary() { return salary; }
String toString() { return Teacher.super.toString(); }
}
interface AssistantTeacher extends Student, Teacher {
String toString() { return "AssistantTeacher: " + getName() + ", Mark = " + getMark() + ", Salary = " + getSalary(); }
}
class AssistantTeacherClass extends StudentClass implements AssistantTeacher {
private final float salary;
AssistantTeacher( final String name, final int mark, final float salary ) {
super( name, mark );
this.salary = salary;
}
float getSalary() { return salary; }
String toString() { return AssistantTeacher.super.toString(); }
}
Note how in the classes the toString method specifies which toString to use (it has two - one from Object and one from its trait).
You could code this way using multiple inheritance and templates (to give access to the multiple supers) in C++, but the trait concept formalizes and enforces this style and prevents other styles that have problems.
You will also note that I am not saying that structural subtyping is a good thing. I like you, don't think it is superior to named subtyping (Christopher calls this normal subtyping).
In summary a trait is like a cross between an interface and multiple inheritance, taking the best features of each. Probably closer to an interface than normal multiple inheritance though.
|
|