Wann sollten Sie das Schlüsselwort "new" verwenden und wann nicht?

15

Ich habe mir eine Google Tech Talk-Präsentation zum Thema Unit Testing von Misko Hevery angesehen new.

Ich habe ein Programm geschrieben, und am Ende habe ich newhier und da das Schlüsselwort verwendet, aber meistens zum Instanziieren von Objekten, die Daten enthalten (dh sie hatten keine Funktionen oder Methoden).

Ich frage mich, ob ich etwas falsch gemacht habe, als ich das neue Schlüsselwort für mein Programm verwendet habe. Und wo können wir diese "Regel" brechen?

Sal
quelle
2
Können Sie einen Tag hinzufügen, der mit der Programmiersprache zusammenhängt? New ist in vielen Sprachen verfügbar (C ++, Java, Ruby, um nur einige zu nennen) und hat eine andere Semantik.
Sakisk

Antworten:

16

Dies ist eher eine Anleitung als eine feste Regel.

Indem Sie in Ihrem Produktionscode "new" verwenden, verbinden Sie Ihre Klasse mit ihren Mitarbeitern. Wenn jemand einen anderen Collaborator verwenden möchte, beispielsweise eine Art Schein-Collaborator für Unit-Tests, kann er dies nicht, da der Collaborator in Ihrer Geschäftslogik erstellt wird.

Natürlich muss jemand diese neuen Objekte erstellen, aber dies lässt sich am besten an einer von zwei Stellen erledigen: einem Abhängigkeitsinjektionsframework wie Spring oder einer anderen Klasse, die Ihre Geschäftslogikklasse instanziiert und über den Konstruktor injiziert wird.

Natürlich können Sie dies zu weit gehen. Wenn Sie eine neue ArrayList zurückgeben möchten, sind Sie wahrscheinlich in Ordnung - insbesondere, wenn es sich um eine unveränderliche Liste handelt.

Die Hauptfrage, die Sie sich stellen sollten, lautet: "Liegt die Hauptverantwortung für das Erstellen von Objekten dieses Typs in diesem Codebaustein, oder handelt es sich nur um ein Implementierungsdetail, das ich vernünftigerweise an einen anderen Ort verschieben könnte?"

Bill Michell
quelle
1
Nun, wenn Sie möchten, dass es eine unveränderliche Liste ist, sollten Sie Collections.unmodifiableListoder so etwas verwenden. Aber ich weiß was du meinst :)
MatrixFrog
Ja, aber Sie müssen irgendwie die ursprüngliche Liste erstellen, die Sie dann in unveränderbare konvertieren ...
Bill Michell
5

Der Kern dieser Frage ist am Ende: Ich frage mich, ob ich etwas falsch gemacht habe, als ich das neue Schlüsselwort für mein Programm verwendet habe. Und wo können wir diese "Regel" brechen?

Wenn Sie in der Lage sind, effektive Komponententests für Ihren Code zu schreiben, haben Sie nichts falsch gemacht. Wenn Ihre Verwendung von newes schwierig oder unmöglich gemacht hat, Ihren Code zu testen, sollten Sie Ihre Verwendung von new neu bewerten. Sie könnten diese Analyse auf die Interaktion mit anderen Klassen ausweiten, aber die Fähigkeit, solide Komponententests zu schreiben, ist häufig ein ausreichend guter Proxy.

ObscureRobot
quelle
5

Kurz gesagt, wenn Sie "new" verwenden, koppeln Sie die Klasse, die diesen Code enthält, eng mit dem zu erstellenden Objekt. Um eines dieser Objekte zu instanziieren, muss die Klasse, die die Instanziierung durchführt, über die konkrete Klasse Bescheid wissen, die instanziiert wird. Wenn Sie also "neu" verwenden, sollten Sie überlegen, ob die Klasse, in der Sie die Instanziierung platzieren, ein "guter" Ort für dieses Wissen ist und Sie bereit sind, Änderungen in diesem Bereich vorzunehmen, wenn die Form der Das zu instanziierende Objekt sollte sich ändern.

