2013-05-19 6 views
13

Sono nuovo al principio SOLID ma lo capisco. Il mio problema principale sta avendo difficoltà a progettare le mie classi per seguire il SOLID in particolare la Dependency Inversion. A volte è facile scrivere l'intera logica in un modello procedurale piuttosto che usare SOLID.Come praticare il principio SOLID del design OOP?

Ad esempio:

Diciamo che stiamo creando un sistema di presenza di monitoraggio, e noi abbiamo la logica (o procedure) che la scansione delle impronte digitali dei dipendenti, get è ID, determinare se si tratta di valido o no, determinare a che ora si trovava, scrivi le informazioni di accesso al database e mostra se ha successo o meno.

E 'facile scrivere questo in un modo procedurale con un gruppo di 'se gli altri', loop e switch. Ma in futuro subirò il "debito in codice".

Se applichiamo il principio SOLID qui. So che abbiamo bisogno di avere un qualche tipo di oggetto come "AttendanceServiceClass" che abbia un metodo come "scanEmployeeID()", "processthislogin()" o "isItsucessful()". E so che questa classe ha una dipendenza da un repository, userinfo e altri oggetti.

Fondamentalmente il mio problema sta analizzando circa il disegno della classe e le sue dipendenze

Qual è il passo per passo modo di analizzare il design della vostra classe?

scusate il mio inglese.

+0

SOLID è l'acronimo di più principi. Devi prima capirli e poi provare ad applicarli quando scrivi il codice. Le ricette non esistono realmente, non si otterrà un passo alla volta come fare le cose altrimenti non ci sarebbe bisogno di sviluppatori umani. Ogni applicazione e problema ha le sue sfide e il contesto unici, ciò che ha funzionato in un caso potrebbe non funzionare in altri casi. – MikeSW

+0

Questa domanda sembra essere troppo ampia per poter rispondere in modo efficace. – anotherdave

+0

