2015-07-15 3 views
8

Приложение может работать в двух режимах - «в реальном времени», где оно смотрит на каждое обновление до состояния мира или «отбирается», когда оно смотрит только на состояние мира каждые три миллисекунды ,Имитация ADT в Java

Если бы я писал Haskell (или любой другой язык с АТД) Я бы смоделировать это как

data Mode = RealTime | Sampled Int 

, который может быть использован следующим способом типобезопасного

case mode of 
    RealTime   -> -- do realtime stuff 
    Sampled interval -> -- do sample stuff with 'interval' 

Я говорю, что он «безопасен по типу», потому что если вы работаете в режиме реального времени, вам не удается получить доступ к полю interval (который предоставляется в то время, когда он вам нужен, если вы работаете в режиме дискретизации).

Как смоделировать одно и то же в Java безопасным способом? То есть, я хочу

  • Для определения класса (или перечисления), который отличает между этими двумя режимами, и
  • Чтобы запретить доступ к interval полю в режиме реального времени, и
  • Чтобы все это проверено компилятором.

Возможно ли это на Java? Если нет, то каков идиоматический способ достижения такого рода безопасности?

ответ

9

Традиционный способ эмулировать замкнутые алгебраические типы данных в языке, как Java, что обеспечивает только открытые классы (которые могут быть унаследованы в любое время) в пути типобезопасного является Visitor pattern:

abstract class Mode { 
    public abstract <T> T accept(ModeVisitor<T> visitor); 
} 

final class RealTime extends Mode { 
    public RealTime() {} 

    public <T> T accept(ModeVisitor<T> visitor) { 
     return visitor.visit(this); 
    } 
} 

final class Sampled extends Mode { 
    private final int interval; 

    public Sampled(int interval) { 
     this.interval = interval; 
    } 

    public int getInterval() { 
     return this.interval; 
    } 

    public <T> T accept(ModeVisitor<T> visitor) { 
     return visitor.visit(this); 
    } 
} 

// The recursion principle itself 
abstract class ModeVisitor<T> { 
    public abstract T visit(RealTime mode); 
    public abstract T visit(Sampled mode); 
} 

// Concrete uses of the recursion principle 
final class ModeShow extends ModeVisitor<String> { 
    private ModeShow() {} 

    public static String show(Mode mode) { 
     return mode.accept(new ModeShow()); 
    } 

    public String visit(RealTime mode) { 
     return "RealTime"; 
    } 

    public String visit(Sampled mode) { 
     return "Sampled " + mode.getInterval(); 
    } 
} 

Как @ user3237465 отмечено, что возможны несколько кодировок типов данных, которые происходят, чтобы совпадать, когда тип данных не является рекурсивным: Церковное кодирование является сгибом: оно позволяет вам накапливать значение путем рекурсии по типу данных, закодированному Церковью. Кодировка Скотта соответствует фактическому сопоставлению шаблонов. В любом случае, посетители могут быть использованы для реализации всех этих кодировок. Спасибо за толчок, @ user3237465!

+0

Спасибо, это выглядит как лучший способ пойти. Можем ли мы избежать определения класса «ModeVisitor» в Java 8 с помощью lambdas вместо этого (например, «public accept (Function visitor)) или вы что-то теряете, делая это? –

+0

@ChrisTaylor: вам нужен посетитель, потому что сам метод 'visit' перегружен, и каждый подкласс« Mode »должен выбрать соответствующую перегрузку. – pyon

+0

@ChrisTaylor: конкретный посетитель фактически может считаться пакетом, содержащим * несколько * lambdas: по одному для каждого варианта ADT.Посетители аналогичны результату применения функций 'возможно' и' либо' (от стандартной библиотеки Haskell) до двух аргументов: 'возможно, foo bar' будет использовать' foo' для посещения 'Nothing' и' bar' для посещения 'Just x'. – pyon

2

Вы можете объявить интерфейс Mode

public interface Mode { 
    void doSmth(); 
} 

И тогда вы можете иметь два класса, реализующие этот интерфейс

public class Realtime implements Mode { 
    @Override 
    public void doSmth() { 
     // Do realtime stuff 
    } 
} 

public class Sampled implements Mode { 
    private final int interval; 

    public Sampled(final int interval) { 
     this.interval = interval; 
    } 

