2009-03-18 16 views
6

Ho fatto il seguente esempio di codice per imparare come usare una firma del metodo generico.Si tratta di un esempio del principio di responsabilità unica?

Al fine di ottenere un metodo Display() sia per clienti e dipendenti, in realtà ho iniziato la mia sostituzione IPerson interfaccia con una classe astratta Person.

Ma poi ho smesso, ricordando un podcast in cui lo zio Bob stava dicendo Scott Hanselman sulla Responsabilità unico principio in cui si dovrebbe avere un sacco di piccole classi ciascuna fare una cosa specifica, vale a dire che una classe cliente non dovrebbe avere un stampa() e Save() e CalculateSalary() metodo, ma che si dovrebbe avere un classe CustomerPrinter e una classe CustomerSaver e Classe CustomerSalaryCalculator.

Sembra un modo strano di programmare. Tuttavia, lo ha eliminato la mia interfaccia anche in modo errato (dal momento che molti contenitori IoC e gli esempi DI li usano intrinsecamente), quindi ho deciso di provare il Principio della singola responsabilità.

Così il seguente codice è diverso da quello che ho programmato in passato (avrei fatto una classe astratta con un metodo display(), ma ho sbarazzarsi dell'interfaccia), ma sulla base di quello che ho sentito parlare di disaccoppiamento e il SOLIDO principi, questo nuovo modo di codificare (l'interfaccia e la classe PersonDisplayer) Penso che questo sia il modo giusto di andare.

Vorrei piacerebbe sentire se altri pensano allo stesso modo su su questo problema o hanno avuto effetti positivi o negativi di questo (ad esempio un numero ingombrante di classi ciascuno facendo una cosa particolare, ecc.).

using System; 

namespace TestGeneric33 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Container container = new Container(); 
      Customer customer1 = container.InstantiateType<Customer>("Jim", "Smith"); 
      Employee employee1 = container.InstantiateType<Employee>("Joe", "Thompson"); 
      Console.WriteLine(PersonDisplayer.SimpleDisplay(customer1)); 
      Console.WriteLine(PersonDisplayer.SimpleDisplay(employee1)); 
      Console.ReadLine(); 
     } 
    } 

    public class Container 
    { 
     public T InstantiateType<T>(string firstName, string lastName) where T : IPerson, new() 
     { 
      T obj = new T(); 
      obj.FirstName = firstName; 
      obj.LastName = lastName; 
      return obj; 
     } 
    } 

    public interface IPerson 
    { 
     string FirstName { get; set; } 
     string LastName { get; set; } 
    } 

    public class PersonDisplayer 
    { 
     private IPerson _person; 

     public PersonDisplayer(IPerson person) 
     { 
      _person = person; 
     } 

     public string SimpleDisplay() 
     { 
      return String.Format("{1}, {0}", _person.FirstName, _person.LastName); 
     } 

     public static string SimpleDisplay(IPerson person) 
     { 
      PersonDisplayer personDisplayer = new PersonDisplayer(person); 
      return personDisplayer.SimpleDisplay(); 
     } 
    } 

    public class Customer : IPerson 
    { 
     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public string Company { get; set; } 
    } 

    public class Employee : IPerson 
    { 
     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public int EmployeeNumber { get; set; } 
    } 
} 
+0

Domanda simile http://stackoverflow.com/questions/1215442/confused-about-single-responsibility-principle-in-the-following-example –

risposta

8

Mi piace pensare allo Single Responsibility Principle come un'implementazione di separation of duties. Prima di iniziare a suddividere le mie lezioni, cerco di pensare a cosa dovrebbe essere responsabile ogni classe.

Le classi sono abbastanza semplici e si prestano bene a una classe astratta con le funzioni implementate e Save() come già menzionato. Tenderei a mantenere quel disegno su quello attuale.

Tuttavia, se la stampa e il salvataggio fossero attività più complicate che potrebbero essere eseguite in modi diversi, sarebbe giustificata una classe dedicata Printer o Saver, poiché tale responsabilità è ora più complessa. La soglia della "complessità" per creare una nuova classe è molto soggettiva e dipenderà dalla situazione esatta, ma alla fine il codice è solo un'astrazione per noi umani da comprendere, quindi rendi tale che sia il più intuitivo.

La classe Container è un po 'fuorviante. In realtà non "contiene" nulla. In realtà implementa il Factory Method Pattern e trarrebbe vantaggio dal fatto di essere nominato fabbrica.

Inoltre, il tuo PersonDisplayer non viene mai istanziato e può fornire tutte le sue funzionalità tramite metodi statici, quindi perché non renderlo una classe statica? Non è insolito per utility classes come stampanti o risparmiatori essere statici. A meno che non sia necessario disporre di istanze separate di una stampante con proprietà diverse, mantenerle statiche.

