Java - Estende una classe che estende essa stessa una classe che a sua volta ... e così via ... è sana ..?

0

NOTA : sentiti libero di modificare il titolo se è leggermente diverso dalla mia domanda.

In una delle nostre applicazioni, stiamo mantenendo molte proprietà all'interno dell'oggetto Instance. Ovviamente è mantenuto come Map<K, Map...> . Esistono pochi oggetti di istanza che contengono diversi tipi di proprietà.

Ad esempio

1) AppPropertyReader - Map<String, String>

2) SMSPropertyReader - Map<UUID, Map<SMSType, String>> // SMSType is enum

3) PropertyReader - Map<UUID, Map<String, Map<String, String>>>

e alcuni altri ...

Quindi per ogni classe di lettori, devo definire getter (s) e setter. A seconda del numero di tipi annidati, è necessario introdurre nuovi getter. Ma sento che è un po 'più povero / brutto (qualcosa che non posso esprimere chiaramente) per duplicare il getter & altri codici essenziali in ogni classe di lettori. Quindi ho scritto tre classi generiche per gestire fino a tre mappe (come definite nella classe PropertyReader).

Titolare della mappa di base

import java.util.HashMap;
import java.util.Map;

public abstract class CommonPropertyHolder<K, V> {

    protected Map<K, V> properties;

    public CommonPropertyHolder() {
        init();
    }

    protected void init() {
        properties = new HashMap<K, V>();
    }

    public Map<K, V> getProperties() {
        return properties;
    }

    public V getProperty(K key) {
        return properties.get(key);
    }

    protected void setProperty(K key, V value) {
        properties.put(key, value);
    }

    protected void clear() {
        init();
    }

}

Titolare della mappa nidificato

import java.util.HashMap;
import java.util.Map;

public class CommonMapPropertyHolder<T, K, V> extends CommonPropertyHolder<T, Map<K, V>> {

    public V getProperty(T type, K key) {
        Map<K, V> properties = getProperty(type);
        if( properties == null ) return null;
        return properties.get(key);
    }

    protected void setProperty(T type, K key, V value) {
        Map<K, V> properties = getProperty(type);
        if( properties == null ) {
            properties = new HashMap<K, V>();
            setProperty(type, properties);
        }
        properties.put(key, value);
    }

}

Detentore della mappa nidificata profonda (non è sicuro se possa essere espresso come "Deep Nested")

import java.util.HashMap;
import java.util.Map;

public class CommonMultiMapPropertyHolder<T, PK, K, V> extends CommonMapPropertyHolder<T, PK, Map<K, V>> {

    public V getProperty(T type, PK parentKey, K key) {
        Map<K, V> properties = getProperty(type, parentKey);
        if( properties == null ) return null;
        return properties.get(key);
    }

    protected void setProperty(T type, PK parentKey, K key, V value) {
        Map<PK, Map<K, V>> mapProperties = getProperty(type);
        if( mapProperties == null ) {
            mapProperties = new HashMap<PK, Map<K, V>>();
            setProperty(type, mapProperties);
        }
        Map<K, V> properties = mapProperties.get(parentKey);
        if( properties == null ) {
            properties = new HashMap<K, V>();
            mapProperties.put(parentKey, properties);
        }
        properties.put(key, value);
    }

}

Quindi le mie classi di lettori di proprietà possono estendersi come

class AppPropertyReader extends CommonPropertyHolder<String, String>

class SMSPropertyReader extends CommonMapPropertyHolder<UUID, SMSType, String>

class PropertyReader extends CommonMultiMapPropertyHolder<UUID, String, String, String>

Funziona in modo incredibilmente perfetto.

Nella vista normale, queste classi generiche non faranno molta differenza. Ma se guardi come ogni classe fa riferimento a super metodi e variabili, saprai quanto è bella.

Se desidero introdurre un altro PropertyReader, devo semplicemente estendere la classe generica appropriata senza scrivere alcun codice diverso dalla dichiarazione della classe.

Ecco le mie domande.

1) Dal momento che sale fino a 3 livelli più profondi per la maggior parte dei generici parametrizzati, influenzerà l'esecuzione runtime ..? (Ho letto da qualche parte in StackOverflow che la profondità fino a 5 livelli non ha un impatto notevole sulle prestazioni.) Ma, dal momento che ogni classe estesa è generica, ci sarà un impatto sulle prestazioni ..?

