Gilt das Prinzip der Schnittstellentrennung für konkrete Methoden?

10

Da das Prinzip der Schnittstellentrennung vorschlägt, sollte kein Client gezwungen werden, sich auf Methoden zu verlassen, die er nicht verwendet. Daher sollte ein Client keine leere Methode für seine Schnittstellenmethoden implementieren, andernfalls sollte diese Schnittstellenmethode in eine andere Schnittstelle eingefügt werden.

Aber wie wäre es mit konkreten Methoden? Sollte ich die Methoden trennen, die nicht jeder Client verwenden würde? Betrachten Sie die folgende Klasse:

public class Car{
    ....

    public boolean isQualityPass(){
        ...
    }

    public int getTax(){
        ...
    }

    public int getCost(){
        ...
    }
}

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Im obigen Code verwendet CarShop die Methode isQualityPass () in Car überhaupt nicht, sollte ich isQualityPass () in eine neue Klasse aufteilen:

public class CheckCarQualityPass{
    public boolean isQualityPass(Car car){
    }
}

um die Kopplung von CarShop zu reduzieren? Weil ich einmal überlege, ob isQualityPass () zusätzliche Abhängigkeit benötigt, z.

public boolean isQualityPass(){
    HttpClient client=...
}

CarShop würde von HttpClient abhängen, auch wenn es HttpClient eigentlich nie verwendet. Meine Frage lautet also: Sollte ich nach dem Prinzip der Schnittstellentrennung konkrete Methoden trennen, die nicht jeder Client verwenden würde, damit diese Methoden nur dann vom Client abhängen, wenn der Client sie tatsächlich verwendet, um die Kopplung zu verringern?

ggrr
quelle
2
Weiß normalerweise ein Auto, wann es "Qualität" besteht? Oder ist es vielleicht eine Geschäftsregel, die von sich aus gekapselt werden könnte?
Laiv
2
Wie das Wort Schnittstelle in ISP impliziert, handelt es sich um Schnittstellen . Wenn Sie also eine Methode in Ihrer CarKlasse haben, über die nicht alle Benutzer Bescheid wissen sollen, erstellen Sie (mehr als eine) Schnittstelle , die von der CarKlasse implementiert wird und die nur Methoden deklariert, die im Schnittstellenkontext nützlich sind.
Timothy Truckle
@Laiv Ich bin sicher, wir werden bald Fahrzeuge sehen, die viel mehr als das wissen. ;)
Unified Modeling Sandwich
1
Ein Auto wird wissen, was sein Hersteller von ihm erwartet. Volkswagen weiß wovon ich spreche :-)
Laiv
1
Sie erwähnen eine Schnittstelle, aber in Ihrem Beispiel gibt es keine Schnittstelle. Sprechen wir darüber, Auto in eine Schnittstelle zu verwandeln und welche Methoden in diese Schnittstelle aufzunehmen sind?
Neil

Antworten:

6

In Ihrem Beispiel CarShophängt dies nicht davon ab isQualityPassund es ist nicht gezwungen, eine leere Implementierung für eine Methode vorzunehmen. Es ist nicht einmal eine Schnittstelle beteiligt. Der Begriff "ISP" passt hier also einfach nicht zusammen. Und solange eine Methode wie diese isQualityPasseine Methode ist, die gut in das CarObjekt passt , ohne es mit zusätzlichen Verantwortlichkeiten oder Abhängigkeiten zu überlasten, ist dies in Ordnung. Es ist nicht erforderlich, eine öffentliche Methode einer Klasse an einen anderen Ort umzugestalten, nur weil ein Client vorhanden ist, der die Methode nicht verwendet.

Es ist jedoch wahrscheinlich auch keine gute Idee , eine Domain-Klasse wie Cardirekt von so etwas abhängig zu machen HttpClient, unabhängig davon, welche Clients die Methode verwenden oder nicht. Das Verschieben der Logik in eine separate Klasse CheckCarQualityPasswird einfach nicht als "ISP" bezeichnet, sondern als "Trennung von Bedenken" . Das Anliegen eines wiederverwendbaren Autoobjekts sollte wahrscheinlich nicht darin bestehen, externe HTTP-Aufrufe zu tätigen, zumindest nicht direkt. Dies schränkt die Wiederverwendbarkeit und darüber hinaus die Testbarkeit zu stark ein.

