C # non ha classi di amici - quali sono le opzioni migliori

5

Ritorno a questa domanda ogni due anni, quindi ora ho deciso di risolverlo una volta per tutte, chiedendo qui.

Quindi, la sequenza:

  1. Sto scrivendo una semplice applicazione che analizza il file Json (configurazione per app di terze parti, ulteriormente denominata config ) e consente all'utente di apportare alcune modifiche con esso. Ho scelto una piattaforma, ho creato un'interfaccia utente di base, ho associato alcuni pulsanti a NewFileCommand, OpenFileCommand, SaveFileCommand, ecc.
  2. Ora ho bisogno di memorizzare da qualche parte la configurazione aperta (e deserializzata in una classe), così ho creato una nuova classe StorageService. Ha alcuni metodi come OpenFile (), ReloadFile (), SaveFile (), ecc. Vengono richiamati dal ViewModel, StorageService apre il file, legge la configurazione e lo memorizza come campo . Quando necessario, lo salva in un file, finora così buono.
  3. In questo momento, voglio aggiungere alcune manipolazioni con la configurazione stessa. Io davvero non voglio metterlo nello StorageService. Alcuni metodi come GetConfigPropertyByNameAndDoSomeMagicWithIt () devono essere sicuramente collocati su un altro servizio.
  4. Quindi, ho creato un altro servizio, OperationsService. Ora voglio che abbia un accesso alla configurazione, memorizzato in StorageService e aggiungere metodi GetConfigPropertyByNameAndDoSomeMagicWithIt (). Non voglio esporre StorageService.config nell'API pubblica, perché ho già tutti gli accessor necessari. Per tutti gli altri, ad eccezione di OperationsService. Che voglio praticamente fare quello, chi può accedere a StorageService.config. Sembra un buon caso d'uso per il vecchio concetto C ++ della classe "friend", dove OperationsService può essere dichiarato amico di StorageService e accedere ai suoi interni.
  5. Sto ricordando che ho già affrontato un problema simile in precedenza, quindi ho cercato su Google "Classe amichevole C #", aperto il primo link nella speranza di trovare una soluzione moderna - e ho capito che ho un sentimento deja vu.

Quindi, qual è il flusso nel mio approccio attuale? Come risolverlo nel modo di "architettura moderna OOP corretta"?
Rendi OperationsService una classe interna di StorageService? Ciò significherebbe che tutto il codice verrebbe memorizzato nello stesso file (praticamente una violazione di SOLID).
Rendi OperationsService una classe interna di StorageService e rendila parziale e spostati in un altro file? Sembra un po 'raccapricciante.

In generale, non voglio nemmeno che OperationsService sia esposto (nemmeno per registrarlo nel contenitore IoC) per il resto dell'app - solo per fare il lavoro pesante. OperationsService non sarebbe così grande, quindi non voglio creare un terzo (una sorta di servizio wrapper).

In termini di oggetti posso riformulare il problema come "l'oggetto A ha un campo privato, che dovrebbe essere accessibile solo per l'oggetto B. La responsabilità è IO, la responsabilità dell'oggetto è una logica, quindi non possono essere unite".

Per riassumere, abbiamo:

  • config, che è POCO e contiene dati deserializzati da Json.
  • StorageService, che contiene OpenFile (), SaveFile (). Inoltre, contiene l'istanza attuale della configurazione.
  • OperationsService teorico, che contiene una logica in GetConfigPropertyByNameAndDoSomeMagicWithIt (). Per poter eseguire questa logica, deve avere anche un accesso alla configurazione. La domanda è come mettere in relazione OperationsService e StorageService.
posta Vitalii Vasylenko 08.08.2018 - 23:04
fonte

4 risposte

1

Un modo di priveligare OperationsService sul resto del sistema rispetto a dati specifici è quello di passare direttamente tali dati ad esso su una necessità di utilizzo: si può mantenere la logica di manipolazione della configurazione in OperationsService e aggiungere un pubblico Metodo AcceptOperations() in StorageService che accetta un oggetto OperationsService come argomento e richiama la relativa logica di manipolazione della configurazione, passandogli direttamente tutti i dati di configurazione rilevanti privati.

Concretamente, potresti ancora GetConfigPropertyByNameAndDoSomeMagicWithIt() essere membro di OperationsService , che chiamerebbe AcceptOperations() sull'istanza StorageService rilevante e passare this come arg. AcceptOperations() stesso chiamerebbe un metodo concreto ManipulateConfig() sull'istanza OperationsService assegnata come parametro, passando i dati di configurazione interni da manipolare come argomenti per ManipulateConfig() .

Questa proposta assomiglia in parte al classico OOP Pattern visitatori , escludendo il Double Dispatch idioma ma mantenendo il concetto di definire in modo restrittivo chi può visitare cosa e in definitiva in linea con l'idea originale dietro il modello che è di eseguire tale separazione delle responsabilità tale che "... [fornisce] la possibilità di aggiungere nuove operazioni alle strutture oggettuali esistenti senza modificare le strutture. " L'intera idea di tale modello è di aiutare a seguire Principio Open-closed che è uno dei principi SOLID, tenendo conto dei quali hai menzionato di essere una tua preoccupazione, per cominciare.

    
risposta data 09.08.2018 - 00:58
fonte
5

Puoi mettere le classi che vuoi essere amichevoli l'una con l'altra in un assembly separato. Quindi dichiara solo quelli che desideri siano disponibili dall'esterno dell'assembly come pubblico . Quelli che desideri nascondere dalle classi in altri assembly che dichiari interno . Questo dovrebbe darti praticamente quello che vuoi.

Da una prospettiva OOP questo è meglio di un amico dato che l'amico consente l'accesso oltre i membri pubblici, che è intrinsecamente contrario all'OOP.

    
risposta data 09.08.2018 - 00:03
fonte
1

Gli oggetti dovrebbero gestire il loro stato interno e dovrebbero modificarsi se necessario. Penso che tu abbia estratto gli oggetti in modo errato. Storage Service è in realtà un gestore di persistenza e non deve contenere valori di configurazione come valori interni. Il servizio di archiviazione dovrebbe solo gestire la traduzione da file a oggetto e viceversa. allora potresti avere un oggetto di configurazione che ha tutti i metodi che fanno roba per i valori di configurazione o esporli, o una classe di manager di configurazione che accetta un oggetto di configurazione durante la creazione e espone i metodi di accesso necessari.

Il motivo per cui hai problemi è che Storage Service sta attualmente infrangendo il principio della responsabilità unica. Diventa più evidente quando stai tentando di romperlo ulteriormente con GetConfigPropertyByNameAndDoSomeMagicWithIt() . In tal caso, la soluzione adeguata sarebbe quella di ridefinire gli oggetti con responsabilità meglio definite. l'amico è una soluzione di cerotto per un design scadente.

    
risposta data 09.08.2018 - 14:30
fonte
0

Penso che quello che ti manca è IConfigurationService, una versione concreta della quale dipende da IConfigStorageService. È possibile avere LocalConfigStorageService, che preleva la configurazione da un file ... o S3ConfigStorageService che recupera da un bucket del cloud. O anche DatabaseConfigStorageService che lo preleva da un database. In tutti e tre i casi IConfigStorageService è responsabile del recupero di una configurazione e dell'archiviazione dell'aggiornamento alla configurazione per IConfigurationService.

IConfigurationService utilizza la configurazione restituita per rispondere alle richieste dei consumatori.

Questa è specificamente un'istanza del Pattern Bridge

    
risposta data 09.08.2018 - 18:15
fonte

Leggi altre domande sui tag