viernes, 6 de julio de 2007

Rants about patterns

Alex is ranting about the Singleton pattern and the Template Method pattern (TMP).

I agree, he is right. The TMP is rather poor, but it is the heart and intent of all object orientation (Encapsulation + Inheritance = Polymorphism). To be fair, Polymorphism comes in 2 flavors in Java:

  1. Class polymorphism, ie: the template method pattern.
  2. Interface polymorphism, ie: use interfaces instead of abstract classes.

I have to agree with Alex on this one, but I don't like his proposed solution: Simply use an interface. What about all the code repetition?

It may be a good mechanism, but it is certainly a lot of code for any programmer to write, the TMP is very simple in comparison.

I have another proposed solution: Use Traits. Simply stated, a trait is just a class that has methods but no instance variables, some of the methods are abstract and must be redefined somewhere and the rest of the methods depend on those methods.

Here is a paper that explains Traits when compared to Interfaces, Mixins and Multiple Inheritance.

Multiple Inheritance is something to avoid at all costs. Interfaces are well defined, but they do not share any behavior, as we all know. Mixins are the next good thing, but a Mixin just mixes 2 classes creating a new one, it reminds me of templates in C++, also something you want to avoid, because the code looks simple, but what is does under the hood is disgusting.

There are some intentions to create alternative versions of Java that support Traits. I wonder if we really need that. Isn't Java Turing complete? Why should I have to extend Java to implement something so simple as a Trait?

Let us explain a wee little bit. First of all, if you use the TMP, you can share some code in the base class and override the template methods in different classes, but the only problem, at least in Java, is that you can't extend several classes at once and therefore there is some code duplication.

For example, let us suppose you have 4 classes: A, B, C and D, and they are defined like this:

class A
{
public void a() { ... }
}

class B extends A
{
public void b() { ... }
}

class C extends A
{
public void c() { ... }
}

class D extends B
{
public void c() { ... }
public void d() { ... }
}

This is the typical Diamond problem (if it were defined using multiple inheritance, and its solution in a sinlge inheritance language). Yes, method c() is repeated both in C and D, but Java doesn't have multiple inheritance, so this is the only solution.

Nevertheless you hate repeated code, you read about Mixins and Traits, Mixins maintain the source clean, which is a good thing, but the compiled code is a mess, so you study Traits.

Are you still with me?

We need a more realistic example to show how a Trait would work.

class Person
{
String id;
String name;
}

class Professor extends Person
{
List courseList; // List of Course
void administerTest( Test test, Course course ) { ... }
}

class Student extends Person
{
List courseList; // List of Course
void giveTest( Test test, Course course ) { ... }
}

class Course
{
Professor professor;
List studentList; // List of Student
List testList;
}

class AssistantProfessor extends Student, Professor
{}

Since AssistantProfessor can't really extend Student and Professor, we need to either extend Student or Professor and copy and paste the missing methods, ie:

class AssistantProfessor extends Student
{
List courseList; // List of Courses to teach
void administerTest( Test test, Course course ) { ... }
}

or:

class AssistantProfessor extends Professor
{
List courseList; // List of Courses to study
void giveTest( Test test, Course course ) { ... }
}

As you can see, the code is heavily repeated one way or the other.

The same solution using Traits:

class PersonTrait {}

class Person extends PersonTrait
{
String id;
String name;
}

class ProfessorTrait extends PersonTrait
{
abstract List getCourseList(); // List of Course
void administerTest( Test test, Course course ) { ... }
}

class Professor extends ProfessorTrait
{
List courseList; // List of Course
List getCourseList() { return courseList; }
}

class StudentTrait extends PersonTrait
{
abstract List getCourseList(); // List of Course
void giveTest( Test test, Course course ) { ... }
}

class Student extends StudentTrait
{
List courseList; // List of Course
List getCourseList() { return courseList; }
}

Now probably you have noticed that each class exists twice, once as a traits class with no instance variables and once as a normal class descending from the traits class. The class hierarchy has only traits classes (which are abstract) and leaf classes descend directly from those traits classes.

Also leaf classes are sometimes identical, like the Professor and the Student classes.

What about the AssistantProfessor?

class AssistantProfessorTrait extends StudentTrait, ProfessorTrait
{}

class AssistantProfessor extends AssistantProfessorTrait
{
List courseList; // List of Course
List getCourseList() { return courseList; }
}

The main problem with this is that AssistantProfessorTrait can't extend 2 classes. Even if that worked, there is no way to define AssistantProfessor's getCourseList() so that it satisfies both StudentTrait and ProfessorTrait.

No hay comentarios: