With inspiration from other answers I came up with the following (rough) class hierarchy that is similar to the cake pattern in Scala:
interface UserRepository {
String authenticate(String username, String password);
}
interface UserRepositoryComponent {
UserRepository getUserRepository();
}
interface UserServiceComponent extends UserRepositoryComponent {
default UserService getUserService() {
return new UserService(getUserRepository());
}
}
class UserService {
private final UserRepository repository;
UserService(UserRepository repository) {
this.repository = repository;
}
String authenticate(String username, String password) {
return repository.authenticate(username, password);
}
}
interface LocalUserRepositoryComponent extends UserRepositoryComponent {
default UserRepository getUserRepository() {
return new UserRepository() {
public String authenticate(String username, String password) {
return "LocalAuthed";
}
};
}
}
interface MongoUserRepositoryComponent extends UserRepositoryComponent {
default UserRepository getUserRepository() {
return new UserRepository() {
public String authenticate(String username, String password) {
return "MongoAuthed";
}
};
}
}
class LocalApp implements UserServiceComponent, LocalUserRepositoryComponent {}
class MongoApp implements UserServiceComponent, MongoUserRepositoryComponent {}
The above compiles on Java 8 as of Jan.9 2013.
So, can Java 8 do a cake-like pattern? Yes.
Is it as terse as Scala, or as effective as other patterns in Java (i.e. dependency injection)?
Probably not, the above sketch required a whole lot of files and is not as terse as Scala.
In summary:
- Self-types (as needed for the cake pattern) can be emulated by extending the base interface we expect.
- Interfaces cannot have inner classes (as noted by @Owen), so instead we can use anonymous classes.
val
andvar
can be emulated by using a static hashmap (and lazy initialization), or by the client of the class simply storing the value on their side (like UserService does).- We can discover our type by using
this.getClass()
in a default interface method. - As @Owen notes, path dependent types are impossible using interfaces, so a full cake pattern is inherently impossible. The above shows, however, that one could use it for dependency injection.