25 Stimmen

Sind Information Expert & Tell Don't Ask nicht im Widerspruch zum Single Responsibility Principle?

Information-Expert, Tell-Don't-Ask und SRP werden oft gemeinsam als bewährte Praktiken genannt. Aber ich denke, sie stehen im Widerspruch zueinander. Hier ist, worum es mir geht.

Code, der SRP bevorzugt, aber gegen Tell-Don't-Ask & Info-Expert verstößt:

Customer bob = ...;
// TransferObjectFactory muss die Accessoren von Customer verwenden, um ihre Arbeit zu erledigen, 
// verstößt gegen Tell Don't Ask
CustomerDTO dto = TransferObjectFactory.createFrom(bob); 

Code, der Tell-Don't-Ask & Info-Expert bevorzugt, aber gegen SRP verstößt:

Customer bob = ...;
// Jetzt macht der Kunde mehr als nur das Domain-Konzept des Kunden darzustellen,
// verstößt gegen SRP
CustomerDTO dto = bob.toDTO();

Bitte erläutern Sie mir, wie diese Praktiken friedlich nebeneinander existieren können.

Definitionen der Begriffe:

  • Information Expert: Objekte, die die für eine Operation benötigten Daten haben, sollten die Operation hosten.

  • Tell Don't Ask: Fordern Sie Objekte nicht nach Daten an, um Arbeit zu erledigen; sagen Sie den Objekten, die Arbeit zu erledigen.

  • Single Responsibility Principle: Jedes Objekt sollte eine klar definierte Verantwortung haben.

0 Stimmen

Eine kleine Vorstellung der Begriffe, die du verwendest, hätte hilfreich sein können.

3 Stimmen

Die kurze Antwort ist ja ... die lange Antwort ist, dass manchmal Leute (Akademiker) vergessen, dass Programmierung noch keine perfekte Wissenschaft ist ... wir haben keine Sprache, die sauber genug ist, um all diese Prinzipien zu erfüllen. Brechen Sie das Prinzip, das am sinnvollsten ist.

1 Stimmen

Dein zweites Beispiel verstößt zwar gegen das SRP, ist aber kein wirklich gutes Beispiel für Tell Don't Ask.

9voto

Hamish Smith Punkte 8011

Ich glaube nicht, dass sie so sehr gegensätzlich sind, wie sie verschiedene Dinge betonen, die dir Schmerzen bereiten werden. Ein Aspekt geht darum, den Code zu strukturieren, um klar zu machen, wo bestimmte Verantwortlichkeiten liegen und die Kopplung zu reduzieren, der andere bezieht sich darauf, die Gründe zu reduzieren, eine Klasse zu modifizieren.

Wir müssen alle täglich Entscheidungen darüber treffen, wie wir den Code strukturieren und welche Abhängigkeiten wir in Designs einführen wollen.

Wir haben viele nützliche Richtlinien, Maximen und Muster entwickelt, die uns helfen können, Entscheidungen zu treffen.

Jede davon ist nützlich, um verschiedene Arten von Problemen zu erkennen, die in unseren Designs auftreten könnten. Für jedes spezifische Problem, das du betrachtest, gibt es einen optimalen Punkt.

Die verschiedenen Richtlinien widersprechen sich. Es bringt nicht automatisch eine Verbesserung des Designs, wenn du einfach jeden Ratschlag anwendest, den du gehört oder gelesen hast.

Für das spezifische Problem, mit dem du heute konfrontiert bist, musst du entscheiden, welche Faktoren für dich am wichtigsten sind und wahrscheinlich Schmerzen verursachen werden.

5voto

Seva Parfenov Punkte 961

Sie können über "Tell Don't Ask" sprechen, wenn Sie den Zustand eines Objekts abfragen, um dem Objekt zu sagen, etwas zu tun.

In Ihrem ersten Beispiel TransferObjectFactory.createFrom ist nur ein Konverter. es sagt dem Customer-Objekt nicht, etwas zu tun, nachdem es den Zustand überprüft hat.

Ich denke, das erste Beispiel ist richtig.

2voto

Phil Bennett Punkte 4733

Diese Klassen stehen nicht im Widerspruch zueinander. Der DTO dient lediglich als Datenleitung von Speicher, die als einfacher Container verwendet werden soll. Es verstößt sicherlich nicht gegen das SRP.

Andererseits ist die Methode .toDTO fragwürdig - warum sollte der Kunde diese Verantwortung haben? Um der "Reinheit" willen würde ich eine andere Klasse haben, die für die Erstellung von DTOs aus Geschäftsobjekten wie dem Kunden zuständig ist.

Vergessen Sie nicht, dass es sich um Prinzipien handelt, und wenn Sie mit einfacheren Lösungen davonkommen, bis sich ändernde Anforderungen erzwingen, dann tun Sie es. Überflüssige Komplexität ist definitiv etwas, das vermieden werden sollte.

Übrigens empfehle ich Robert C. Martins Agile Patterns, Practices und Prinzipien für eine viel tiefgreifendere Behandlung dieses Themas.

1voto

Michael Parker Punkte 5289

DTOs mit einer Schwesterklasse (wie du sie hast) verletzen alle drei Prinzipien, die du genannt hast, und die Kapselung, deshalb hast du hier Probleme.