2) Prima di inventare queste 3 classi generiche, ho cercato di raggiungere qualsiasi livello di mappe nidificate con una sola classe generica. Ma non riesco a trovare alcuna soluzione implementabile. È possibile implementare solo una singola classe generica in grado di gestire qualsiasi livello di profondità ...? (Naturalmente tutto deve essere ricorsivo se implementabile)

    
posta The Coder 11.07.2016 - 19:23
fonte

2 risposte

4

Per rispondere direttamente alle tue domande:

1) No, non influenzerà molto le prestazioni. Generics sono per lo più cancellati dal momento in cui il programma viene eseguito comunque, quindi il costo aggiuntivo è in realtà solo più (fino a 3) ricerche mappa invece di 1. Non mi aspetto che questo sia mai un collo di bottiglia in uno scenario in cui l'uso di uno standard HashMap è accettabile.

2) Non proprio. I generici sono usati per la sicurezza in fase di compilazione e al compilatore non piace molto nessuna cosa sconosciuta durante la compilazione, incluso il numero di parametri. Sento che i modelli C ++ farebbero il trucco, ma è una bestia piuttosto diversa (sebbene spesso usata per la stessa cosa).

Un paio di commenti secondari sul tuo approccio:

1) Almeno la mappa a due tasti è un problema molto comune e uno risolto. Cerca% gu_de% di Guava - è praticamente quello che vuoi, una tabella che ha 2 chiavi, insieme ad alcuni bonus aggiuntivi.

2) A meno che non sia necessario interrogare separatamente le mappe per riga o per colonna (piuttosto che fare solo letture o scritture di proprietà singole), una chiave combinata è preferibile quando ci sono più dimensioni. Puoi vedere come le tue firme aumentano di dimensioni. Mentre una semplice classe di chiavi avrà una forma molto più leggibile.

3) Se insisti su lunghi elenchi di tipi generici, un modo molto migliore per farlo sarebbe la composizione. È quello che stai facendo comunque, solo senza il disordine (hai 3 Table metodi esposti nel tuo "multimap", e se decidi di aver bisogno di un 4 ° param, il metodo continuerà solo in crescita). Tutti gli argomenti per il principio "Composizione sull'ereditarietà" definiti da numerose persone si applicano anche qui.

4) Un setProperty è comunemente noto come una mappa di chiavi per la raccolta di valori, piuttosto che per la mappa- a-map-to-map-to-valori. Potresti confondere i tuoi compagni di squadra se ti attieni a questa denominazione della tua classe:)

    
risposta data 11.07.2016 - 20:14
fonte
1

Se ti confonde un po ', è un problema. Qualcuno che non ha familiarità con il codice (che probabilmente sarai tu tra 2 mesi) sarà completamente sconcertato.

Dall'aspetto del tuo codice, ti stai lasciando sfuggire un sacco di complimenti. Si potrebbe essere meglio serviti guardando un singolo problema e dicendo "Qual è la cosa più semplice che potrebbe funzionare" e implementare solo questo - quindi passare al problema successivo. Se trovi somiglianze, fai comparire POIN refactoring in qualcosa di più generale. Quando lavori sulla logica di business, non provare immediatamente a creare strumenti riutilizzabili, ma crea una classe di business che fa solo ciò di cui hai bisogno e attendi che i pattern emergano più tardi.

Ecco perché presumo che tu stia creando strumenti generali: in Java, Generics è principalmente uno strumento per i costruttori di strumenti. Non esclusivamente (sono molto utili), ma se hai bisogno di una classe basata su hashmap che contiene, ad esempio, fatture indicizzate in base al numero di fattura, devi creare una classe che sia esattamente quella, InvoiceHolder. un getInvoice prende un numero di fattura e restituisce un oggetto di fattura - non sono necessari generici al di fuori della classe.

Sembra che tu stia cercando di semplificare l'accesso alle proprietà in tutta l'app, il che è un obiettivo lodevole, ma come ho detto prima, è il tooling. Il modo più semplice per capire gli strumenti è fare prima la cosa semplice e poi generalizzarla come richiesto da DRY, altrimenti YAGNI.

Un secondo punto: Estende in genere aggiunge alla complessità del tuo codice e non fornisce il vantaggio che penseresti. L'ho imparato nel modo più duro perché sembra uno strumento molto potente, ma di solito non funziona in questo modo. Ove possibile, considerare prima l'incapsulamento. Ciò non significa che non si estenda mai, significa solo che una combinazione di contenere un altro oggetto e / o implementare un'interfaccia sembra sempre funzionare meglio della sottoclasse anche se devi avere un paio di metodi di inoltro.

    
risposta data 11.07.2016 - 21:37
fonte

Leggi altre domande sui tag