Interfaccia Segregation Principle: programma su un’interfaccia

Stavo leggendo su SOLID e altri principi di progettazione. Pensavo che l’ISP fosse lo stesso di “Programma per un’interfaccia, non un’implementazione”. Ma sembra che questi siano principi diversi?

C’è una differenza?

L’ISP è focalizzato sull’idea di ciascuna interfaccia che rappresenta un comportamento discreto e coeso.

Cioè, ogni gruppo logico di cose che un object dovrebbe fare si assocerebbe ad una singola interfaccia specifica. Una class potrebbe voler fare diverse cose, ma ogni cosa dovrebbe mappare a un’interfaccia specifica che rappresenta quel comportamento. L’idea è che ogni interfaccia è molto focalizzata.

Robert Martin ha una spiegazione molto buona del principio di segregazione dell’interfaccia (ISP), nel suo libro “UML for Java Programmers”. Sulla base di ciò, non penso che l’ISP riguardi l’interfaccia “focalizzata” su un gruppo logico e coerente di cose. Perché, è ovvio; o, almeno dovrebbe essere ovvio. Ogni class, interfaccia o class astratta dovrebbe essere progettata in questo modo.

Allora, qual è l’ISP? Lascia che ti spieghi con un esempio. Di ‘, hai una class A e una class B, che è il client della class A. Supponiamo che la class A abbia dieci metodi, di cui solo due sono usati da B. Ora, B ha bisogno di conoscere tutti e dieci i metodi di A ? Probabilmente no – il principio di hide le informazioni. Più esponi, più crei la possibilità di accoppiare. Per questo motivo, puoi inserire un’interfaccia, chiamala C, tra le due classi (segregazione). Quell’interfaccia dichiarerà solo i due metodi che sono usati da B, e B dipenderà da quell’interfaccia, invece che direttamente su A.

Così ora,

class A { method1() method2() // more methods method10() } class B { A a = new A() } 

diventerà

 interface C { method1() method2() } class A implements C{ method1() method2() // more methods method10() } class B { C c = new A() } 

Questo impedisce a B di sapere più del dovuto.

Supponiamo di avere un’interfaccia grossa con molti metodi da implementare.

Qualsiasi class che implementa quell’interfaccia fat deve fornire l’implementazione per tutti questi metodi. Alcuni dei metodi potrebbero non essere applicabili a quella class concreta. Ma deve ancora fornire implementazione in assenza del principio di segregazione dell’interfaccia.

Diamo un’occhiata al codice di esempio in assenza di segregazione dell’interfaccia .

 interface Shape{ public int getLength(); public int getWidth(); public int getRadius(); public double getArea(); } class Rectangle implements Shape{ int length; int width; public Rectangle(int length, int width){ this.length = length; this.width = width; } public int getLength(){ return length; } public int getWidth(){ return width; } public int getRadius(){ // Not applicable return 0; } public double getArea(){ return width * length; } } class Square implements Shape{ int length; public Square(int length){ this.length = length; } public int getLength(){ return length; } public int getWidth(){ // Not applicable return 0; } public int getRadius(){ // Not applicable return 0; } public double getArea(){ return length * length; } } class Circle implements Shape{ int radius; public Circle(int radius){ this.radius = radius; } public int getLength(){ // Not applicable return 0; } public int getWidth(){ // Not applicable return 0; } public int getRadius(){ return radius; } public double getArea(){ return 3.14* radius * radius; } } public class InterfaceNoSeggration{ public static void main(String args[]){ Rectangle r = new Rectangle(10,20); Square s = new Square(15); Circle c = new Circle(2); System.out.println("Rectangle area:"+r.getArea()); System.out.println("Square area:"+s.getArea()); System.out.println("Circle area:"+c.getArea()); } } 

produzione:

 java InterfaceNoSeggration Rectangle area:200.0 Square area:225.0 Circle area:12.56 

Gli appunti:

  1. Shape è un’interfaccia fat general purpose, che contiene i metodi richiesti per tutte le implementazioni Shape come Rectangle , Circle e Square . Ma solo alcuni metodi sono necessari nei rispettivi child di Shape

      Rectangle : getLength(), getWidth(), getArea() Square : getLength() and getArea() Circle : getRadius() and getArea() 
  2. In assenza di segregazione, tutte le forms hanno implementato un’intera interfaccia fat: Shape.

Possiamo ottenere lo stesso risultato con il principio di segregazione dell’interfaccia se cambiamo il codice come segue.