+0

PersonDisplayer è istatato nel suo metodo SimpleDisplay statico – user76035

+0

Destra, questo è un pattern che ho usato molto in PHP: una classe con metodi statici che istanzia la propria classe, quindi esegue un determinato metodo interno, fondamentalmente dando allo sviluppatore solo la possibilità scrivere one-liners invece di istanziare una classe, quindi chiamare un metodo su di essa. –

+3

Questa è la creazione di un oggetto non necessaria e comporta una penalizzazione delle prestazioni per nessuna funzionalità aggiunta. Il PersonDisplayer non ha motivo di essere istanziato, tutte le funzionalità possono essere fornite staticamente. –

1

Beh, non ho mai sentito parlare di questo 'principio di responsabilità singolo' prima, ma quello che sembra a me che quello che stai facendo per avere queste classi di classe CustomerPrinter e CustomerSaver viene semplicemente convertendo le classi di nuovo a struct e de-object-orientando tutto.

Ad esempio, ciò significherebbe che tipi di clienti diversi avrebbero bisogno di diversi casi nella classe CustomerPrinter se dovessero essere stampati in modo diverso. Ma, a quanto ho capito, uno dei punti chiave dell'organizzazione OO, e dell'utilizzo di alberi ereditari e tutto il resto, è quello di eliminare la necessità di questo CustomerPrinter di sapere come stampare tutto: i clienti sanno come stampare da soli.

Non credo di seguire rigidamente questi paradigmi in ogni caso. Ad esempio, non sono sicuro di quale sia la differenza tra un'interfaccia e una classe astratta. Ma poi di nuovo io sono un programmatore C++ non un programmatore C# ...

+0

con un'interfaccia Posso forzare le mie classi ad avere determinate firme di metodo, ma io non posso dare loro metodi con funzionalità, per questo ho bisogno di una classe astratta, vedo le interfacce solo come un "contratto leggero" che le mie classi devono essere passate in modo intelligente intorno all'applicazione –

4

Penso che tu sia sulla strada giusta. Comunque non sono completamente sicuro della classe Container. In genere, preferisco utilizzare la soluzione più semplice di usare "nuovo" per questi oggetti, a meno che non si abbia bisogno di un'interfaccia per le aziende.(Non ritengo che "pulito" sia un requisito aziendale in questo senso)

Ma la separazione tra "essere" una responsabilità del cliente da "visualizzazione di un cliente" è buona. Attenersi a ciò, è una buona interpretazione dei principi SOLIDI.

Personalmente ho ora completamente fermato utilizzato qualsiasi tipo di metodi statici in questo tipo di codice, e faccio affidamento su DI per ottenere tutti gli oggetti del servizio giusti al momento giusto posto &. Una volta che inizierai a elaborare ulteriormente i principi SOLID, scoprirai che stai facendo molte più lezioni. Cerca di lavorare su queste convenzioni di denominazione per rimanere coerente.

+0

interessante, sembra proprio che il mio "cliente" e le classi "dipendenti" saranno molto piccole, forse solo proprietà e un po 'costruttore, questi tipi di classi erano tali pietre miliari dell'applicazione –

+0

Sì, la loro responsabilità è quella di "essere" un dipendente. Potresti scoprire che aggiungerai anche altri metodi, non solo le proprietà. Non c'è motivo di essere religiosi al riguardo. – krosenvold

1

Alcune note:

  • generale SRP è tutto buono, come è la separazione di visualizzazione formattazione dai dati.
  • Considerando la visualizzazione ecc. Preferisco pensare in termini di servizi, ad esempio PersonDisplayer è single, senza stato e offre una funzione di visualizzazione di stringhe (IPerson). IMHO, un wrapper di classe speciale solo per fornire display non offre alcun vantaggio.
  • Tuttavia, se si è utilizzato l'associazione dati per wpf, è possibile che una classe DisplayablePerson propaghi PropertyChanged se Persona è stata modificata. Metterei gli oggetti DisplayablePerson in ObservableCollection e servirlo come ItemSource di alcuni controlli di lista.
  • Per cosa è necessario Container, è solo per la creazione di istanze e la configurazione dell'istanza? Prova quindi Cliente cliente1 = nuovo Cliente {FirstName = "Jim", LastName = "Smith"};

  • Su una nota a margine, ho provato object.Method < SomeType> (...) invocazione un paio di volte, in quanto sembrava la soluzione più rapida e più semplice. Tuttavia, dopo un po 'di tempo mi sono sempre imbattuto in problemi con quello e ho finito con object.Method (Digitare someTypeType, ...)

Problemi correlati