Requring il setter della classe statica da chiamare prima del costruttore, cattivo design?

4

Ho una classe, diciamo Foo, e ogni istanza di Foo avrà bisogno e conterrà lo stesso oggetto List, myList.

Poiché ogni istanza di classe condividerà lo stesso oggetto List, ho pensato che sarebbe stato utile rendere myList static e utilizzare una funzione statica per impostare myList prima che venga chiamato il costruttore.

Mi stavo chiedendo se questo fosse sbagliato, perché questo richiede che il setter venga chiamato prima del costruttore? Se la persona non lo fa, il programma si bloccherà. Il modo alternativo sarebbe passare myList ogni volta.

    
posta roverred 19.10.2013 - 23:46
fonte

5 risposte

6

Richiedere un certo metodo per chiamarsi prima un altro è chiamato Accoppiamento sequenziale , ed è spesso considerato un antipattern. Prendi ad esempio questo generatore di codice:

cg.enterScope();
... // add operations inside this scope
cg.leaveScope();

Qui, leaveScope deve sempre essere chiamato dopo enterScope , altrimenti viene emesso un codice errato. Questo può essere banalmente refactored per usare un tipo Scope :

Scope(scoped_operations).generate_code(cg);

Il tuo caso è più complicato, dato che vuoi inizializzare pigramente un membro statico prima che vengano create tutte le istanze di quella classe. Immagino che assomigli a

TheClass.initialize();
var instance = new TheClass();  // first use

Il primo passo è rendere initialize threadsafe. Quindi, deve registrare se è già stato eseguito. Se è così, ogni chiamata futura semplicemente cortocircuito:

static bool is_initialized = false;

static synchronized void initialize() {
  if (is_initialized) return;
  ...; // normal initialization
  is_initialized = true;
}

Ora possiamo fare questa chiamata all'interno del costruttore per assicurarci che la classe sia sempre inizializzata prima dell'esecuzione di altri codici:

TheClass(...) {
  if (!is_initialized) initialize(); // try to avoid call to synchronized method
  ...;
}

Se l'inizializzazione non deve avvenire pigramente, è possibile utilizzare varie soluzioni più semplici. (In questo contesto, l'inizializzazione pigra significa che l'inizializzazione viene eseguita sempre solo se viene creata un'istanza). Per qualcosa come economico come creazione di un oggetto List, probabilmente vorrai inizializzarlo indipendentemente da qualsiasi oggetto creato. Lingue diverse hanno meccanismi diversi, per esempio

// Java, C#
static List<Foo> myList = new List<Foo>();

// Java: static initializer block
static {
  myList = new List<Foo>();
}

Se il valore di inizializzazione non è noto al momento della compilazione, ma è piuttosto fornito da un utente, questo si complica nuovamente. In alcune lingue è possibile parametrizzare le classi stesse, ad es. %codice%. Nelle lingue prive di un Metaobject Protocol, è possibile utilizzare Pattern di fabbrica : TheClass = new TheMetaClass(42); instance = new TheClass() . All'inizio sembra simile all'esempio accoppiato sequenzialmente, ma con una fabbrica il sistema di tipi può garantire che l'inizializzazione sia avvenuta prima che venga creata un'istanza.

    
risposta data 21.10.2013 - 10:54
fonte
4

Sì, è brutto.

Considera l'alternativa in cui passi sempre nella lista in costruttore; puoi ancora fare in modo che ogni oggetto riceva lo stesso elenco, ad es. memorizzando l'elenco in fabbrica per l'oggetto e passandolo su nuovo.

E poiché la classe annuncia che ha bisogno di un'istanza di lista, offrendo solo un costruttore che accetta questa istanza, l'utente sa che sta passando in null se non sta usando la fabbrica. Questo è molto più ovvio che richiedere di chiamare una funzione statica.

    
risposta data 21.10.2013 - 13:49
fonte
3

In C # è possibile creare l'istanza dell'elenco nel costruttore statico implicitamente:

static List<int> myList = new List<int>();

public Foo()
{
   myList.Add(42); // note: not thread safe!!!
}

o esplicitamente:

static List<int> myList;

static Foo()
{
   myList = new List<int>();    
}

public Foo()
{
   myList.Add(42); // note: not thread safe!!!
}

È meglio che impostarlo in modo indipendente per una serie di motivi, ma principalmente:

  • I costruttori statici vengono eseguiti una volta sola (utile in contesti multithreading)
  • I costruttori statici vengono eseguiti prima di ogni altra cosa, quindi puoi presumere che il campo sia sempre inizializzato

Più in generale, è buona pratica rendere una classe "pronta all'uso" nel costruttore e membri statici nel costruttore statico. Ciò impedisce di avere un'istanza con campi non inizializzati.

Richiedere un particolare ordine di chiamate è probabilmente un anti-pattern noto come Sequential Coupling e eviterei di farlo a meno che necessario.

    
risposta data 20.10.2013 - 00:05
fonte
1

Con lo stato globale hai implementato efficacemente un FooFactory come un singleton (beh, usando i campi static , ma l'effetto è lo stesso).

Che ne direste di rendere esplicita la parte di fabbrica e di eliminare la parte di singleton (dato che Singleton è abbastanza non testabile, a causa del problema dell'azione a distanza):

public class FooFactory {
  private List myList;
  public FooFactory(List myList) {
    // ideally probably create an immutable copy of it
    this.myList = Collections.unmodifiableList(new ArrayList(myList));
    // ensure that myList has all the required characteristics
    if (this.myList.isEmpty()) {
      throw new IllegalArgumentException("Can't build a FooFactory with an empty myList");
    }
  }

  public Foo newFoo() {
    return new Foo(myList);
  }
}

Questo, insieme a un costruttore privato di private_cartoni% del pacchetto (in modo che nessuno possa seguire la rotta diretta e pericolosa), assicurerà che vengano creati solo gli% oggetti Foo .

  • nessuna azione a distanza, fai sempre riferimento esplicitamente al Foo che contiene lo stato
  • non cambierà involontariamente qualcun altro FooFactory (dato che è immutabile)
  • non puoi creare accidentalmente un oggetto FooFactory rotto
risposta data 24.10.2013 - 13:16
fonte
0

Un'altra possibile soluzione sarebbe utilizzare il modello di progettazione Singleton .

Un esempio reale sarebbe se un lavoratore ha un compito da svolgere e tale attività richiede l'uso di un elenco condiviso perché il manager dovrebbe dire al lavoratore quale lista usare quando il suo compito sa già quale lista è necessaria?

Rendi la classe myList a singleton, quindi nel costruttore della classe Foo ottieni un puntatore all'istanza di un oggetto myList .

Class myList
{
  static myList * get_instance()
  {
    static myList m_instance;
    return &m_instance;
  }
};

Class Foo
{
  public Foo()
  {
    myList * m_list = MyList::get_instance();
  }
};

Questa soluzione è la più semplice per i requisiti dichiarati perché non richiede che il codice che crea l'oggetto si preoccupi della lista condivisa, ma crea semplicemente l'oggetto Foo.

    
risposta data 22.10.2013 - 21:23
fonte

Leggi altre domande sui tag