Wofür verwendest du diesen CustomerDTO, und warum kannst du nicht einfach Customer verwenden und die DTO-Daten im Kunden haben? Wenn du nicht aufpasst, wird der CustomerDTO einen Kunden benötigen, und ein Kunde wird einen CustomerDTO benötigen.

TellDontAsk besagt, dass, wenn du eine Entscheidung auf dem Zustand eines Objekts (z.B. eines Kunden) basierst, diese Entscheidung innerhalb der Kundenklasse selbst durchgeführt werden sollte.

Ein Beispiel ist, wenn du den Kunden daran erinnern möchtest, alle ausstehenden Rechnungen zu bezahlen, also rufst du

  List bills = Customer.GetOutstandingBills();
  PaymentReminder.RemindCustomer(customer, bills);

dies ist ein Verstoß. Stattdessen möchtest du

Customer.RemindAboutOutstandingBills() 

(und natürlich musst du PaymentReminder als Abhängigkeit bei der Konstruktion des Kunden übergeben).

Information Expert sagt im Grunde dasselbe.

Das Prinzip der Einzelverantwortung kann leicht missverstanden werden - es besagt, dass die Kundenklasse eine Verantwortung haben sollte, aber auch, dass die Verantwortung, Daten, Methoden und andere Klassen, die dem 'Kunden'-Konzept entsprechen, von nur einer Klasse umschlossen werden sollten. Was eine einzige Verantwortung ausmacht, ist äußerst schwer genau zu definieren, und ich würde empfehlen, mehr über das Thema zu lesen.

1voto

Matthew Flynn Punkte 2182

Craig Larman diskutierte dies, als er GRASP in "Applying UML and Patterns to Object-Oriented Analysis and Design and Iterative Development" (2004) einführte:

In einigen Situationen ist eine Lösung, die vom Experten vorgeschlagen wird, in der Regel aufgrund von Problemen mit Kopplung und Kohäsion unerwünscht (diese Prinzipien werden später in diesem Kapitel diskutiert).

Zum Beispiel, wer sollte für das Speichern einer Sale in einer Datenbank verantwortlich sein? Sicherlich befindet sich ein Großteil der zu speichernden Informationen im Sale-Objekt, und daher könnte der Experte argumentieren, dass die Verantwortung in der Sale-Klasse liegt. Und durch die logische Erweiterung dieser Entscheidung würde jede Klasse ihre eigenen Dienste haben, um sich selbst in einer Datenbank zu speichern. Aber wenn man dieser Argumentation folgt, führt dies zu Problemen bei Kohäsion, Kopplung und Duplizierung. Zum Beispiel muss die Sale-Klasse jetzt Logik enthalten, die sich auf die Datenbankverarbeitung bezieht, wie z.B. auf SQL und JDBC (Java Database Connectivity). Die Klasse konzentriert sich nicht mehr nur auf die reine Anwendungslogik des "Verkaufs sein". Nun verringern andere Arten von Verantwortlichkeiten ihre Kohäsion. Die Klasse muss an die technischen Datenbankservices eines anderen Subsystems gekoppelt sein, wie z.B. an JDBC-Services, anstatt nur an andere Objekte in der Domänenschicht von Softwareobjekten gekoppelt zu sein, so dass ihre Kopplung steigt. Und es ist wahrscheinlich, dass ähnliche Datenbanklogik in vielen persistenten Klassen dupliziert werden würde.

All diese Probleme deuten auf eine Verletzung eines grundlegenden architektonischen Prinzips hin: Entwurf für eine Trennung der wichtigen Systemanliegen. Halten Sie die Anwendungslogik an einem Ort (zum Beispiel in den Domänensoftwareobjekten), halten Sie die Datenbanklogik an einem anderen Ort (zum Beispiel in einem separaten Persistenzdienstesubsystem) usw., anstatt verschiedene Systemanliegen in derselben Komponente zu vermischen.[11]

Die Unterstützung einer Trennung der wichtigen Anliegen verbessert die Kopplung und Kohäsion in einem Entwurf. Daher würden wir trotz der Argumente des Experten einige Rechtfertigungen dafür finden können, die Verantwortung für die Datenbankservices in der Sale-Klasse zu platzieren. Aus anderen Gründen (meistens Kohäsion und Kopplung) würden wir jedoch mit einem schlechten Design enden.

Das SRP übertrumpft im Allgemeinen Information Expert.

Es kann jedoch gut funktionieren, wenn das Dependency Inversion Principle mit Expert kombiniert wird. Das Argument hier wäre, dass Customer keine Abhängigkeit von CustomerDTO haben sollte (allgemein zu Detail), sondern umgekehrt. Das würde bedeuten, dass CustomerDTO der Experte ist und wissen sollte, wie es sich selbst aufbaut, wenn ein Customer gegeben ist:

CustomerDTO dto = new CustomerDTO(bob);

Wenn Sie allergisch auf new reagieren, könnten Sie static verwenden:

CustomerDTO dto = CustomerDTO.buildFor(bob);

Oder wenn Sie beide hassen, kommen wir wieder auf eine AbstractFactory zurück:

public abstract class DTOFactory {
    public abstract D createDTO(E entity);
}

public class CustomerDTOFactory extends DTOFactory {
    @Override
    public CustomerDTO createDTO(Customer entity) {
        return new CustomerDTO(entity);
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X