Interfaccia marcatore: esempio pratico di codice di "comunicazione di un vincolo semantico / garanzia che altrimenti non modifica l'API

1

Iniziamo con una definizione allentata di "interfaccia marker" (correggimi o contestalo se non sei d'accordo):

If applied to a type, a marker interface doesn't provide any contract of methods to implement for that type. A marker interface gives semantic meaning.

Quindi, OK, va bene. Tuttavia, dare un significato a qualcosa deve alla fine avere un consumo pratico o l'uso altrove per essere di beneficio, giusto? Quindi potremmo avere due tipi che sono diversi, ma che sono (per esempio) i tipi relativi a "attività pianificate" in un sistema, quindi potremmo "decorarli" entrambi con un'interfaccia "IAmTask".

OK, quindi come lo mettiamo in pratica? Ho letto questo:

One of the benefits of a marker interface is to 'communicate a semantic constraint/guarantee that doesn't otherwise change the API'.

OK, beh, un'interfaccia marcatore non sta definendo un contratto per i metodi che devono essere presenti sul tipo, quindi quale è un esempio pratico nel codice di come un vincolo / garanzia semantica può essere in definitiva restituito all'utente e consumato in un modo che rafforza la garanzia prevista per il consumatore, senza imporre metodi sull'API? Ci deve essere un modo per farlo, altrimenti stiamo semplicemente dichiarando un significato essenzialmente inutile.

    
posta Johnny 07.07.2017 - 02:02
fonte

1 risposta

2

Poiché le interfacce marker forniscono un significato semantico, possono essere utilizzate come annotazioni per decorare un tipo e puoi utilizzare la riflessione per prendere decisioni basate sulla semantica. L'interfaccia marker non fornisce alcun contratto applicabile al compilatore.

Ad esempio, hai un'interfaccia UserRepository e hai due implementazioni InMemoryUserRepository che memorizza gli utenti in memoria e DatabaseUserRepository che memorizza gli utenti in un database.

interface UserRepository { def storeUser(user) }
class InMemoryUserRepository implements UserRepository { ... }
class DatabaseUserRepository implements UserRepository { ... }

Potrei introdurre un'interfaccia marcatore per descrivere la semantica delle implementazioni. Ad esempio, se volessi trovare dinamicamente implementazioni di UserRepository con semantica locale, potrei introdurre l'interfaccia marker LocalRepository e avere InMemoryUserRepository implementare LocalRepository

interface UserRepository { def storeUser(user) }
interface LocalRepository {}
class InMemoryUserRepository implements UserRepository, LocalRepository { ... }
class DatabaseUserRepository implements UserRepository { ... }

L'utilità è che ora sono in grado di distinguere le due classi di repository l'una dall'altra e decidere di utilizzare quella con semantica locale tramite riflessione.

Alcuni altri esempi di utilizzo delle interfacce marker:

  • Grande (O) complessità di implementazione, ad es. java.util.RandomAccess (dal commento @immibis). Forse hai una dozzina di algoritmi di ordinamento diversi. Potresti introdurre interfacce marcatore con le peggiori case e le migliori complessità del caso.
  • Locale vs remoto. Questo è utile per selezionare dinamicamente una configurazione per il tuo sistema da testare. Potresti configurare il tuo sistema di iniezione delle dipendenze per preferire implementazioni marcate localmente durante il test.
  • Formato di serializzazione. Potrei avere un paio di serializzatori diversi per diversi formati di file che hanno tutti la stessa interfaccia. Potrei introdurre un'interfaccia marker per distinguere serializzatori JSON e serializzatori XML, e usare le preferenze di runtime per usare il formato file. Oppure usa un'interfaccia marcatore per descrivere il loro ingombro e scegli il miglior serializzatore per il trasferimento di rete in base alle dimensioni.
risposta data 07.07.2017 - 03:29
fonte

Leggi altre domande sui tag