Шаблон торта с Java8 возможен?

Мне просто интересно: с Java 8 и возможностью добавления реализации в интерфейсы (немного похожей на трейты Scala) можно ли будет реализовать шаблон пирога, как мы можем сделать в Scala?

Если это так, может ли кто-нибудь предоставить фрагмент кода?


person Sebastien Lorber    schedule 10.01.2013    source источник


Ответы (5)


Вдохновленный другими ответами, я придумал следующую (грубую) иерархию классов, которая похожа на шаблон торта в 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 {}

Вышеприведенное компилируется на Java 8 по состоянию на 9 января 2013 года.


Итак, может ли Java 8 создать шаблон как торт? Да

Является ли он таким же кратким, как Scala, или таким же эффективным, как другие шаблоны в Java (например, внедрение зависимостей)? Вероятно, нет, приведенный выше скетч требует большого количества файлов и не такой лаконичный, как Scala.

В итоге:

  • Самотипы (по мере необходимости для шаблона торта) можно эмулировать, расширяя базовый интерфейс, который мы ожидаем.
  • Интерфейсы не могут иметь внутренних классов (как заметил @Owen), поэтому вместо этого мы можем использовать анонимные классы.
  • val и var можно эмулировать с помощью статической хэш-карты (и ленивой инициализации) или с помощью клиента класса, просто сохраняющего значение на своей стороне (как это делает UserService).
  • Мы можем обнаружить наш тип, используя this.getClass() в методе интерфейса по умолчанию.
  • Как отмечает @Owen, типы, зависящие от пути, невозможны с использованием интерфейсов, поэтому полный шаблон торта по своей сути невозможен. Вышеприведенное показывает, однако, что его можно использовать для внедрения зависимостей.
person Alex DiCarlo    schedule 10.01.2013
comment
Вы должны иметь доступ к this и this.getClass() в теле метода по умолчанию, и вы можете добавить дополнительное состояние через карту слабой идентификации. Однако в примере с ведением журнала это просто не способ Java; нет ничего плохого в простом/старом решении добавления поля экземпляра final Logger logger=Logger.of(this); для достижения эффекта миксина. - person irreputable; 10.01.2013

Может быть, вы можете сделать что-то подобное в 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"

Но это ничуть не лучше, чем простой/старый подход

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"
person irreputable    schedule 10.01.2013
comment
Ваш первый пример компилируется и работает, как и ожидалось, в Java 8. - person Alex DiCarlo; 10.01.2013

Недавно я сделал небольшое доказательство концепции. Вы можете увидеть сообщение в блоге здесь: http://thoredge.blogspot.no/2013/01/cake-pattern-in-jdk8-evolve-beyond.html и репозиторий github здесь: https://github.com/thoraage/cake-db-jdk8

В принципе, вы можете это сделать, но вы сталкиваетесь по крайней мере с двумя препятствиями, которые делают его менее гладким, чем Scala. Во-первых, трейты Scala могут иметь состояние, а интерфейс Java — нет. Многие модули нуждаются в состоянии. Это можно исправить, создав компонент общего состояния для хранения этой информации, но это должно быть в классе. По крайней мере частично. Вторая проблема заключается в том, что вложенный класс в интерфейсе больше похож на статический вложенный класс в классе. Таким образом, вы не можете получить доступ к методам интерфейсов непосредственно из класса модуля. Метод интерфейса по умолчанию имеет доступ к этой области и может добавить ее в конструктор класса модуля.

person thoredge    schedule 10.01.2013
comment
Состояние можно обрабатывать (очень неэлегантным образом) с помощью статических карт идентификаторов, сопоставления из (экземпляр -> значение) для каждого поля, а затем ленивой инициализации его в геттерах для каждого поля. - person Alex DiCarlo; 10.01.2013

Несколько экспериментов показывают, что нет:

  • Вложенные классы автоматически статичны. Это по своей сути 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();
    
  • Итак, по-видимому, вложенные интерфейсы, хотя сообщения об ошибках выманить сложнее:

    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.
    

Таким образом, без классов-членов экземпляров у вас нет типов, зависящих от пути, что в основном необходимо для шаблона торта. Так что, по крайней мере, нет, не прямым путем, это невозможно.

person Owen    schedule 10.01.2013

Игнорируя новую функциональность в Java 8, вы теоретически можете использовать шаблон Cake в Java 5 и выше, используя время компиляции ITD AspectJ.

DTO AspectJ позволяют создавать миксины. Напрягает только то, что придется делать два артефакта: аспект (ИТД) и интерфейс. Однако ITD позволяют вам делать некоторые сумасшедшие вещи, такие как добавление аннотаций к классам, реализующим интерфейс.

person Adam Gent    schedule 10.01.2013