Heim >Web-Frontend >js-Tutorial >Designmuster entmystifizieren
Die KI-Ära ist also da, der große Sprung nach vorne, der im Moment Node-Code mit const fetch = require('node-fetch') ausspuckt? (gilt ab heute sowohl für ChatGPT als auch für Gemini) und speist eine weitere Drehung der zyklischen Maschine, die das Internet und seine Inhalte darstellt.
In dieser Verschmelzung von Inhalten tauchen wieder Designmuster auf
Von Beiträgen, die erklären, wie man Entwurfsmuster in Node(???) anwendet, bis hin zu Beiträgen, die mit allen Details veraltete Dinge erklären, wie zum Beispiel, wie man das Factory-Muster in Java anwendet (Java 8 wurde im März 2014 veröffentlicht Lambdas hinzugefügt ).
Sind Sie schon einmal auf den Refactoring-Guru gestoßen?
Es handelt sich um eine Website, die Sie wahrscheinlich auf Ihrer Lernreise in die Informatik, insbesondere in die Programmierung, besucht haben. Der Abschnitt mit den Designmustern ist ziemlich gut erklärt und einer der im Laufe der Jahre in verschiedenen Foren am häufigsten geteilten Abschnitte.
Wenn wir uns mit der Definition dessen befassen, was Designmuster sind, finden wir:
Entwurfsmuster sind typische Lösungen für häufige Probleme
im Softwaredesign. Jedes Muster ist wie eine Blaupause
die Sie anpassen können, um ein bestimmtes Problem zu lösen
Designproblem in Ihrem Code.
Warum dann dieser Beitrag? Ich meine, es gibt jede Menge Informationen auf der oben verlinkten Website; das könnte alles sein.
Die Sache ist, dass es mir immer schwerfiel, diese Definition zu akzeptieren... „um ein bestimmtes Designproblem in meinem Code zu lösen“... in meinem Code? Hat mein Code ein Problem, das ich lösen muss?
Was wirklich passiert, ist, dass ich ein bestimmtes „Etwas“ programmieren muss, für das es der im Projekt verwendeten Programmiersprache an Abstraktionen mangelt.
Schlicht und einfach. Für den Fall, dass Ihnen das noch nicht gefällt, sehen wir uns einige Beispiele mit Code an.
Dies ist eine wirklich einfache Implementierung des Factory Pattern in Java (hauptsächlich eine objektorientierte Programmiersprache).
public class ShapeFactory { public Shape createShape(String type) { if (type.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (type.equalsIgnoreCase("SQUARE")) { return new Square(); } return null; } }
Dann hat Java 8 (März 2014, nur für den Fall, dass Sie es vergessen haben) Lambdas (ein Konzept aus der funktionalen Programmierung) hinzugefügt, damit wir stattdessen Folgendes tun können:
Map<String, Supplier<Shape>> shapeFactory = new HashMap<>(); shapeFactory.put("CIRCLE", Circle::new); shapeFactory.put("SQUARE", Square::new); Shape circle = shapeFactory.get("CIRCLE").get();
Das Factory-Design-Muster wird nie wieder benötigt (zumindest in Java).
Ja, ich weiß, dass das Fabrikmuster das Beispiel ist, das die meisten Leute ständig verwenden, aber was passiert mit den anderen? Und was passiert in anderen Programmiersprachen?
Dies ist das Besuchermuster in Typescript:
interface Shape { draw(): void; accept(visitor: ShapeVisitor): void; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } draw() { console.log("Drawing a circle"); } accept(visitor: ShapeVisitor) { visitor.visitCircle(this); } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } draw() { console.log("Drawing a square"); } accept(visitor: ShapeVisitor) { visitor.visitSquare(this); } } interface ShapeVisitor { visitCircle(circle: Circle): void; visitSquare(square: Square): void; } class AreaCalculator implements ShapeVisitor { private area = 0; visitCircle(circle: Circle) { this.area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${this.area}`); } visitSquare(square: Square) { this.area = square.sideLength * square.sideLength; console.log(`Square area: ${this.area}`); } getArea(): number { return this.area; } } // Using the Visitor const circle = new Circle(5); const square = new Square(4); const calculator = new AreaCalculator(); circle.accept(calculator); square.accept(calculator);
Der folgende Code macht genau das Gleiche, verwendet jedoch Reflexion (die Fähigkeit einer Sprache, ihre eigenen Objekte zur Laufzeit zu untersuchen und zu manipulieren) anstelle des Besuchermusters:
interface Shape { draw(): void; } class Circle implements Shape { // ... (same as before) radius: number; } class Square implements Shape { // ... (same as before) sideLength: number; } function calculateArea(shape: Shape) { if (shape instanceof Circle) { const circle = shape as Circle; // Type assertion const area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${area}`); } else if (shape instanceof Square) { const square = shape as Square; // Type assertion const area = square.sideLength * square.sideLength; console.log(`Square area: ${area}`); } } const circle = new Circle(5); const square = new Square(4); calculateArea(circle); calculateArea(square);
Jetzt das Beobachtermuster, auch in TypeScript:
interface Observer { update(data: any): void; } class NewsPublisher { private observers: Observer[] = []; subscribe(observer: Observer) { this.observers.push(observer); } unsubscribe(observer: Observer) { this.observers = this.observers.filter(o => o !== observer); } notify(news: string) { this.observers.forEach(observer => observer.update(news)); } } class NewsletterSubscriber implements Observer { update(news: string) { console.log(`Received news: ${news}`); } } // Using the Observer const publisher = new NewsPublisher(); const subscriber1 = new NewsletterSubscriber(); const subscriber2 = new NewsletterSubscriber(); publisher.subscribe(subscriber1); publisher.subscribe(subscriber2); publisher.notify("New product launched!");
Dasselbe, aber unter Verwendung des integrierten (in der Node-API) EventEmitter:
public class ShapeFactory { public Shape createShape(String type) { if (type.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (type.equalsIgnoreCase("SQUARE")) { return new Square(); } return null; } }
An diesem Punkt haben Sie vielleicht erkannt, dass das „Problem“ die OOP-Implementierung ist, und Sie hätten völlig Recht, aber nicht vollständig.
Jedes Programmierparadigma, insbesondere wenn es in seiner reinsten Form betrachtet wird, hat seine Macken, Schwierigkeiten oder „Dinge, die nicht auf einem geradlinigen Weg erreicht werden können“, wenn Sie so wollen.
Kommen wir zum Bereich der funktionalen Programmierung. Sie haben wahrscheinlich schon von Monaden gehört.
Ganz gleich, ob Sie in die Denkfalle der mathematischen Definition geraten sind oder nicht, wir – Softwareentwickler – könnten Monaden auch als Entwurfsmuster verstehen. Denn in einer Welt reiner Funktionen, in der nichts Unerwartetes passiert, ist es schwierig, sich einen Nebeneffekt vorzustellen, aber die meisten Softwareprodukte brauchen Nebenwirkungen, also wie können wir...?
Dies ist ein Beispiel der IO-Monade in Haskell:
Map<String, Supplier<Shape>> shapeFactory = new HashMap<>(); shapeFactory.put("CIRCLE", Circle::new); shapeFactory.put("SQUARE", Square::new); Shape circle = shapeFactory.get("CIRCLE").get();
Der Nebeneffekt (Lesen einer Datei) ist in der IO-Monade enthalten.
Fügen wir ein monadisches Beispiel mit Typoskript hinzu;
interface Shape { draw(): void; accept(visitor: ShapeVisitor): void; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } draw() { console.log("Drawing a circle"); } accept(visitor: ShapeVisitor) { visitor.visitCircle(this); } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } draw() { console.log("Drawing a square"); } accept(visitor: ShapeVisitor) { visitor.visitSquare(this); } } interface ShapeVisitor { visitCircle(circle: Circle): void; visitSquare(square: Square): void; } class AreaCalculator implements ShapeVisitor { private area = 0; visitCircle(circle: Circle) { this.area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${this.area}`); } visitSquare(square: Square) { this.area = square.sideLength * square.sideLength; console.log(`Square area: ${this.area}`); } getArea(): number { return this.area; } } // Using the Visitor const circle = new Circle(5); const square = new Square(4); const calculator = new AreaCalculator(); circle.accept(calculator); square.accept(calculator);
Ein Klassiker, ich habe die Monade vielleicht 50 Mal im Internet gesehen, aber was ist das eigentlich?
Das Problemdas es zu lösen versucht:
interface Shape { draw(): void; } class Circle implements Shape { // ... (same as before) radius: number; } class Square implements Shape { // ... (same as before) sideLength: number; } function calculateArea(shape: Shape) { if (shape instanceof Circle) { const circle = shape as Circle; // Type assertion const area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${area}`); } else if (shape instanceof Square) { const square = shape as Square; // Type assertion const area = square.sideLength * square.sideLength; console.log(`Square area: ${area}`); } } const circle = new Circle(5); const square = new Square(4); calculateArea(circle); calculateArea(square);
Wir haben vergessen, die Eigenschaften unseres Objekts zu definieren! ?
In einem realen Anwendungsfall wäre dies meist die Eingabe eines Nebeneffekts, wie das Lesen aus einer Datenbank oder einer Datei
Wenn wir das jetzt tun:
interface Observer { update(data: any): void; } class NewsPublisher { private observers: Observer[] = []; subscribe(observer: Observer) { this.observers.push(observer); } unsubscribe(observer: Observer) { this.observers = this.observers.filter(o => o !== observer); } notify(news: string) { this.observers.forEach(observer => observer.update(news)); } } class NewsletterSubscriber implements Observer { update(news: string) { console.log(`Received news: ${news}`); } } // Using the Observer const publisher = new NewsPublisher(); const subscriber1 = new NewsletterSubscriber(); const subscriber2 = new NewsletterSubscriber(); publisher.subscribe(subscriber1); publisher.subscribe(subscriber2); publisher.notify("New product launched!");
Das Programm explodiert.
Die Lösung ohne die Maybe-Monade:
import { EventEmitter } from 'events'; class NewsPublisher extends EventEmitter { publish(news: string) { this.emit('news', news); } } const publisher = new NewsPublisher(); publisher.on('news', (news) => { console.log(`All subscribers received the news: ${news}`); }); publisher.publish("New product launched!");
Das Programm explodiert nicht.
Die vielleicht-Monade ist in JavaScript oder Typoskript aufgrund des optionalen Verkettungsoperators nicht erforderlich. Wenn Sie jedoch eine Sprache verwenden, die sie nicht implementiert, können Sie die vielleicht-Monade oder anwenden Soll ich Designmuster sagen?
Ja, ich weiß, es gibt Leute, die gerade das Vielleicht-Ding gelernt haben und es eifrig auf 6 Nebenprojekte gleichzeitig angewendet haben, und jetzt bin ich der Lacher auf der Party, weil ich dir sage: „Du brauchst es nicht“. Du kannst es aber trotzdem verwenden, ich lade dich sogar dazu ein, wenn du es cool findest (am Ende des Tages ist es dein Code mit diesem hübschen Gesicht, mit dem du machen kannst, was du willst! ?)
Aber zurück zum Wesentlichen. Was ist mit anderen Paradigmen? Wenn Sie über den OOP/FP-Rahmen hinaus denken, gefällt es mir!
Alle Paradigmen haben definitiv ihre eigenen wiederkehrenden Lösungen und Techniken, auch wenn sie nicht immer formal als „Entwurfsmuster“ bezeichnet werden.
Hier sind ein paar Beispiele (danke Gemini, dass du mir das Nachdenken erspart hast, danke mir für die hübsche Formatierung und den Mehrwert?):
Es gibt eine Menge „Techniken“ und „Muster“, diese Liste soll Ihnen nur Anregungen geben, wenn Sie neugierig sind.
Ich hoffe, dass Sie das nützlich finden, ich werde es bald lesen!
Während der Begriff „Entwurfsmuster“ am engsten mit OOP verbunden ist, haben andere Paradigmen ihre eigenen wiederkehrenden Lösungen und Techniken. Diese Techniken gehen auf die spezifischen Herausforderungen und Einschränkungen dieser Paradigmen ein und bieten etablierte Ansätze für häufige Probleme. Auch wenn sie nicht immer offiziell als „Entwurfsmuster“ bezeichnet werden, dienen sie einem ähnlichen Zweck, indem sie Entwickler zu effektiven und wartbaren Lösungen führen.
Wir können Entwurfsmuster als bekannte Problemumgehungen zum Patchen von Funktionen verstehen, für die der von uns verwendeten Programmiersprache Abstraktionen fehlen.
Dieser Beitrag wurde fast ausschließlich von mir geschrieben, angegebene Beispiele stammen von Gemini 1.5 Pro
Das obige ist der detaillierte Inhalt vonDesignmuster entmystifizieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!