Un sacco di buoni video su SOLID può essere trovato qui: [DimeCasts.net] (http://dimecasts.net/Casts/ByTag/SOLID%20Principle) – PositiveGuy

risposta

7

A volte è facile scrivere tutta la logica in modello procedurale piuttosto che usare SOLID

non posso essere più d'accordo, è più facile per noi programmatore per gestire il codice nel modello procedurale. Questo rende OOP difficile da programmare per chi è abituato alla programmazione procedurale.

Tuttavia ho trovato più semplice scrivere prima l'interfaccia generale e il consumatore piuttosto che rompere l'interfaccia progettata per moduli più piccoli. Questo è, una specie di pratica Test First Development -> Red, green, refactor. (si prega di notare che, se si vuole raggiungere il neat design, considerare i seguenti TDD invece questa guida. Questa guida è solo una piccola sezione di fare TDD)

dire che vogliamo creare il ServiceAttendance fare scanEmployeeID. Ci sarà ha un'interfaccia simile (si prega di notare l'esempio è in C# di denominazione):

public interface IServiceAttendance{ 
    bool ScanEmployeeId(); 
} 

Si prega di notare che decido il metodo per restituire bool invece di vuoto per determinare il successo/fallimento dell'operazione. Si prega di notare l'esempio di consumo di seguito non implementare alcun DI perché voglio solo mostrare come consumarlo. Quindi nel consumatore, possiamo avere:

public void ConsumeServiceAttendance(){ 
    IServiceAttendance attendance = Resolve<IServiceAttendance>(); 
    if(attendance.ScanEmployeeId()){ 
     // do something 
    } 
} 

Questo conclude il consumatore. Ora passiamo all'implementazione. Di 'che puoi svilupparlo usando la programmazione procedurale e ottenuto il blocco di codice monolitico. È possibile dichiarare l'implementazione con una dichiarazione simile a pseu.

public class ServiceAttendance : IServiceAttendance{ 
    public bool ScanEmployeeId(){ 
     bool isEmpValid = false; 
     // 1 scan the employee id 
     // 2 validate the login 
     // 3 if valid, create the login session 
     // 4 notify the user 
     return isEmpValid; 
    } 
} 

Ora abbiamo 4 passaggi da fare in questa unica operazione. Il mio principale è, non fare più di 3 processi di facciata in un metodo, quindi posso semplicemente refactoring il 3 e il 4 per un processo. Ora abbiamo

public class ServiceAttendance : IServiceAttendance{ 
    public bool ScanEmployeeId(){ 
     bool isEmpValid = false; 
     // 1 scan the employee id 
     // 2 validate the login 
     // 3 if valid, create the login session and notify the user 
     return isEmpValid; 
    } 
} 

Questo, abbiamo 3 operazione principale. Possiamo analizzare se abbiamo bisogno di creare un modulo più piccolo o meno abbattendo l'operazione. Diciamo che vogliamo interrompere la seconda operazione. Possiamo ottenere:

// 2 validate the login 
// 2.1 check if employee id matches the format policy 
// 2.2 check if employee id exists in repository 
// 2.3 check if employee id valid to access the module 

L'operazione di interruzione in sé è abbastanza ovvia da interrompere il secondo modulo in un altro modulo più piccolo. Per 2.2 e 2.3, abbiamo bisogno di un modulo più piccolo da iniettare. Semplicemente perché avrà bisogno di dipendenza dal repository, quindi deve essere iniettato. Lo stesso caso si applica per la fase operativa 1 scan the employee id, poiché richiede la dipendenza dallo scanner di impronte digitali, pertanto il gestore dello scanner deve essere implementato in un modulo separato.

possiamo sempre ripartizione l'operazione, come possiamo farlo in 2.1:

// 2.1 check if employee id matches the format policy 
// 2.1.1 employee id must match the length 
// 2.1.2 employee id must has format emp##### 

Ora sto incerto se 2.1.1 e 2.1.2 necessità di essere suddiviso in 2 moduli separati, spetta a voi decidere. E ora abbiamo le interfacce, quindi possiamo iniziare l'implementazione. Aspettatevi di lanciare exceptions durante la convalida o dovrete passare la classe personalizzata per gestire i messaggi di errore.

+0

L'esempio DI è l'utilizzo errato. La dipendenza dovrebbe essere INIETTATA come argomento del metodo o tramite il costruttore di oggetti. Solo in alcuni casi quando si costruisce un framework o qualcosa di simile è opportuno utilizzare direttamente il resolver. – MikeSW

+2

Assolutamente. È per questo che ho menzionato nell'esempio che "Si prega di notare che l'esempio di consumatore di seguito non implementa alcun DI perché voglio solo mostrare come consumarlo" – Fendy

28

Prima di tutto, solido non è un principio, si distingue per 5 differenti principi:

  • SRP (Responsabilità unico principio): la classe deve avere un solo responsabilità ben definita;
  • OCP (Open-Closed Principle): la classe dovrebbe essere aperto per l'estensione, ma chiuso per la modifica;
  • LSP (Principio di sostituzione di Liskov): questo guida l'utente a decidere se utilizzare o meno una relazione di ereditarietà tra classe A e B. L'ereditarietà è adatta quando tutti gli oggetti di una classe derivata B possono essere sostituiti da oggetti della loro classe genitore A senza alcuna perdita di funzionalità;
  • ISP (Interface segregazione Principio): afferma che nessun cliente dovrebbe essere costretto a dipendere da metodi non utilizza;
  • DIP (Dependency Injection/Inversion): afferma che i moduli di alto livello non dovrebbe dipendere moduli di livello basso.

Questi principi sono guide, ma non significa che devi usarli rigorosamente ogni volta.

Dalla tua descrizione, posso vedere la vostra difficoltà principale è quello di pensare OO. Stai ancora pensando a come fare le cose e questa è una mentalità procedurale . Ma in OOP è più importante decidere chi farà queste cose.

Pensando a DI, utilizzando il vostro esempio, vediamo lo scenario:

public class AttendanceService { 
    // other stuff... 

    public boolean scanEmployeeId() { 
     // The scanning is made by an barcode reader on employee's name tag 
    } 
} 

Qual è il problema?

Beh, prima di tutto, questo codice viola SRP: Che cosa succede se le modifiche di processo di autenticazione? Se la società ha deciso che le etichette dei nomi non sono sicure e installano un sistema di riconoscimento biometrico? Bene, qui c'è un motivo per cui la tua classe deve cambiare, ma questa classe non fa solo l'autenticazione, fa altre cose, quindi, ci saranno altre ragioni per cambiare. SRP afferma che le tue classi dovrebbero avere solo un motivo per cambiare.

Viola anche OCP: Che cosa succede se c'è un altro metodo di autenticazione avaiable e voglio essere in grado di utilizzare come voglio? Non posso Per cambiare il metodo auth, devo modificare la classe.

Viola ISP: Perché un oggetto ServiceAttendance ha un metodo per l'autenticazione dei dipendenti, se si deve solo fornire servizio assistenza?


Diamo migliorare un po ':

public class BarCodeAuth { 
    public boolean authenticate() { 
     // Authenticates... 
    } 
} 

public class AttendanceService { 
    private BarCodeAuth auth; 
    public AttendanceClass() { 
     this.auth = new BarCodeAuth(); 
    } 

    public void doOperation() { 
     if(this.auth.authenticate()) { 
      // do stuff.. 
     } 
    } 
} 

Ora che è un po' meglio. Abbiamo risolto i problemi con SRP e ISP, ma se si pensa meglio, viola ancora OCP e ora vìola DIP. Il problema è che AttendanceService è strettamente accoppiato con BarCodeAuth. Non riesco ancora a modificare il metodo di autenticazione senza toccare AttendanceService.

Ora applichiamo OCP e DIP insieme:

public interface AuthMethod { 
    public boolean authenticate(); 
} 

public class BarCodeAuth implements AuthMethod { 
    public boolean authenticate() { 
     // Authenticates... 
    } 
} 

public class BiometricAuth implements AuthMethod { 
    public boolean authenticate() { 
     // Authenticates... 
    } 
} 

public class FooBarAuth implements AuthMethod { 
    public boolean authenticate() { 
     // Authenticates... 
    } 
} 

public class AttendanceClass { 
    private AuthMethod auth; 
    public AttendanceClass(AuthMethod auth) { 
     this.auth = auth; 
    } 

    public void doOperation() { 
     if(this.auth.authenticate()) { 
      // do stuff.. 
     } 
    } 
} 

Ora posso fare:

new AttendanceClass(new BarCordeAuth()); 
new AttendanceClass(new BiometricAuth()); 

per modificare il comportamento, non ho bisogno di toccare la classe. Se viene visualizzato un altro metodo di autenticazione, devo solo implementarlo, rispettando l'interfaccia ed è pronto per l'uso (ricorda OCP?). Ciò è dovuto al fatto che sto utilizzando DIP su ServiceAttendance. Sebbene abbia bisogno di un metodo di autenticazione, non è la sua responsabilità di crearne uno. In effetti, per questo oggetto, non importa il metodo di autenticazione, ma solo sapere se il chiamante (utente) è o non è autorizzato a fare ciò che sta cercando di fare.

Questo è tutto su DIP è: i componenti devono dipendere da astrazioni, non da implementazioni.

+1

Mi piace come hai iniziato con 'Questi principi sono guide, ma lo fa non significa che devi usarli STRETTAMENTE ogni volta. Sono completamente d'accordo, non possiamo sempre implementarlo tutto il tempo. A volte dobbiamo scendere a compromessi e fare ciò che deve essere fatto per raggiungere un requisito. Mi piace anche tenerlo semplice (KIS) e non voglio essere troppo perfetto e finire con una classe che stampa 'Hello' e un'altra classe che stampa' World', è solo un'esagerazione, ma succede. – JohnnyQ

3

Prima di tutto, pensa a diverse parti del sistema di presenze. Interfaccia utente, scanner di impronte digitali, repository di database, processo di login e flusso di lavoro. Per progettare questo sistema possiamo iniziare a progettare le parti isolate e collegarle come un sistema.

Un progetto di massima potrebbe essere di circa seguendo le parti del sistema:

  • scanner di impronte digitali e Listener
  • presenze Servizio
  • Repository dei dipendenti
  • Accesso Repository
  • User Interface
  • presenze Controller del flusso di lavoro
  • impronte digitali Firma

Nel seguente listato di codice alcuni aspetti di principi di progettazione sarà visibile già:

  • SRP - Un soggetto è responsabile di un lavoro
  • LoD - Legge di Demetra - Solo parlare ai tuoi amici più stretti Noterai che Controller non sa nulla dei repository.
  • DBC (Design by Contract) - Lavoro contro interfacce
  • Usa iniezione di dipendenza e il CIO - iniezione costruttore e il metodo iniezioni
  • ISP (Interface segregazione Principio) - Le interfacce sono magra
  • OCP - sostituire i metodi di interfaccia in derivati classi o passare diverse implementazioni come interfacce iniettate possono estendere il comportamento senza la necessità di modificare la classe.

Sulla base di questo molto pensiero, il sistema potrebbe funzionare in questo modo:

[Si può ulteriormente migliorare su di esso e aggiungere la logica manca, sto fornendo un contorno design molto veloce con brevi implementazione.]

codice

interface IAttedanceController 
{ 
    run(); 
} 

interface IFingerprintHandler 
{ 
    void processFingerprint(IFingerprintSignature fingerprintSignature); 
} 

interface IFingerprintScanner 
{ 
    void run(IFingerprintHandler fingerprintHandler); 
} 

interface IAttendanceService 
{ 
    void startService(); 
    void stopService(); 
    bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature); 
    string getFailureMessage(); 
} 

