Come condividere i membri dei dati tra le classi in C ++ senza violare troppo l'incapsulamento

4

In C ++ diciamo che ho qualche classe A:

Class A
      {
      int a1, a2, a3;

      void foo();
      }

e ho bisogno di usare un sottoinsieme di membri (a1, a2) in una funzione membro per una seconda classe B.

Quello che mi chiedo è se dovrei definire gli argomenti della funzione membro di B passando un puntatore ad A come argomento, o se dovrei passare i membri di A come argomenti. per es.,

Class B
      {
      int b1, b2;

      void bar(A &a);
      }

o dovrebbe essere

Class B
      {
      int b1, b2;

      void bar(int a1, int a2);
      }

Quest'ultimo sembrerebbe minimizzare di dover violare l'incapsulamento, perché B può quindi essere in gran parte agnostico sui costituenti di A; mentre nel primo caso B dovrebbe sapere qualcosa sui membri di A. Quindi sembrerebbe preferibile la seconda implementazione.

Un altro motivo per cui la prima implementazione mi sembra problematica è che, idealmente, mi piacerebbe mantenere i membri (a1, a2, a3) di A protetti piuttosto che pubblici. Normalmente sarei tentato di fare di B una classe amica di A, ma il problema qui è che A e B hanno entrambe entrambe diverse classi derivate, e poiché l'amicizia non eredita in C ++ non sembra essere una buona soluzione.

Quindi questi motivi potrebbero sostenere l'utilizzo di qualcosa come la seconda definizione di B. Tuttavia, nel codice reale che sto trattando, ci sono alcuni (circa 6 o più) membri di A che B richiederà, quindi questo potrebbe diventare poco maneggevole. Essere in grado di passare semplicemente un puntatore a A sembra preferibile da un punto di vista di leggibilità e nascondere i dettagli di ciò che richiede B.bar () da altre parti del codice che trattano di B.

Forse una soluzione potrebbe essere quella di avere metodi in A che facciano qualcosa del tipo: A::get_a1() {return this->a1} o qualcosa del genere, ma non sono nemmeno sicuro se questa sia davvero la soluzione di design corretta qui.

    
posta user1790399 04.08.2015 - 17:03
fonte

3 risposte

5

However, in the actual code I'm dealing with, there are quite a few (about 6 or more) members of A that B will require, so this could get unwieldy.

Crea una classe Trasferimento dati che contiene i membri che desideri trasferire dalla classe A alla classe B, quindi scrivi il tuo metodo sulla classe B in modo che richieda un'istanza della nuova classe.

    
risposta data 04.08.2015 - 17:10
fonte
3

Raccomando di astrarre questo livello, soprattutto se trovi che hai bisogno di a1 e a2 in più posizioni. I passaggi vanno in questo modo:

  1. Identifica quali dati richiede la funzione.

  2. Raggruppa i dati insieme in una propria unità.

  3. Descrivi i dati. Che cosa rappresentano questi dati?

  4. Crea un'interfaccia 1 dal nome della descrizione dei dati.

Ora erediti dall'interfaccia nella classe A e la funzione class B prende un riferimento alla nuova interfaccia.

I vantaggi:

  1. Accoppiamento lento : tu disgiungi le due classi. Se B deve sapere sui dati che A ha ma non è appropriato che siano strettamente accoppiati, l'interfaccia aggiunge uno strato di astrazione che mantiene pulito il progetto (anche se un po 'più grande).

  2. Riutilizzo del codice : se i dati nell'interfaccia sono applicabili in altri luoghi, puoi riutilizzare l'interfaccia.

  3. Se un particolare uso dell'interfaccia si presta maggiormente a suggerire Robert Harvey di utilizzare una classe Data Transfer, è possibile creare un'implementazione barebone dell'interfaccia (pura classe virtuale) e usarla. Le due classi sarebbero quindi intercambiabili per quanto riguarda gli utenti dell'interfaccia, il che aggiunge flessibilità.

Esempio di codice:

class Foo {
public:
  int getA1() const = 0;
  int getA2() const = 0;
};

class A : public Foo {
  int a1, a2, a3;
public:
  // C++11 adds the "override" keyword.
  int getA1() const override { return a1; }
  int getA2() const override { return a2; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a);

Nota: potrebbe anche essere valido che la classe A contenga una Foo tramite la composizione anziché implementarla tramite l'ereditarietà. Questa è probabilmente la soluzione migliore, ma sulla base della formulazione della tua domanda ho ritenuto che la soluzione di cui sopra fosse più appropriata. Ecco un esempio di codice per la completezza:

class Foo {
  int a1, a2;
public:
  int getA1() const { return a1; }
  int getA2() const { return a2; }
};

class A {
  int a3;
  Foo foo;
public:
  const Foo& getFoo() const { return foo; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a.getFoo());

1 C ++ non ha parole chiave interface come fanno certe altre lingue. Né ha un concetto di classe abstract o pure virtual : questi concetti si applicano solo a funzioni nelle classi. In questo contesto, un'interfaccia C ++ significa una classe in cui tutte le funzioni sono% virtual . Le uniche eccezioni sono costruttori e il distruttore, che non può essere virtual e deve esistere anche se il compilatore li genera per te. Si noti inoltre che il distruttore deve essere dichiarato virtuale per qualsiasi classe progettata per avere sottoclassi.

    
risposta data 06.08.2015 - 16:04
fonte
0

Se vuoi evitare una "classe di trasferimento dati" extra come suggerito da @Robert Harvey, devi accedere alle tue variabili membro tramite le funzioni getter e setter.
Quindi il tuo approccio descritto nel tuo ultimo paragrafo

Perhaps one work around would be to have methods inside A that do something like:A::get_a1() {return this->a1} or something, but I'm also not sure if that's really the correct design solution here.

sarebbe la soluzione giusta per me.

Tuttavia, ho creato dozzine di classi (in C #, ma il problema di fondo era lo stesso) con un gruppo di variabili membro in ciascuna. Avevo anche bisogno di accedere a queste variabili membro, come fai tu. Hai solo bisogno di leggere il più lontano possibile. Sto scrivendo anche loro.
La mia soluzione non era una coppia di funzione get / set per ogni membro (il che significa che per i membri n devi creare 2 * n funzioni), ma con solo 1 get-function e 1 set-function (o 1 di get / set per ogni membro tipo variabile). La scelta della variabile da impostare viene eseguita passando una variabile intera (un Enum, per essere precisi: PID, ID parametro) con quelle funzioni:

int GetValue (EnumPID i_enPID, out int    o_iValue);
int GetValue (EnumPID i_enPID, out double o_dValue);
int GetValue (EnumPID i_enPID, out String o_sValue);  // C#
...
int SetValue (EnumPID i_enPID, int    i_iValue);
int SetValue (EnumPID i_enPID, double i_dValue);
int SetValue (EnumPID i_enPID, String i_sValue);  // C#
...
// the int return value is the result of the function: SUCCESS (=1), failure (= 1001 upwards).
// Actually, it's an Enum in my case, too, (EnumResult)...

I crediti per questa soluzione vanno al mio ex capo, che mi ha mostrato questo tipo di design.
Da allora ha funzionato perfettamente in C #.

    
risposta data 06.08.2015 - 15:25
fonte

Leggi altre domande sui tag