Wenn isQualityPassdies nicht einfach in eine andere Klasse verschoben werden kann, besteht die Alternative darin, die HttpAufrufe über eine abstrakte Schnittstelle zu tätigen, IHttpClientin die Carzur Konstruktionszeit eingefügt wird, oder indem die gesamte Überprüfungsstrategie "QualityPass" (mit eingekapselter HTTP-Anforderung) in das CarObjekt eingefügt wird . Dies ist meiner Meinung nach jedoch nur die zweitbeste Lösung, da sie die Gesamtkomplexität erhöht, anstatt sie zu reduzieren.

Doc Brown
quelle
Was ist mit dem Strategiemuster zum Auflösen der isQualityPass-Methode?
Laiv
@Laiv: Technisch wird dies sicher funktionieren (siehe meine Bearbeitung), aber es wird zu einem komplexeren CarObjekt führen. Es wäre nicht meine erste Wahl für eine Lösung (zumindest nicht im Zusammenhang mit diesem erfundenen Beispiel). Allerdings kann es im "echten" Code sinnvoller sein, ich weiß es nicht.
Doc Brown
6

Meine Frage lautet also: Sollte ich nach dem Prinzip der Schnittstellentrennung konkrete Methoden trennen, die nicht jeder Client verwenden würde, damit diese Methoden nur dann vom Client abhängen, wenn der Client sie tatsächlich verwendet, um die Kopplung zu verringern?

Beim Prinzip der Schnittstellentrennung geht es nicht darum, den Zugriff auf das zu verbieten, was Sie nicht benötigen. Es geht darum, nicht darauf zu bestehen, auf das zuzugreifen, was Sie nicht benötigen.

Schnittstellen gehören nicht der Klasse, die sie implementiert. Sie gehören den Objekten, die sie verwenden.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        Car car=carList[carId];
        int price=car.getTax() + car.getCost()... (some formula);
        return price;
    }
}

Was hier verwendet wird, ist getTax()und getCost(). Worauf bestanden wird, ist alles zugänglich durch Car. Das Problem besteht darauf Car, dass der Zugriff darauf besteht, auf isQualityPass()den nicht zugegriffen werden muss.

Dies kann behoben werden. Sie fragen, ob es konkret behoben werden kann. Es kann.

public class CarShop{
    ...
    public int getCarPrice(int carId){
        CarLiability carLiability=carLiabilityList[carId];
        int price=carLiability.getTax() + carLiability.getCost()... (some formula);
        return price;
    }
}

Keiner dieser Codes weiß, ob CarLiabilityes sich um eine Schnittstelle oder eine konkrete Klasse handelt. Das ist gut. Es will es nicht wissen.

Wenn es sich um eine Schnittstelle handelt, wird diese Carmöglicherweise implementiert. Dies würde ISP nicht verletzen , denn obwohl isQuality()in ist Car CarShopnicht darauf bestehen. Das ist in Ordnung.

Wenn es konkret ist, kann es sein, dass es isQuality()entweder nicht existiert oder an einen anderen Ort verlegt wurde. Das ist in Ordnung.

Es kann auch sein, dass CarLiabilityes sich um eine konkrete Hülle Carhandelt, die die Arbeit an sie delegiert. Solange CarLiabilitynicht aussetzt , isQuality()dann CarShopist in Ordnung. Natürlich wirft dies nur die Dose die Straße runter und CarLiabilitymuss herausfinden, wie man dem ISP auf Cardie gleiche Weise folgt, wie CarShopes zu tun war.

Kurz gesagt, isQuality()muss nicht Carwegen ISP entfernt werden. Das implizite isQuality()Bedürfnis nach muss entfernt werden, CarShopweil CarShopes nicht benötigt wird, also sollte es nicht danach fragen.

candied_orange
quelle
4