interface ILoginRepository 
{ 
    bool loginEmployee(IEmployee employee, DateTime timestamp); 
    void open(); 
    void close(); 
} 

interface IEmployeeRepository 
{ 
    IEmployee findEmployee(IFingerprintSignature fingerprintSignature); 
    void open(); 
    void close(); 
} 

//----------------------------------------- 

class AttendanceService : IAttendanceService 
{ 
    private IEmployeeRepository _employeeRepository; 
    private ILoginRepository _loginRepository; 
    private string _failureMessage; 

    public class AttendanceService(
     IEmployeeRepository employeeRepository, 
     ILoginRepository loginRepository) 
    { 
     this._employeeRepository = employeeRepository; 
     this._loginRepository = loginRepository; 
    } 

    public bool attempEmployeeLogin(IFingerprintSignature fingerprintSignature) 
    { 
     IEmployee employee = this._employeeRepository.findEmployee(fingerprintSignature); 

     if(employee != null) 
     { 
      //check for already logged in to avoid duplicate logins.. 

      this._loginRepository.loginEmployee(employee, DateTime.Now); 
      //or create a login record with timestamp and insert into login repository 

      return true; 
     } 
     else 
     { 
      this._failureMessage = "employee not found"; 
      return false; 
     } 
    } 

    public string getFailureMessage() 
    { 
     return "reason for failure"; 
    } 