Enge Kopplung, dh ein Objekt mit Kenntnis einer anderen konkreten Klasse, ist nicht immer zu vermeiden; Irgendwann muss etwas, irgendwo, wissen, wie man dieses Objekt erstellt, auch wenn sich alles andere mit dem Objekt befasst, indem man ihm eine Kopie von irgendwo anders gibt. Wenn sich die zu erstellende Klasse ändert, muss jedoch jede Klasse, die über die konkrete Implementierung dieser Klasse Bescheid weiß, aktualisiert werden, um die Änderungen dieser Klasse korrekt zu verarbeiten.

Die Frage, die Sie sich immer stellen sollten, lautet: "Wird es zur Pflicht, wenn diese Klasse weiß, wie diese andere Klasse erstellt wird, wenn Sie die App warten?" Die beiden wichtigsten Entwurfsmethoden (SOLID und GRASP) antworten normalerweise aus subtil unterschiedlichen Gründen mit "Ja". Es handelt sich jedoch nur um Methoden, und beide haben die äußerste Einschränkung, dass sie nicht auf der Grundlage der Kenntnis Ihres einzigartigen Programms formuliert wurden. Als solche können sie nur zur Vorsicht neigen und davon ausgehen, dass ein Punkt mit fester Kopplung EVENTUELL ein Problem verursacht, wenn Änderungen an einer oder beiden Seiten dieses Punkts vorgenommen werden. Sie müssen eine endgültige Entscheidung treffen, wenn Sie drei Dinge wissen; die theoretische Best Practice (dh alles locker zu koppeln, weil sich alles ändern kann); die Kosten für die Implementierung der theoretischen Best Practice (die mehrere neue Abstraktionsebenen umfassen kann, die eine Art von Änderung erleichtern und eine andere behindern); und die reale Wahrscheinlichkeit, dass die Art von Änderung, die Sie erwarten, jemals notwendig sein wird.

Einige allgemeine Richtlinien:

  • Vermeiden Sie eine enge Kopplung zwischen kompilierten Codebibliotheken. Die Schnittstelle zwischen DLLs (oder einer EXE-Datei und ihren DLLs) ist der Hauptort, an dem eine enge Kopplung einen Nachteil darstellt. Wenn Sie eine Änderung an einer Klasse A in DLL X vornehmen und Klasse B in der Haupt-EXE-Datei über Klasse A Bescheid weiß, müssen Sie beide Binärdateien neu kompilieren und freigeben. Innerhalb einer einzelnen Binärdatei ist eine engere Kopplung im Allgemeinen zulässig, da die gesamte Binärdatei ohnehin für jede Änderung neu erstellt werden muss. Manchmal ist es unvermeidlich, mehrere Binärdateien neu zu erstellen, aber Sie sollten Ihren Code so strukturieren, dass Sie ihn nach Möglichkeit vermeiden können, insbesondere in Situationen, in denen die Bandbreite knapp ist (z. B. beim Bereitstellen mobiler Apps; das Pushen einer neuen DLL in einem Upgrade ist weitaus günstiger als das gesamte Programm schieben).

  • Vermeiden Sie eine enge Kopplung zwischen den wichtigsten "Logikzentren" Ihres Programms. Sie können sich ein gut strukturiertes Programm vorstellen, das aus horizontalen und vertikalen Segmenten besteht. Horizontale Schichten können herkömmliche Anwendungsebenen sein, wie z. B. Benutzeroberfläche, Controller, Domäne, DAO, Daten; Vertikale Segmente können für einzelne Fenster oder Ansichten oder für einzelne "User Stories" definiert werden (wie das Erstellen eines neuen Datensatzes eines Grundtyps). Wenn Sie einen Anruf tätigen, der sich in einem gut strukturierten System nach oben, unten, links oder rechts bewegt, sollten Sie diesen Anruf im Allgemeinen abstrahieren. Wenn die Validierung beispielsweise Daten abrufen muss, sollte sie keinen direkten Zugriff auf die Datenbank haben, sondern eine Schnittstelle zum Abrufen von Daten aufrufen, die von dem tatsächlichen Objekt unterstützt wird, das dies weiß. Wenn eine UI-Steuerung eine erweiterte Logik mit einem anderen Fenster ausführen muss, es sollte die Auslösung dieser Logik über ein Ereignis und / oder einen Rückruf abstrahieren; Es muss nicht bekannt sein, was als Ergebnis ausgeführt wird. So können Sie die Ausführung ändern, ohne das Steuerelement zu ändern, das sie auslöst.

  • Überlegen Sie auf jeden Fall, wie einfach oder schwierig eine Änderung sein wird und wie wahrscheinlich diese Änderung sein wird. Wenn ein Objekt, das Sie erstellen, immer nur von einer Stelle aus verwendet wird und Sie diese Änderung nicht vorhersehen, ist eine enge Kopplung im Allgemeinen zulässiger und kann in dieser Situation sogar einer losen Kopplung überlegen sein. Lose Kopplung erfordert Abstraktion. Dies ist eine zusätzliche Ebene, die Änderungen an abhängigen Objekten verhindert, wenn sich die Implementierung einer Abhängigkeit ändern muss. Wenn sich jedoch die Schnittstelle selbst ändern muss (Hinzufügen eines neuen Methodenaufrufs oder Hinzufügen eines Parameters zu einem vorhandenen Methodenaufruf), erhöht eine Schnittstelle tatsächlich den Arbeitsaufwand, der für die Änderung erforderlich ist. Sie müssen die Wahrscheinlichkeit abwägen, dass verschiedene Arten von Änderungen das Design beeinflussen.