 interface Length{ public int getLength(); } interface Width{ public int getWidth(); } interface Radius{ public int getRadius(); } interface Area { public double getArea(); } class Rectangle implements Length,Width,Area{ int length; int width; public Rectangle(int length, int width){ this.length = length; this.width = width; } public int getLength(){ return length; } public int getWidth(){ return width; } public int getRadius(){ // Not applicable return 0; } public double getArea(){ return width * length; } } class Square implements Length,Area{ int length; public Square(int length){ this.length = length; } public int getLength(){ return length; } public int getWidth(){ // Not applicable return 0; } public int getRadius(){ // Not applicable return 0; } public double getArea(){ return length * length; } } class Circle implements Radius,Area{ int radius; public Circle(int radius){ this.radius = radius; } public int getLength(){ // Not applicable return 0; } public int getWidth(){ // Not applicable return 0; } public int getRadius(){ return radius; } public double getArea(){ return 3.14* radius * radius; } } public class InterfaceSeggration{ public static void main(String args[]){ Rectangle r = new Rectangle(10,20); Square s = new Square(15); Circle c = new Circle(2); System.out.println("Rectangle area:"+r.getArea()); System.out.println("Square area:"+s.getArea()); System.out.println("Circle area:"+c.getArea()); } } 

Gli appunti:

Ora le singole forms come Rectangle , Square e Circle hanno implementato solo le interfacce richieste e si sono liberati dei metodi non utilizzati.

D’accordo con entrambe le risposte sopra. Solo per dare un esempio dell’odore di codice di TrueWill sopra, non dovresti trovarti a fare questo:

 @Override public void foo() { //Not used: just needed to implement interface } 

Ecco un esempio reale di questo principio (in PHP)

Dichiarazione problema:

Voglio che varie forms di contenuto abbiano commenti / discussioni associati a loro. Quel contenuto potrebbe essere qualsiasi cosa, da un argomento del forum, a un articolo di notizie, al profilo di un utente, a un messaggio privato in stile conversazione.

Architettura

Vogliamo una class DiscussionManager riutilizzabile che allega una Discussion a una determinata quadro di contenuto. Tuttavia, i quattro esempi precedenti (e molti altri) sono tutti concettualmente diversi. Se vogliamo che DiscussionManager li usi, tutti e quattro + devono avere un’interfaccia comune condivisa da tutti. DiscussionManager può essere utilizzato in altro modo, a meno che non si desideri che i propri argomenti diventino nudi (ad esempio, nessun tipo di controllo).

Soluzione: interfaccia Discussable con questi metodi:

  • attachDiscussion($topic_id)
  • detachDiscussion()
  • getDiscussionID()

Quindi DiscussionManager potrebbe assomigliare a questo:

 class DiscussionManager { public function addDiscussionToContent(Discussable $Content) { $Discussion = $this->DiscussionFactory->make( ...some data...); $Discussion->save() // Or $this->DiscussionRepository->save($Discussion); $Content->attachDiscussion($Discussion->getID()); // Maybe saves itself, or you can save through a repository } public function deleteDiscussion(Discussable $Content) { $id = $Content->getDiscussionID(); $Content->detatchDiscussion(); $this->DiscussionRepository->delete($id); } public function closeDiscussion($discussion_id) { ... } } 

In questo modo, DiscussionManager non si preoccupa di nessuno dei comportamenti non correlati dei vari tipi di contenuti che utilizza. SOLO si preoccupa dei comportamenti di cui ha bisogno, indipendentemente da quali siano i comportamenti associati. Quindi, assegnando a ciascun tipo di contenuto per il quale desideri discutere, un’interfaccia Discussable , stai utilizzando il principio di segregazione dell’interfaccia.

Questo è anche un buon esempio di una situazione in cui una class base astratta non è una buona idea. Un argomento del forum, un profilo utente e un articolo di notizie non sono nemmeno lontanamente concettualmente la stessa cosa, quindi cercando di convincerli ad ereditare i comportamenti di discussione porta a strani accoppiamenti con un genitore non correlato. Utilizzando un’interfaccia specifica che rappresenta le discussioni, è ansible assicurarsi che le quadro che si desidera avere discussioni siano compatibili con il codice client che gestirà tali discussioni.

Questo esempio potrebbe anche essere un buon candidato per l’utilizzo di Tratti in PHP, per quello che vale.

  1. Interfaccia IWorker:

     public interface IWorker { public void work(); public void eat(); } 
  2. Classe di sviluppatori:

     public class Developer implements IWorker { @Override public void work() { // TODO Auto-generated method stub System.out.println("Developer working"); } @Override public void eat() { // TODO Auto-generated method stub System.out.println("developer eating"); } } 
  3. Classe robot:

     public class Robot implements IWorker { @Override public void work() { // TODO Auto-generated method stub System.out.println("robot is working"); } @Override public void eat() { // TODO Auto-generated method stub throw new UnsupportedOperationException("cannot eat"); } } 

Per un esempio più completo vai qui .