Cake pattern with Java8 possible?
Asked Answered
S

5

31

I just wonder: with Java 8, and the possibility to add implementation in interfaces (a bit like Scala traits), will it be possible to implement the cake pattern, like we can do in Scala?

If it is, can someone provide a code snippet?

Spirant answered 10/1, 2013 at 0:12 Comment(0)
A
27

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 and var 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.
Alurd answered 10/1, 2013 at 1:3 Comment(1)
You should be able to access this and this.getClass() in a default method body, and you can add extra state through a weak identity map. However in the logging example, it is just not the java way; nothing wrong with the plain/old solution of adding an instance field final Logger logger=Logger.of(this); to achieve the mixin effect.Britzka
B
3

Maybe you can do something like this in Java 8

interface DataSource
{
    String lookup(long id);
}  

interface RealDataSource extends DataSource
{
    default String lookup(long id){ return "real#"+id; }
}  

interface TestDataSource extends DataSource
{
    default String lookup(long id){ return "test#"+id; }
}  

abstract class App implements DataSource
{
    void run(){  print( "data is " + lookup(42) ); }
}  


class RealApp extends App implements RealDataSource {}

new RealApp().run();  // prints "data is real#42"


class TestApp extends App implements TestDataSource {}

new TestApp().run();  // prints "data is test#42"

But it is in no way better than the plain/old approach

interface DataSource
{
    String lookup(long id);
}  

class RealDataSource implements DataSource
{
    String lookup(long id){ return "real#"+id; }
}  

class TestDataSource implements DataSource
{
    String lookup(long id){ return "test#"+id; }
}  

class App
{
    final DataSource ds;
    App(DataSource ds){ this.ds=ds; }

    void run(){  print( "data is " + ds.lookup(42) ); }
}  


new App(new RealDataSource()).run();  // prints "data is real#42"


new App(new TestDataSource()).run();  // prints "data is test#42"
Britzka answered 10/1, 2013 at 1:42 Comment(1)
Your first example compiles and runs as expected in Java 8.Alurd
A
3

I did a small proof-on-concept on this recently. You can see the blog post here: http://thoredge.blogspot.no/2013/01/cake-pattern-in-jdk8-evolve-beyond.html and the github repo here: https://github.com/thoraage/cake-db-jdk8

Basically you can do it, but you face at least two obstacles that makes it less slick than Scala. Firstly the Scala traits can have state and Java's interface can't. Many modules need state. This can be fixed by creating a general state component to hold this information, but this will need to be in a class. At least in part. Second issue is that a nested class in an interface is more akin to a static nested class in class. So you can't access the interfaces methods directly from the module class. The default interface method have access to this scope and can add this to the constructor of the module class.

Addi answered 10/1, 2013 at 7:34 Comment(1)
State can be handled (in a very inelegant manner) by using static identity maps, mapping from (instance -> value) for each field, and then lazily initializing it in the getters for each field.Alurd
O
2

A few experiments suggest no:

  • Nested classes are automatically static. This is inherently uncakelike:

    interface Car {
        class Engine { }
    }
    
    // ...
        Car car = new Car() { };
        Car.Engine e = car.new Engine();
    
    error: qualified new of static class
        Car.Engine e = car.new Engine();
    
  • So, apparently, are nested interfaces, although it's harder to coax out the error messages:

    interface Car {
        interface Engine { }
    }
    
    // ...
        Car car = new Car() { };
        class Yo implements car.Engine {
        }
    
     error: package car does not exist
            class Yo implements car.Engine {
    
     // ...
    
    class Yo implements Car.Engine {
    }                                                                                                      
    
    
     // compiles ok.
    

So, without instance member classes, you do not have path dependent types, which is basically necessary for the cake pattern. So at least, no, not in the straightforward way, it is not possible.

Outgo answered 10/1, 2013 at 1:5 Comment(0)
P
2

Ignoring the new functionality in Java 8 you can in theory do the Cake Pattern in Java 5 and above using compile time AspectJ ITDs.

AspectJ DTO's allow you to make Mixins. The only annoying thing is that you will have to make two artifacts: the aspect (ITD) and the interface. However ITDs allow you to do some crazy stuff like add annotations to classes that implement an interface.

Potassium answered 10/1, 2013 at 3:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.