KeithS
quelle
3

Objekte, die nur Daten enthalten, oder DTOs (Datenübertragungsobjekte) sind in Ordnung, erstellen diese den ganzen Tag. Wenn Sie vermeiden möchten, dass newKlassen Aktionen ausführen, möchten Sie, dass die konsumierenden Klassen stattdessen über die Schnittstelle programmieren . Dort können sie diese Logik aufrufen und verwenden, sind jedoch nicht dafür verantwortlich, die Instanzen der Klassen zu erstellen, die die Logik enthalten . Diese aktionsausführenden oder Logik enthaltenden Klassen sind Abhängigkeiten. Miskos Vortrag zielt darauf ab, dass Sie diese Abhängigkeiten einschleusen und es einer anderen Entität überlassen, sie tatsächlich zu erstellen.

Anthony Pegram
quelle
2

Andere haben diesen Punkt bereits erwähnt, wollten ihn jedoch aus Onkel Bobs (Bob Martin) Clean Code zitieren, da dies das Verständnis des Konzepts erleichtern könnte:

"Ein leistungsfähiger Mechanismus zur Trennung von Konstruktion und Nutzung ist Dependency Injection (DI), die Anwendung von Inversion of Control (IoC) auf das Abhängigkeitsmanagement. Inversion of Control verlagert sekundäre Verantwortlichkeiten von einem Objekt auf andere Objekte, die dem Zweck dienen, und unterstützt so Prinzip der Einzelverantwortung : Im Kontext des Abhängigkeitsmanagements sollte ein Objekt nicht die Verantwortung für die Instanziierung von Abhängigkeiten selbst übernehmen, sondern diese Verantwortung auf einen anderen "maßgeblichen" Mechanismus übertragen und dadurch die Steuerung umkehren Der maßgebliche Mechanismus ist normalerweise entweder die "Hauptroutine" oder ein Spezialcontainer. "

Ich denke, es ist immer eine gute Idee, diese Regel zu befolgen. Andere erwähnten die wichtigere IMO -> sie entkoppelt Ihre Klasse von ihren Abhängigkeiten (Kollaborateure). Der zweite Grund ist, dass Ihre Klassen dadurch kleiner und knapper werden, einfacher zu verstehen sind und sogar in anderen Kontexten wiederverwendet werden können.

c_maker
quelle