    @Override 
    public void doSmth() { 
     // Do sampled stuff 
    } 
} 

Затем вы объявляете переменную типа Mode

final Mode mode = ... // Initialize however you want, calling one of the constructors. 

И то, поскольку mode имеет тип Mode, вы сможете получить доступ только к тому, что объявлено в интерфейсе. Таким образом, вы можете просто позвонить mode.doSmth() и, в зависимости от того, как вы инициализировали mode, он будет работать в режиме реального времени или сэмпла.

Если классы имеют некоторый код, вы можете объявить класс abstract вместо interface и иметь оба класса extend этот класс. Таким образом, то они могут вызвать общие методы, которые заявлены в abstract класса (который должен быть объявлен с protected видимости, так что только подклассы могут видеть их)

+0

Похоже, что это заставляет меня поместить поведение * внутри * реализаций режима, это правильно? Что делать, если у меня есть разные коды кода, которые хотят делать разные вещи в зависимости от режима? Фактически, это часть библиотеки, поэтому пользователи библиотеки должны иметь возможность добавлять свои собственные поведения для разных режимов. –

+0

@ChrisTaylor Java 8 поддерживает функции передачи в качестве параметров. Поэтому вместо объявления метода no-arg. Вы можете объявить функцию, которая получает текущую задачу. Итак, 'RealTime' будет просто постоянно называть эту функцию, а' Sampled' будет называть ее каждые x секунд или что-то еще. Другой подход мог бы объявить пару методов 'ifRealTime (f)' и 'ifSampled (f)' для интерфейса и в конкретных реализациях, 'RealTime' вызовет функцию в' ifRealTime' и ничего не сделает в 'ifSampled' , Надеюсь, я дал себе понять – xp500

+0

Принцип _abstraction_ заключается в разработке интерфейса (или абстрактного класса), который полностью инкапсулирует все возможные поведения, связанные с функциональностью. Итак, если в коде есть несколько точек, которые зависят от того, как реализована эта функциональность, каждый из них заслуживает правильного _method_ в этом интерфейсе. –

0

Да, Corse можно, Altough он dependes на уровне сложности, которую вы готовы достичь.

Если вы хотите, чтобы достичь самого высокого уровня типа-безопасности, я рекомендую вам объявить абстракции (названной AbstractModeManager), который ответственностью будет запрашивать состояние мира и сохранить его в переменных экземпляра, которые должны быть доступными через общедоступные методы получения.И он также должен знать, когда произошло обновление:

abstract class AbstractModeManager 
{ 
    // Instance variables to contain the last state read. 
    private ... state; 

    protected void queryTheStateOfTheWorld(...) throws ... 
    { 
     ... fill this.state ... 
    } 

    public ... getState() 
    { 
     return this.state; 
    } 

    public abstract void updateOccurred(...); 
} 

Под этим, вы должны кодировать две реализации: один для реального времени, а другой для пробы:

class RealTimeModeManager extends AbstractModeManager 
{ 
    public void updateOccurred(...) 
    { 
     // In this implementation, every time there has been an update, 
     // a query must be performed: 
     queryTheStateOfTheWorld(...); 
    } 
} 

class SampledModeManager extends AbstractModeManager 
{ 
    public SampledModeManager(int interval) 
    { 
     ... program a timer to call the queryTheStateOfTheWorld ... 
    } 

    public void updateOccurred(...) 
    { 
     // Do nothing. 
    } 
} 

И наконец, вы должны решить, какие из них должны быть использованы в каждом случае, хранить его в переменной:

private AbstractModeManager mode=... 

... И взывать систематически его при каждом обновлении в Система:

mode.updateOccurred(...) 

Таким образом, после инициализации этой переменной, то все равно, в каком режиме система работает: Это поведение остается на ваше абстракции.

+0

Это не касается того, как можно определить произвольные функции путем анализа case на 'Mode', не изменяя сами 'Mode'. В чем смысл [алгебраических типов данных] (https://en.wikipedia.org/wiki/Algebraic_data_type). – pyon

+0

Да, вы правы: это потому, что я предпочел сосредоточиться на шаблоне абстракции. В этом случае шаблон посетителя, который вы предложили, является хорошим выбором, а также шаблон слушателя. –

Смежные вопросы