Buon design per la classe con costruttori simili

2

Stavo leggendo questa domanda e pensavo che fossero stati fatti buoni punti, ma la maggior parte delle soluzioni prevedeva la ridenominazione di uno dei metodi. Sto refactoring un po 'di codice scritto male e ho incontrato questa situazione:

public class Entity {

  public Entity(String uniqueIdentifier, boolean isSerialNumber) {
    if (isSerialNumber) {
      this.serialNumber = uniqueIdentifier;
      //Lookup other data
    } else {
      this.primaryKey = uniqueIdentifier;
      // Lookup other data with different query
    }
  }

}

L'evidente difetto di progettazione è che qualcuno aveva bisogno di due modi diversi per creare l'oggetto, ma non poteva sovraccaricare il costruttore poiché entrambi gli identificatori erano dello stesso tipo ( String ). Così hanno aggiunto una bandiera per differenziare.

Quindi, la mia domanda è questa: quando si presenta questa situazione, quali sono i buoni progetti per differenziare tra questi due modi di istanziare un oggetto?

I miei primi pensieri

  1. Potresti creare due diversi metodi statici per creare il tuo oggetto. I nomi dei metodi potrebbero essere diversi. Questo è debole perché i metodi statici non vengono ereditati.
  2. Potresti creare oggetti diversi per forzare i tipi a essere diversi (ad esempio, crea una classe PrimaryKey e una classe SerialNumber ). Mi piace perché sembra essere un design migliore, ma è anche un doloroso refactoring se serialNumber è un String ovunque.
posta RustyTheBoyRobot 06.06.2012 - 22:11
fonte

4 risposte

3

Una cosa che puoi fare è sottoclasse Entity e definire costruttori specifici per ciascuno. Scommetterei che c'è un altro codice che potrebbe essere spostato nelle sottoclassi da quello principale. Se non ora, probabilmente in futuro. In entrambi i casi, hai ancora tutte le funzionalità comuni in Entity.

public class SerialEntity extends Entity{
    public SerialEntity(String serialNumber){
        this.serialNumber = serialNumber;
    }
}

public class PKEntity extends Entity{
    public PKEntity(String primaryKey){
        this.primaryKey= primaryKey;
    }
}

Forse allo stesso tempo rendere Entity astratto? Ma è leggermente fuori portata qui.

Tuttavia, se la cosa solo diversa su Entity è l'identificatore, quindi crea un'interfaccia EntityIdentifier con le implementazioni per ciascun tipo di identificatore. In questo modo Entity richiede solo un costruttore con un singolo argomento.

public class SerialIdentifier implements EntityIdentifier{
    private final String serialNumber;

    public SerialIdentifier(String serialNumber){
        this.serialNumber = serialNumber;
    }

    @Override
    public String getKey(){
        return serialNumber;
    }
}

public class Entity{
    public Entity(EntityIdentifier id){
        this.id = id;
    }
}

(Questo presuppone che tu abbia sempre bisogno solo di serialNumber xor primaryKey , però.)

Penso che l'approccio identificativo sia complementare alla creazione di tipi specifici per i valori stessi. Cioè, se la tua applicazione ha davvero bisogno di un tipo SeraialNumber , ma non ha bisogno di un tipo "chiave pimaria", allora puoi facilmente refactificare il SerialIdentifier sopra per lavorare con il nuovo tipo. Questo disaccoppia anche SerialNumber dall'essere un tipo di identificatore, se tale disaccoppiamento è necessario. La bellezza di questo è che non dovresti preoccuparti del resto della classe Entity perché funziona già contro l'interfaccia EntityIdentifier ed è indipendente da ciò che è effettivamente "id".

    
risposta data 06.06.2012 - 22:33
fonte
3

Personalmente, la mia soluzione a questo sarebbe di considerare il costruttore non prendere parametri e usare l'iniezione setter. Dopo tutto, se il numero di serie della metà del tempo può essere non inizializzato e metà della volta che la chiave primaria può essere non inizializzata, è ovvio che nessuno è essenziale per il funzionamento della classe. Poiché questo è il caso, dimentica la goffa ginnastica primitiva e lascia ai clienti la possibilità di specificare quali dati hanno.

Questo potrebbe rivelare il requisito che uno o l'altro sia impostato, ma ciò sembrerebbe portare a considerare due classi fuori dalla classe target piuttosto che rappresentare una sorta di struttura sindacale in stile C.

    
risposta data 06.06.2012 - 23:24
fonte
1

Mi piace la seconda opzione che hai dato. È sicuro e per me è più intuitivo.

Non sono sicuro che il codice qui sia C # o Java, ma se è C #, puoi prendere la seconda opzione e aggiungere un conversione implicita a string per aiutare con gli altri posti in cui serialNumber potrebbe essere usato come stringa.

public class SerialNumber
{
    private string serialNumber = null;

    public SerialNumber(string serialNumber)
    {
        this.serialNumber = serialNumber;
    }

    public static implicit operator string(SerialNumber sn)
    {
        return sn.serialNumber;
    }
}

Ovviamente, in passato gli operatori impliciti sono stati disapprovati, quindi dovresti valutare i pro e i contro.

    
risposta data 06.06.2012 - 22:20
fonte
1

Il numero 2 è facilmente la soluzione migliore. Ti consente di eseguire la convalida e fornisce una distinzione effettiva nel sistema di tipi per tipi distinti.

    
risposta data 06.06.2012 - 22:21
fonte

Leggi altre domande sui tag