Stellen Sie sich Folgendes vor: Sie stehen in Ihrer Küche und sind bereit, eine leckere Mahlzeit zuzubereiten. Sie haben alle Zutaten vorbereitet, aber Ihnen fehlt ein Rezept, dem Sie folgen können. Sie beginnen zu experimentieren, fühlen sich aber bald überfordert. Wenn Sie einem Gericht zu viel Salz hinzufügen, verbrennen Sie ein anderes. Ohne einen klaren Plan wird das Kochen zum Rätselraten.
So kann sich das Erstellen von Software anfühlen. Sie verfügen über alle Tools und das gesamte Know-how, aber das Hinzufügen neuer Funktionen kann ohne einen gut organisierten Ansatz zu einem frustrierenden Rätsel werden. Verstehen Sie, was Ihr Code tun muss, aber überlegen Sie auch, wie alles am besten zusammenarbeitet? Da wird es kompliziert. Ein kleiner Fehler, und Sie geraten in ein Loch voller Bugs und wirrem Code.
Geben Sie Designmuster ein – die bewährten Rezepte, die Programmierer im Laufe der Jahre weitergegeben haben. Diese wiederverwendbaren Korrekturen helfen Ihnen, die kniffligen Teile der Softwareerstellung zu bewältigen, ohne ins Schwitzen zu geraten. Wir gehen darauf ein, was Designmuster genau sind, wie sie Ihnen das Programmieren erleichtern können und warum sie der Schlüssel zum Erstellen robuster, leicht zu wartender Apps sind. Um die Sache interessanter zu machen, verwenden wir in unserer gesamten Erklärung Kochterminologie – denn seien wir mal ehrlich, wer liebt nicht eine gute Kochshow?
Was ist also ein Designmuster? Wie werden sie uns helfen, bessere Apps zu entwickeln?
Ein Designmuster ist eine wiederverwendbare Lösungsvorlage, die auf wiederkehrende Probleme und Themen im Softwaredesign angewendet werden kann. Das wird ein gutes Kochbuch mit bewährten Lösungen von erfahrenen Entwicklern sein, die an häufigen Problemen des Softwaredesigns arbeiten. Die Richtlinien besagen, dass wir mit Entwurfsmustern wartbaren und wiederverwendbaren Code in unseren Apps erreichen können.
Designmuster werden im Wesentlichen entsprechend dem Problem, das sie lösen, in drei große Kategorien eingeteilt: schöpferische Designmuster, strukturelle Designmuster und Verhaltensdesignmuster.
Entwurfsmuster werden basierend auf dem Problem, das sie lösen, in drei Kategorien unterteilt. Es handelt sich dabei um schöpferische Entwurfsmuster, strukturelle Entwurfsmuster und verhaltensbezogene Entwurfsmuster.
Kreative Entwurfsmuster bieten Mechanismen zum Erstellen von Objekten. Im Kontext einer Kochshow ähneln diese Muster dem Sammeln und Zubereiten von Zutaten vor dem Kochen. Einige Muster, die in diese Kategorie fallen, sind Constructor, Factory, Abstract, Prototype, Singleton und Builder. Um ein besseres Verständnis zu erlangen, schauen Sie sich die drei folgenden Beispiele an.
1. Singleton
Stellen Sie sich vor, dass es eine geheime Soße einer Familie gibt, die nur in einem speziellen Topf zubereitet werden kann, der über Generationen hinweg weitergegeben wird. Natürlich kann die Soße nicht gleich schmecken, wenn der Topf anders ist. Das ist so ziemlich das, was Singleton macht: ein Entwurfsmuster, bei dem eine Klasse auf eine einzige Instanz beschränkt ist.
class SecretSauce { constructor() { if (SecretSauce.instance) { return SecretSauce.instance; } SecretSauce.instance = this; this.flavor = 'Umami'; } getFlavor() { return this.flavor; } } const sauce1 = new SecretSauce(); const sauce2 = new SecretSauce(); console.log(sauce1.getFlavor() === sauce2.getFlavor()); // true
2. Fabrikmethode
Die Factory-Methode bietet eine generische Schnittstelle zum Erstellen von Objekten, die es uns ermöglicht, die Art des gewünschten Objekts anzugeben. In unserer Kochshow ist das Rezeptbuch die Fabrik. Abhängig von der Art des Gerichts, das Sie zubereiten möchten, erhalten Sie das Rezept – Objekt –, das Sie benötigen.
// Product Classes class Pizza { constructor(size, toppings) { this.size = size; this.toppings = toppings; } prepare() { console.log(`Preparing a ${this.size} pizza with ${this.toppings.join(', ')} toppings.`); } } class Pasta { constructor(sauce, noodles) { this.sauce = sauce; this.noodles = noodles; } prepare() { console.log(`Preparing pasta with ${this.noodles} noodles and ${this.sauce} sauce.`); } } // Creator Class class RecipeBook { createDish(type, options) { let dish; if (type === 'Pizza') { dish = new Pizza(options.size, options.toppings); } else if (type === 'Pasta') { dish = new Pasta(options.sauce, options.noodles); } return dish; } } // Usage const recipeBook = new RecipeBook(); const pizzaOptions = { size: 'large', toppings: ['cheese', 'pepperoni', 'olives'] }; const pastaOptions = { sauce: 'alfredo', noodles: 'fettuccine' }; const pizza = recipeBook.createDish('Pizza', pizzaOptions); const pasta = recipeBook.createDish('Pasta', pastaOptions); pizza.prepare(); // Preparing a large pizza with cheese, pepperoni, olives toppings. pasta.prepare(); // Preparing pasta with fettuccine noodles and alfredo sauce.
Die Factory-Methode ist praktisch in Szenarien der Erstellung komplexer Objekte – zum Beispiel beim Generieren unterschiedlicher Instanzen je nach Umgebung oder beim Verwalten vieler ähnlicher Objekte.
*3. Abstrakte Fabrik *
Es kapselt die Implementierungsdetails aus der allgemeinen Verwendung der Objekte. Das lässt sich am besten erklären, wenn man über einen Essenspaket-Lieferservice nachdenkt: Egal, ob man italienisch, chinesisch oder mexikanisch kocht, dieser Service liefert alles mit Zutaten und Rezepten, nur abgestimmt auf die jeweilige Küche, sodass alles perfekt passt.
// Abstract Factory Interfaces class ItalianKitchen { createPizza(options) { return new Pizza(options.size, options.toppings); } createPasta(options) { return new Pasta(options.sauce, options.noodles); } } class MexicanKitchen { createTaco(options) { return new Taco(options.shellType, options.fillings); } createBurrito(options) { return new Burrito(options.size, options.fillings); } } // Concrete Product Classes class Pizza { constructor(size, toppings) { this.size = size; this.toppings = toppings; } prepare() { console.log(`Preparing a ${this.size} pizza with ${this.toppings.join(', ')} toppings.`); } } class Pasta { constructor(sauce, noodles) { this.sauce = sauce; this.noodles = noodles; } prepare() { console.log(`Preparing pasta with ${this.noodles} noodles and ${this.sauce} sauce.`); } } class Taco { constructor(shellType, fillings) { this.shellType = shellType; this.fillings = fillings; } prepare() { console.log(`Preparing a taco with a ${this.shellType} shell and ${this.fillings.join(', ')} fillings.`); } } class Burrito { constructor(size, fillings) { this.size = size; this.fillings = fillings; } prepare() { console.log(`Preparing a ${this.size} burrito with ${this.fillings.join(', ')} fillings.`); } } // Client Code const italianKitchen = new ItalianKitchen(); const mexicanKitchen = new MexicanKitchen(); const italianPizza = italianKitchen.createPizza({ size: 'medium', toppings: ['mozzarella', 'tomato', 'basil'] }); const mexicanTaco = mexicanKitchen.createTaco({ shellType: 'hard', fillings: ['beef', 'lettuce', 'cheese'] }); italianPizza.prepare(); // Preparing a medium pizza with mozzarella, tomato, basil toppings. mexicanTaco.prepare(); // Preparing a taco with a hard shell and beef, lettuce, cheese fillings.
Strukturelle Entwurfsmuster konzentrieren sich auf die Objektkomposition und identifizieren einfache Möglichkeiten, Beziehungen zwischen verschiedenen Objekten herzustellen. Sie tragen dazu bei, dass die Gesamtstruktur stabil bleibt, wenn sich ein Teil eines Systems ändert. Beim Kochen stellen diese Muster die Techniken und Werkzeuge dar, die wir verwenden, um Zutaten zu einem harmonischen und köstlichen Gericht zu kombinieren.
Zu den Mustern, die in diese Kategorie fallen, gehören Decorator, Facade, Flyweight, Adapter und Proxy.
1. Das Fassadenmuster
Das Fassadenmuster bietet eine praktische High-Level-Schnittstelle zu einem komplexeren Codekörper und verbirgt so effektiv die zugrunde liegende Komplexität. Stellen Sie sich einen Sous-Chef vor, der dem Chefkoch komplexe Aufgaben vereinfacht. Der Sous-Chef sammelt die Zutaten, bereitet sie vor und organisiert alles, damit sich der Chefkoch auf den letzten Schliff des Gerichts konzentrieren kann
// Complex Subsystem class IngredientPrep { chop(ingredient) { console.log(`Chopping ${ingredient}.`); } measure(amount, ingredient) { console.log(`Measuring ${amount} of ${ingredient}.`); } } class CookingProcess { boil(waterAmount) { console.log(`Boiling ${waterAmount} of water.`); } bake(temp, duration) { console.log(`Baking at ${temp} degrees for ${duration} minutes.`); } } class Plating { arrangeDish(dish) { console.log(`Arranging the ${dish} on the plate.`); } garnish(garnish) { console.log(`Adding ${garnish} as garnish.`); } } // Facade Class class SousChef { constructor() { this.ingredientPrep = new IngredientPrep(); this.cookingProcess = new CookingProcess(); this.plating = new Plating(); } prepareDish(dishName) { console.log(`Starting to prepare ${dishName}...`); this.ingredientPrep.chop('vegetables'); this.ingredientPrep.measure('2 cups', 'flour'); this.cookingProcess.boil('1 liter'); this.cookingProcess.bake(180, 30); this.plating.arrangeDish(dishName); this.plating.garnish('parsley'); console.log(`${dishName} is ready!`); } } // Client Code const sousChef = new SousChef(); sousChef.prepareDish('Lasagna'); // Output: // Starting to prepare Lasagna... // Chopping vegetables. // Measuring 2 cups of flour. // Boiling 1 liter of water. // Baking at 180 degrees for 30 minutes. // Arranging the Lasagna on the plate. // Adding parsley as garnish. // Lasagna is ready!
2. Decorator
The Decorator pattern is used to modify existing systems by adding features to objects without significantly altering the underlying code. If our applications require many distinct types of objects, this pattern is ideal. For instance, when making coffee, we start with a basic cup and then dynamically add ingredients like milk, sugar, or whipped cream. The Decorator pattern lets us add the base coffee without changing the core recipe.
// Base Component class Coffee { constructor() { this.description = 'Basic Coffee'; } getDescription() { return this.description; } cost() { return 2; // Base cost for a simple coffee } } // Decorator Class class CoffeeDecorator { constructor(coffee) { this.coffee = coffee; } getDescription() { return this.coffee.getDescription(); } cost() { return this.coffee.cost(); } } // Concrete Decorators class Milk extends CoffeeDecorator { constructor(coffee) { super(coffee); } getDescription() { return `${this.coffee.getDescription()}, Milk`; } cost() { return this.coffee.cost() + 0.5; } } class Sugar extends CoffeeDecorator { constructor(coffee) { super(coffee); } getDescription() { return `${this.coffee.getDescription()}, Sugar`; } cost() { return this.coffee.cost() + 0.2; } } class WhippedCream extends CoffeeDecorator { constructor(coffee) { super(coffee); } getDescription() { return `${this.coffee.getDescription()}, Whipped Cream`; } cost() { return this.coffee.cost() + 0.7; } } // Client Code let myCoffee = new Coffee(); console.log(`${myCoffee.getDescription()} costs $${myCoffee.cost()}`); // Basic Coffee costs $2 myCoffee = new Milk(myCoffee); console.log(`${myCoffee.getDescription()} costs $${myCoffee.cost()}`); // Basic Coffee, Milk costs $2.5 myCoffee = new Sugar(myCoffee); console.log(`${myCoffee.getDescription()} costs $${myCoffee.cost()}`); // Basic Coffee, Milk, Sugar costs $2.7 myCoffee = new WhippedCream(myCoffee); console.log(`${myCoffee.getDescription()} costs $${myCoffee.cost()}`); // Basic Coffee, Milk, Sugar, Whipped Cream costs $3.4
3. Flyweight
The Flyweight pattern is a classical structural solution for optimizing code that is repetitive, slow, and inefficiently shares data. It aims to minimize memory in use in an application by sharing as much data as possible with related objects. Think of common ingredients like salt, pepper, and olive oil that are used in many dishes. Instead of having separate instances of these ingredients for each dish, they are shared across dishes to save resources. For example, you put salt on fried chicken and beef stew from the same jar.
// Flyweight Class class Ingredient { constructor(name) { this.name = name; } use() { console.log(`Using ${this.name}.`); } } // Flyweight Factory class IngredientFactory { constructor() { this.ingredients = {}; } getIngredient(name) { if (!this.ingredients[name]) { this.ingredients[name] = new Ingredient(name); } return this.ingredients[name]; } getTotalIngredientsMade() { return Object.keys(this.ingredients).length; } } // Client Code const ingredientFactory = new IngredientFactory(); const salt1 = ingredientFactory.getIngredient('Salt'); const salt2 = ingredientFactory.getIngredient('Salt'); const pepper = ingredientFactory.getIngredient('Pepper'); salt1.use(); // Using Salt. salt2.use(); // Using Salt. pepper.use(); // Using Pepper. console.log(ingredientFactory.getTotalIngredientsMade()); // 2, Salt and Pepper were created only once console.log(salt1 === salt2); // true, Salt is reused
Behavioral patterns focus on improving or streamlining the communication between disparate objects in a system. They identify common communication patterns among objects and provide solutions that distribute the communication responsibility among different objects, thereby increasing communication flexibility. In a cooking show, behavioral design patterns are the way we cook the dish, the process of cooking, and how various parts of the kitchen interact with each other to create the final dish. Some of the behavioral patterns are Iterator, Mediator, Observer, and Visitor.
1.Observer
The Observer pattern is used to notify components of state changes. When a subject needs to inform observers about a change, it broadcasts a notification. If an observer no longer wishes to receive updates, they can be removed from the list of observers. For example, once the head chef finishes preparing a dish, all the assistant chefs need to be notified to begin their tasks, such as plating or garnishing. The Observer pattern allows multiple chefs (observers) to be notified when the head chef (subject) completes a dish.
// Subject Class class HeadChef { constructor() { this.chefs = []; this.dishReady = false; } addObserver(chef) { this.chefs.push(chef); } removeObserver(chef) { this.chefs = this.chefs.filter(c => c !== chef); } notifyObservers() { if (this.dishReady) { this.chefs.forEach(chef => chef.update(this.dishName)); } } prepareDish(dishName) { this.dishName = dishName; console.log(`HeadChef: Preparing ${dishName}...`); this.dishReady = true; this.notifyObservers(); } } // Observer Class class Chef { constructor(name) { this.name = name; } update(dishName) { console.log(`${this.name}: Received notification - ${dishName} is ready!`); } } // Client Code const headChef = new HeadChef(); const chef1 = new Chef('Chef A'); const chef2 = new Chef('Chef B'); headChef.addObserver(chef1); headChef.addObserver(chef2); headChef.prepareDish('Beef Wellington'); // Output: // HeadChef: Preparing Beef Wellington... // Chef A: Received notification - Beef Wellington is ready! // Chef B: Received notification - Beef Wellington is ready!
2. Mediator
The Mediator pattern allows one object to be in charge of the communication between several other objects when an event occurs. While it does sound similar to the Observer pattern, the key difference is that the Mediator handles communication between objects rather than just broadcasting changes. For example, let's think of our kitchen with its grill, bakery, and garnish station sections. A kitchen coordinator (mediator) handles the communication so that all the preparations are done on time.
// Mediator Class class KitchenCoordinator { notify(sender, event) { if (event === 'dishPrepared') { console.log(`Coordinator: Notifying all stations that ${sender.dishName} is ready.`); } else if (event === 'orderReceived') { console.log(`Coordinator: Received order for ${sender.dishName}, notifying preparation stations.`); } } } // Colleague Classes class GrillStation { constructor(coordinator) { this.coordinator = coordinator; } prepareDish(dishName) { this.dishName = dishName; console.log(`GrillStation: Grilling ${dishName}.`); this.coordinator.notify(this, 'dishPrepared'); } } class BakeryStation { constructor(coordinator) { this.coordinator = coordinator; } bakeDish(dishName) { this.dishName = dishName; console.log(`BakeryStation: Baking ${dishName}.`); this.coordinator.notify(this, 'dishPrepared'); } } // Client Code const coordinator = new KitchenCoordinator(); const grillStation = new GrillStation(coordinator); const bakeryStation = new BakeryStation(coordinator); grillStation.prepareDish('Steak'); // Output: // GrillStation: Grilling Steak. // Coordinator: Notifying all stations that Steak is ready. bakeryStation.bakeDish('Bread'); // Output: // BakeryStation: Baking Bread. // Coordinator: Notifying all stations that Bread is ready.
3. Command
The Command design pattern is an Object Behavioral Pattern that encapsulates the invocation of methods, requests, or operations into a single object and allows both parameterization and pass method calls that can be executed at our discretion. For example, look at how the head chef gives the command below.
// Command Interface class Command { execute() {} } // Concrete Commands class GrillCommand extends Command { constructor(grillStation, dishName) { super(); this.grillStation = grillStation; this.dishName = dishName; } execute() { this.grillStation.grill(this.dishName); } } class BakeCommand extends Command { constructor(bakeryStation, dishName) { super(); this.bakeryStation = bakeryStation; this.dishName = dishName; } execute() { this.bakeryStation.bake(this.dishName); } } // Receiver Classes class GrillStation { grill(dishName) { console.log(`GrillStation: Grilling ${dishName}.`); } } class BakeryStation { bake(dishName) { console.log(`BakeryStation: Baking ${dishName}.`); } } // Invoker Class class HeadChef { setCommand(command) { this.command = command; } executeCommand() { this.command.execute(); } } // Client Code const grillStation = new GrillStation(); const bakeryStation = new BakeryStation(); const grillCommand = new GrillCommand(grillStation, 'Steak'); const bakeCommand = new BakeCommand(bakeryStation, 'Bread'); const headChef = new HeadChef(); headChef.setCommand(grillCommand); headChef.executeCommand(); // GrillStation: Grilling Steak. headChef.setCommand(bakeCommand); headChef.executeCommand(); // BakeryStation: Baking Bread.
Behavioral patterns can feel similar, so let's highlight their differences:
Observer: When a head chef prepares a dish, several other chefs are informed about it.
Mediator: A coordinator works in the kitchen, facilitating communication between various stations in the kitchen.
Command: The head chef issues commands to grill or bake dishes, encapsulating these actions as objects.
Design patterns give a clear way to fix common issues in software development much like a tidy kitchen and smart cooking methods lead to a good meal. When you get these patterns and put them to use, you make your coding easier and help your apps work better and grow more. It doesn't matter if you're new to coding or have done it for a long time - think of design patterns as trusted recipes passed down by many coders over the years. Try them out, play around with them, and soon you'll find that making strong apps becomes as natural as following a recipe you love. Happy coding!
Das obige ist der detaillierte Inhalt vonKochen Sie Ihren Code: JavaScript-Designmuster. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!