想象一下:您站在厨房里准备做饭。你已经准备好了所有的原料,但你缺少一个可以遵循的食谱。你开始尝试,但很快你就感到不知所措。你在一道菜中加了太多盐,烧焦了另一道菜。如果没有明确的计划,烹饪就会变成一团乱七八糟的猜测。
创建软件就像这样。您拥有所有工具和专业知识,但如果没有良好组织的方法,添加新功能可能会成为一个令人沮丧的难题。您是否了解您的代码需要做什么,但是您是否正在找出使所有内容协同工作的最佳方法?这就是事情变得复杂的地方。一个微小的错误,你就会发现自己掉进了一个充满错误和混乱代码的洞。
输入设计模式——程序员多年来传承下来的经过时间考验的秘诀。这些可重复使用的修复程序可以帮助您毫不费力地处理制作软件的棘手部分。我们将了解设计模式到底是什么,它们如何使您的编码生活更轻松,以及为什么它们是构建健壮、易于维护的应用程序的关键。为了让事情变得更有趣,我们将在整个解释中使用烹饪术语 - 因为,说实话,谁不喜欢精彩的烹饪节目?
那么,什么是设计模式?他们将如何帮助我们构建更好的应用程序?
设计模式是一种可重用的解决方案模板,可以应用于软件设计中反复出现的问题和主题。这将是一本很好的食谱,其中包含经验丰富的开发人员针对软件设计常见问题进行尝试和测试的解决方案。指南指出,通过设计模式,我们可以在应用程序中实现可维护和可重用的代码。
设计模式本质上根据其解决的问题分为三大类:创造型设计模式、结构型设计模式和行为型设计模式。
设计模式根据其解决的问题分为三类。它们是创造型设计模式、结构型设计模式和行为型设计模式。
创建设计模式提供了创建对象的机制。在烹饪节目的背景下,这些模式就像烹饪前收集和准备食材一样。属于此类别的一些模式包括构造函数、工厂、抽象、原型、单例和生成器。为了更好地理解,请看下面的三个示例。
1。辛格尔顿
想象一下,有一些家庭的秘制酱汁,只能在一些特殊的锅里制作,代代相传。当然,锅不同,酱汁的味道也不可能一样。这几乎就是 Singleton 所做的:一种设计模式,其中类仅限于具有单个实例。
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。工厂方法
工厂方法提供了创建对象的通用接口,允许我们指定我们想要的对象类型。在我们的烹饪节目中,食谱书就是工厂。根据您想要制作的菜肴类型,它会给您所需的食谱(对象)。
// 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.
工厂方法在创建复杂对象的场景中会派上用场,例如根据环境生成不同的实例或管理许多相似的对象。
*3。抽象工厂 *
它封装了对象一般用法的实现细节。解释这一点的最好方法是,如果您考虑提供餐包送货服务:无论您烹饪意大利菜、中国菜还是墨西哥菜,这项服务都将提供包含食材和食谱的所有食物,仅根据手头的菜肴量身定制,因此一切都完美契合。
// 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.
结构设计模式侧重于对象组合,确定在不同对象之间建立关系的简单方法。它们有助于确保当系统的某一部分发生变化时,整体结构保持稳定。在烹饪中,这些图案代表了我们将食材组合成和谐美味菜肴的技术和工具。
属于此类别的模式包括装饰器、外观、享元、适配器和代理。
1。外观图案
外观模式为更复杂的代码体提供了方便的高级接口,有效地隐藏了底层的复杂性。想象一下,一位副主厨为主厨简化了复杂的任务。副主厨收集食材、准备它们并组织一切,以便主厨可以专注于菜肴的最后润色
// 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!
以上是编写你的代码:JavaScript 设计模式的详细内容。更多信息请关注PHP中文网其他相关文章!