"Uno di alcuni" tipi

3

Dì che hai alcune classi diverse che non hanno alcuna funzionalità in comune, ma da qualche parte nel tuo programma, hai bisogno di una (che non è necessariamente importante) perché hanno un funzionalità in quel contesto. Qual è il modo migliore per farlo?

So che puoi prendere un Object e usare instanceof per verificare se è uno di quei tipi, ma sto cercando un modo per far sì che il sistema di tipi assicuri che sarà solo uno di quei tipi . O è impraticabile / impossibile?

Ciò che ho pensato di fare era creare un'interfaccia vuota e implementarla in tutte le classi che lo richiedevano. ma ho visto che si suppone che usi le annotazioni invece di un'interfaccia marker , ma non sono sicuro che si applica in questa situazione perché non mi interessa davvero le informazioni di runtime. Un'altra cosa che mi preoccupa è che l'implementazione di quell'interfaccia in quelle classi causerà il codice spaghetti. Quelle classi non dovrebbero davvero sapere nulla su come sono usate altrove.

    
posta Torm 12.04.2015 - 05:12
fonte

3 risposte

4

Ecco un'implementazione generica di barebone per due alternative:

public abstract class Either<A, B> {
    private Either() { /* prevent outside subclassing */ }

    public static Either<A, B> ofA(A a) {
        return new OptionA<A, B>(a);
    }

    public static Either<A, B> ofB(B b) {
        return new OptionB<A, B>(b);
    }

    public abstract <R> R match(Function<? super A, R> ifA, Function<? super B, R> ifB);

    private static final OptionA<A, B> extends Either<A, B> {
        private final A value;

        private OptionA(A value) { this.value = value; }

        @Override
        public <R> R match(
                Function<? super A, R> ifA,
                Function<? super B, R ifB) {
            return ifA.apply(value);
        }
    }

    private static final OptionB<A, B> extends Either<A, B> {
        private final B value;

        private OptionB(B value) { this.value = value; }

        @Override
        public <R> R match(
                Function<? super A, R> ifA,
                Function<? super B, R> ifB) {
            return ifB.apply(value);
        }
    }
}

Riempi gli oggetti di uno dei due tipi in Either e poi usa match per chiamare una funzione in base al tipo di oggetto boxed. La combinazione di una sottoclasse private constructor e final inner garantisce OptionA e OptionB sono le uniche sottoclassi possibili di Either , quindi a meno che non inizi a scherzare con il casting, se riesci a mettere le mani su un Either<Integer, String> tu sappi che può contenere solo Integer o String .

Dovrebbe essere abbastanza semplice per:

  • Estendi questo a più di 3 alternative.
  • Aggiungi metodi di convenienza come boolean hasA() o A getA() throws NoSuchElementException() per le volte in cui match sarebbe troppo prolisso.
  • Utilizza un visitatore interface con due metodi invece di due singoli Function s (ma Function s è più conciso se usi lambdas)
  • Scegli altri nomi per le alternative (ad esempio Left / Right o First / Second invece di A / B ).
  • Aggiungi equals , hashCode e tutto il resto.
  • Aggiungi null -check.
  • Aggiungi cache nei metodi di fabbrica.

Vedi anche Come si codificano i tipi di dati algebrici in un linguaggio C # o simile a Java? .

    
risposta data 16.04.2015 - 19:04
fonte
2

Non puoi fondere tre classi in modo che un'istanza di questa classe chimerica possa essere usata come un'istanza di uno dei tre tipi (come in LSP ).

Puoi creare una classe ombrello che, a differenza di un'interfaccia vuota, ti offre un vero controllo dei tipi.

class AppleBookCar() {
   final Apple apple;
   final Book book;
   final Car car;
   private AppleBookCar(Apple a, Book b, Car c) {
     apple = a; book = b; car = c;
   }
   public static AppleBookCar fromApple(Apple apple) { 
     return AppleBookCar(a, null, null);
   }
   public static AppleBookCar fromBook(Book book) { 
     return AppleBookCar(null, book, null);
   }
   public static AppleBookCar fromCar(Car car) { 
     return AppleBookCar(null, null, car);
   }
   public Apple asApple() { return this.apple; }
   public Book asBook() { return this.book; }
   public Car asCar() { return this.car; }
}

Questa norma è, ovviamente, noiosa; le lingue migliori lo scriverebbero per te.

    
risposta data 13.04.2015 - 00:29
fonte
0

Bene, tutti gli oggetti si estendono dalla classe Object, quindi potresti provare a creare una classe generica e passare come parametri ciascuno dei tipi. Ma non sono sicuro di come o perché sarebbero tutti collegati perché, come hai detto tu, non hanno niente a che fare l'uno con l'altro. Interessante.

    
risposta data 12.04.2015 - 05:37
fonte

Leggi altre domande sui tag