    public void startService() 
    { 
     this._employeeRepository.open(); 
     this._loginRepository.open(); 
    } 

    public void stopService() 
    { 
     this._employeeRepository.close(); 
     this._loginRepository.close(); 
    } 
} 

//----------------------------------------- 

class AttendanceController : IAttedanceController, IFingerprintHandler 
{ 
    private ILoginView _loginView; 
    private IAttendanceService _attedanceService; 
    private IFingerprintScanner _fingerprintScanner; 

    public AttendanceController(
     ILoginView loginView, 
     IAttendanceService attendanceService, 
     IFingerprintScanner fingerprintScanner) 
    { 
     this._loginView = loginView; 
     this._attedanceService = attedanceService; 
     this._fingerprintScanner = fingerprintScanner; 
    } 

    public void run() 
    { 
     this._attedanceService.startService(); 
     this._fingerprintScanner.run(this); 
     this._loginView.show(); 
    } 

    public void IFingerprintHandler.processFingerprint(IFingerprintSignature fingerprintSignature) 
    { 
     if(this._attedanceService.login(fingerprintSignature)) 
     { 
     this._loginView.showMessage("Login successful"); 
     } 
     else 
     { 
     string errorMessage = string getFailureMessage(); 
     this._loginView.showMessage("errorMessage"); 
     } 

     // on return the fingerprint monitor is ready to take another finter print 
    } 
} 

//----------------------------------------- 

App.init() 
{ 
    // Run app bootstrap 
    // Initialize abstract factories or DI containers 

    IAttedanceController attedanceController = DIContainer.resolve("AttedanceController"); 
    attedanceController.run(); 
} 

//----------------------------------------- 
2

Certo, programmazione procedurale è molto più facile per le persone che vengono utilizzati per la scrittura di codice procedurale. Per quelli che sono abituati a scrivere bene il codice orientato agli oggetti fattorizzato, il codice procedurale è in realtà più difficile.

Sì, il codice orientato agli oggetti ben calcolato genera spesso più lavoro e più codice effettivo. Ma se fatto correttamente, rende il codice più facile da mantenere, più facile da estendere, più facile da eseguire il debug (e, soprattutto, più facile da testare).

7

Non specificamente su SOLID, ma degno di menzione come un interessante OOP- training di Jeff Bay: Object Oriented Calisthenics. L'idea è che puoi provare a seguire una serie di regole molto rigide su un piccolo progetto non reale.

The Rules 

1. One level of indentation per method 
2. Don’t use the ELSE keyword 
3. Wrap all primitives and Strings 
4. First class collections 
5. One dot per line 
6. Don’t abbreviate 
7. Keep all entities small 
8. No classes with more than two instance variables 
9. No getters/setters/properties 

Sospendendo l'incredulità, e l'applicazione di queste regole rigidamente su un piccolo, progetto di linea 1000, potrai iniziare a vedere un significativamente diverso approccio alla progettazione del software. Una volta che hai scritto 1000 righe di codice , l'esercizio è terminato e puoi rilassarti e tornare a utilizzare queste 9 regole come linee guida utilizzando .

Questo è un esercizio difficile, in particolare perché molte di queste regole non sono universalmente applicabili. Il fatto è, a volte le classi sono un po 'più di 50 linee. Ma c'è il grande valore nel pensare a cosa dovrebbe succedere per spostare le responsabilità dello in oggetti reali di prima classe. È sviluppare questo tipo di pensiero che è il vero valore dell'esercizio . Quindi allunga i limiti di ciò che immagini è possibile, e vedi se inizi a pensare al tuo codice in un modo nuovo.

+1

+1 per l'introduzione alla Calisthenica orientata agli oggetti. Grazie! –