OTPattern/TournamentPolymorphism

From Eclipsepedia

Jump to: navigation, search
Warning2.png
A warning note:
This advanced pattern builds on and extends the concept of family polymorphism. It is probably not suitable as an introductory example for Object Teams.


Contents

Intent

A group of families should be able to interchange their roles. The whole group again should be treated polymorphically.

Motivation

Family polymorphism defines that each role immutably belongs to one team (family). In some situations it is, however, desirable that different teams can indeed interchange their roles. In order to still control such interchange (or "transfer") of roles, a group of teams should be made explicit. At the next level such group of teams should perhaps be subject of inheritance and polymorphism.

As an example consider board games as teams containing roles of types like Player, Board, Token, Move, Rule, etc. Family polymorphism allows to define a hierarchy of board games, comprising specific games like Chess, TicTacToe, Checkers etc. Thanks to family polymorphism, the type checker ensures that a tic-tac-toe player will never move a chess token, etc.

Next consider a tournament of chess games. After each round all black players should move to the next board to meet a new opponent. So these chess players need to migrate from one team to another, and still we want to be sure, that the chess player will only play chess games.

Finally consider the logic of tournaments to be defined generically so that we polymorphically speak of tournaments of board games, and still we want to be sure that all board games in a tournament are of the same kind.

Structure

Tournament polymorphism is realized in two steps: Allowing roles from specific teams to migrate to like teams. Creating a parallel hierarchy for groups of teams ("tournaments").

Step 1. is achieved by declaring that a specific role implements the interface ITeamMigratable (which was introduced in OTDT version 1.2.5. see Trac ticket #178). E.g.:

public final team class Chess {
        public class Player implements ITeamMigratable {
        }
}

This enables a client of class Chess to write methods like this (special type checking for migrateToTeam indeed guarantees consistency!):

void swapBlackPlayers(final BoardGame g1, final BoardGame g2) {
        Player<@g1> newPlayer1 = g2.getBlackPlayer().migrateToTeam(g1); // migrate from g2 to g1
        Player<@g2> newPlayer2 = g1.getBlackPlayer().migrateToTeam(g2); // migrate from g1 to g2
        g1.setBlackPlayer(newPlayer1); 
        g2.setBlackPlayer(newPlayer2);
}

Note, that the use of ITeamMigratable requires the enclosing team to be final which is not exactly what we want, so let's proceed to the next step.

OTTournaments.png

Step 2. deals with the contradiction that we need final team classes for board games and want to use team inheritance and polymorphism. This is solved by one level of indirection: In addition to the hierarchy of board games we define a hierarchy of tournaments, and finalness of teams is introduced only in the context of tournaments:

public team class Chess extends BoardGame {  // no longer final
        public class Player { } // can no longer implement ITeamMigratable
}
 
public team class Tournament 
{
        /* Re-interpret board games as final teams: */
        protected final team class Game extends BoardGame {
                public class Player implements ITeamMigratable { }
        }
        protected List<Game> allGames = ...;
        void swapBlackPlayers(final Game g1, final Game g2) { /* body as above */ }
        /* one more example method: */
        public void swapAllPlayers() {
                final Game firstGame = allGames.get(0);
                Player<@firstGame> firstPlayer = firstGame.getBlackPlayer();
                for(int i=0; i<allGames.size()-1; i++) {
                        final Game g1 = allGames.get(i);
                        final Game g2 = allGames.get(i+1);
                        g1.setBlackPlayer(g2.getBlackPlayer().migrateToTeam(g1)); // migrate from g2 to g1
                }
                final Game lastGame = allGames.get(allGames.size()-1);
                lastGame.setBlackPlayer(firstPlayer.migrateToTeam(lastGame));   
        }
}
public team class ChessTournament extends Tournament {
        @SuppressWarnings("overridefinalrole")
        protected final team class Game extends Chess { }
        /* everything else inherited from Tournament without change. */
}

This specifies that in the context of a Tournament we want to consider BoardGame as final, giving this class a new name: Game. In a specialized context ChessTournament we refine the notions of games to refer to chess games, but still disallow sub-classing. This enables us to mark Players as implementing ITeamMigratable.

Here we distinguish between explicit sub-classing using extends and implicit inheritance between roles from different teams. Normally, both kinds of sub-classing are forbidden when a class is marked as final. For implicit inheritance, however, this rule can be relaxed (using a compiler option) to only produce warnings when a final role is overridden, and these warnings in turn can be suppressed using an annotation (@SuppressWarnings("overridefinalrole")).

As a result, each context (Tournament, ChessTournament etc.) has a fixed understanding of Game as a non-subclassable type. Yet, from one tournament to the other, this notion can be refined. Now for every instance of Tournament the body of swapBlackPlayers knows that g1 and g2 have the exact same type, and thus role transfer between g1 and g2 is type safe.

From the outside we can define variables of type Tournament and invoke swapAllPlayers without knowing the exact type of tournaments / games. Yet, each instance of Tournament has a fixed interpretation of Game and can thus safely transfer roles (Player) from one game to another.

Consequences

to be written