2015-07-15 17 views
8

Aplikacja może działać w dwóch trybach - "w czasie rzeczywistym", w której analizuje każdą aktualizację do stanu świata, lub "próbkowaną", gdy patrzy tylko na stan świata co T milisekund .Symulowanie ADT w Javie

Gdybym pisanie Haskell (lub dowolnego języka z ADTS) bym tego modelu jako

data Mode = RealTime | Sampled Int 

które mogą być wykorzystane w następujący typu bezpieczny sposób

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

powiedzieć, że jest "bezpieczny dla typu", ponieważ jeśli pracujesz w trybie czasu rzeczywistego, nie możesz próbować uzyskać dostępu do pola interval (które jest dostępne w momencie, gdy jest potrzebne, jeśli pracujesz w trybie próbkowania).

Jak mogę modelować to samo w Javie w bezpieczny sposób? Oznacza to, że chcę

  • Aby zdefiniować klasę (lub enum), który odróżnia między tymi dwoma trybami i
  • Aby zabronić dostępu do pola interval w trybie czasu rzeczywistego, a
  • Aby mieć wszystko to sprawdzane przez kompilator.

Czy to możliwe w Javie? Jeśli nie, jaki jest idiomatyczny sposób osiągnięcia tego rodzaju bezpieczeństwa?

Odpowiedz

9

Tradycyjny sposób naśladować zamkniętych algebraiczne typy danych w językach takich jak Java, że ​​tylko dostarcza otwarte zajęcia (które mogą być dziedziczone w każdej chwili) w typie bezpieczny sposób jest 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(); 
    } 
} 

Jako @ user3237465 zauważono, że możliwe jest kilka kodowań typów danych, które zdarzają się, aby pokryć się, gdy typ danych nie jest rekursywny: kodowanie Kościoła jest krotnością: pozwala gromadzić wartość przez rekursję w zakodowanym kościele typie danych. Kodowanie Scott odpowiada faktycznemu dopasowaniu do wzorca. W każdym przypadku użytkownicy mogą zostać wykorzystani do implementacji wszystkich tych kodowań. Dzięki za szturchnięcie @ user3237465!

+0

Dzięki, to wygląda najlepiej. Czy możemy uniknąć definiowania klasy 'ModeVisitor' w Javie 8, używając zamiast niej lambdas (np.' Publiczny akceptuje (funkcja )) lub czy coś w tym traci? –

+0

@ChrisTaylor: Potrzebujesz gościa, ponieważ sama metoda 'visit' jest przeciążona, a każda podklasa" Mode "musi wybrać odpowiednie przeciążenie. – pyon

+0

@ChrisTaylor: Właściwy użytkownik może być traktowany jako pakiet zawierający * kilka * lambd: jeden dla każdego wariantu ADT.Odwiedzający są analogiczne do wyników stosowania funkcji 'maybe' i' either' (ze standardowej biblioteki Haskella) do dwóch argumentów: 'maybe foo bar' użyje' foo' do odwiedzenia 'Nothing' i' bar' do odwiedzenia 'Just x'. – pyon

2

Można zadeklarować interfejs Mode

public interface Mode { 
    void doSmth(); 
} 

I wtedy można mieć dwie klasy implementujące ten interfejs

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 
    } 
} 

Następnie należy zadeklarować zmienną typu Mode

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

I następnie, od mode jest typu Mode, będziesz mieć dostęp tylko do tego, co zostało zadeklarowane w interfejsie. Możesz więc zadzwonić pod numer mode.doSmth() i, w zależności od tego, jak zainicjowałeś mode, uruchomi się w czasie rzeczywistym lub spróbuje.

Jeśli klasy mają jakiś kod, można zadeklarować klasę abstract zamiast interface i mają obie klasy extend tej klasy. Tak więc mogą wywołać wspólne metody, które są zadeklarowane w klasie abstract (która powinna być zadeklarowana z widocznością protected, tak aby tylko podklasy mogły je zobaczyć)

+0

Wydaje się, że to zmusza mnie do umieszczenia zachowania * wewnątrz * implementacji 'Mode', czy to prawda? Co jeśli mam różne ścieżki kodu, które chcą robić różne rzeczy w oparciu o tryb? W rzeczywistości jest to część biblioteki, więc użytkownicy biblioteki muszą mieć możliwość dodawania własnych zachowań dla różnych trybów. –

+0

@ChrisTaylor Java 8 obsługuje przekazywanie funkcji jako parametry. Zamiast deklarować metodę no-arg. Możesz zadeklarować funkcję, która otrzyma faktyczne zadanie do wykonania. Wtedy 'RealTime' po prostu wywoła tę funkcję w sposób ciągły, a' Sampling' wywoła ją co X sekund lub cokolwiek innego. Innym podejściem może być deklarowanie kilku metod 'ifRealTime (f)' oraz 'ifSampled (f)' do interfejsu i w konkretnych implementacjach, 'RealTime' wywoła funkcję w' ifRealTime' i nie zrobi nic w 'ifSampled' . Mam nadzieję, że się jasno wyjaśniłem. – xp500

+0

Zasada _abstrakcji_ polega na zaprojektowaniu interfejsu (lub klasy abstrakcyjnej), który całkowicie obejmuje wszystkie możliwe zachowania dotyczące funkcjonalności. Tak więc, jeśli w twoim kodzie jest kilka punktów, które zależą od tego, w jaki sposób jest realizowana ta funkcja, każdy z nich zasługuje na właściwy _method_ w tym interfejsie. –

0

Tak, z corse jest to możliwe, ale zależy od poziomu złożoności jesteś gotowy do osiągnięcia.

Jeśli chcesz osiągnąć najwyższy poziom typu solidność, polecam Ci zadeklarować abstrakcję (o nazwie AbstractModeManager), który odpowiedzialny będzie kwerendy stan świata i przechowywać go w zmiennych instancji, które powinny być dostępne za pomocą publicznych metod pozyskiwania.I musi też wiedzieć, kiedy nastąpiła zmiana:

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(...); 
} 

Under że trzeba zakodować dwie implementacje: jeden dla czasu rzeczywistego, a inny dla objętych próbą:

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. 
    } 
} 

i ostatni, musisz zdecydować, które z nich należy stosować w każdym przypadku, należy go przechowywać w zmiennej:

private AbstractModeManager mode=... 

... I powołać systematycznie go przy każdej aktualizacji w System:

mode.updateOccurred(...) 

W ten sposób, po uruchomieniu tej zmiennej, nie będzie obchodziło, który tryb jest uruchomiony system: To zachowanie jest pozostawione do abstrakcji.

+0

Nie obejmuje to sposobu definiowania dowolnych funkcji przez analizę przypadku w 'Trybie', bez modyfikowania samych trybów. O to właśnie chodzi [algebraiczne typy danych] (https://en.wikipedia.org/wiki/Algebraic_data_type). – pyon

+0

Tak, masz rację: to dlatego, że wolałem skupić się na abstrakcji. Jeśli o to chodzi, proponowany przez ciebie wzór odwiedzin to dobry wybór, a także wzorzec słuchacza. –