Gilt das Prinzip der Schnittstellentrennung für konkrete Methoden?

Aber wie wäre es mit konkreten Methoden? Sollte ich die Methoden trennen, die nicht jeder Client verwenden würde?

Nicht wirklich. Es gibt verschiedene Möglichkeiten , sich zu verstecken Car.isQualityPassaus CarShop.

1. Zugriff auf Modifikatoren

Aus Sicht des Demeter-Gesetzes könnten wir in Betracht ziehen Carund CardShopkeine Freunde sein . Es legitimiert uns, das nächste zu tun.

package com.my.package.domain.model
public class Car{
    ...
    protected boolean isQualityPass(){...}
}

package com.my.package.domain.services
public class CarShop{
    ...
}

Beachten Sie, dass sich beide Komponenten in unterschiedlichen Paketen befinden. Hat jetzt CarShopkeine Sichtbarkeit über Car geschützte Verhaltensweisen. (Entschuldigen Sie mich im Voraus, wenn das obige Beispiel so simpel aussieht).

2. Schnittstellentrennung

Der ISP geht davon aus, dass wir mit Abstraktionen arbeiten, nicht mit konkreten Klassen. Ich gehe davon aus, dass Sie bereits vertraut sind mit der ISP Implementierung und die Rolle Schnittstellen .

Trotz der tatsächlichen CarImplementierung hindert uns nichts daran, ISP zu praktizieren.

//role interfaces 
public interface Billable{
   public int getCosts();
   public int getTaxs();
}

//role interfaces
public interface QualityAssurance{
   public boolean isQualityPass();
}

public class Car implements Billable, QualityAssurance{
   ...
}

public class CarShop {
  ...
  public int getPrice(Billable billable){
     return billable.getCosts() * billable.getTaxs();
  }
}

Was ich hier gemacht habe. Ich habe die Interaktion zwischen Carund CarShopüber die Rollenschnittstelle Billable eingegrenzt . Beachten Sie die Änderung an der getPriceUnterschrift. Ich habe das Argument absichtlich geändert. Ich wollte offensichtlich machen , die CarShopnur „gebunden / gebunden“ an einen der Rolle Schnittstellen zur Verfügung. Ich hätte die tatsächliche Implementierung verfolgen können, aber ich kenne die tatsächlichen Implementierungsdetails nicht und befürchte, dass die tatsächliche Implementierung getPrice(String carId)Zugriff (Sichtbarkeit) auf die konkrete Klasse hat. Wenn dies der Fall ist, wird die gesamte Arbeit mit dem ISP nutzlos, da es in den Händen des Entwicklers liegt, Casting durchzuführen und nur mit der Schnittstelle Billable zu arbeiten . Egal wie methodisch wir sind, die Versuchung wird immer da sein.

3. Einzelverantwortung

Ich fürchte, ich bin nicht in der Lage zu sagen, ob die Abhängigkeit zwischen Carund HttpClientangemessen ist, aber ich stimme @DocBrown zu, es gibt einige Warnungen, die eine Entwurfsprüfung wert sind. Weder Demeters Gesetz noch ISP werden Ihr Design zu diesem Zeitpunkt "besser" machen. Sie werden das Problem nur maskieren, nicht beheben.

Ich habe DocBrown das Strategiemuster als mögliche Lösung vorgeschlagen. Ich stimmte ihm zu, dass das Muster die Komplexität erhöht, aber ich denke auch, dass jede Neugestaltung dies tun wird. Es ist ein Kompromiss, je mehr Entkopplung wir wollen, desto mehr bewegliche Teile haben wir (normalerweise). Wie auch immer, ich denke beide sind sich einig, dass eine Neugestaltung sehr ratsam ist.

Zusammenfassen

Nein, Sie müssen keine konkreten Methoden in äußere Klassen verschieben, um sie nicht zugänglich zu machen. Es könnte unzählige Verbraucher geben. Würden Sie jedes Mal, wenn ein neuer Verbraucher ins Spiel kommt , alle konkreten Methoden in äußere Klassen verschieben ? Ich hoffe du nicht.

Laiv
quelle