」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > 設計模式:深入探討常見設計模式

設計模式:深入探討常見設計模式

發佈於2024-11-03
瀏覽:663

DESIGN PATTERNS : A Deep Dive into Common Design Patterns

What is a design pattern?

Design patterns are solutions to complex problems. Design patterns are all about crafting your classes and interfaces in a way that solves a particular design problem. Usually, while designing a system, we encounter some issues, and for those problems, we have a set of design patterns. Design patterns are generally templates that involve classes, interfaces, and the relationships between those classes.

Types of design patterns:

Creational design patterns:

These types of patterns deal with the creation of objects in a way that is compatible with the given situation.
At the creational level, we can determine how specific parts of our systems can be created independently or composed together, ensuring flexibility and compatibility.
The list of design patterns that fall under this category are:

  • Singleton: In this design pattern, we just have only one instance and that instance will be used throughout our application.

Essentials of singleton design pattern:

  1. private constructor: Marking constructor as private is very crucial, as we want to ensure that only one instance of the class is created.
  2. Private Static Instance: We use private access modifier,because we want to ensure that we only have one instance of the object in the class memory.
  3. Public Static Method (Accessor): This is a global access point to the single instance. This method basically creates the instance if it doesn't exist else return the same instance if it already exists.

Example of Singleton design pattern

public class Singleton {
    // Private static instance of the class
    private static Singleton instance;
    private int count;

    // Private constructor to prevent instantiation
    private Singleton() {
        // initialization code
    }

    // Public static method to provide access to the instance
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    // Example method
    public void getCount() {
        System.out.println("The value of count is: "   count);
    }

    public void increaseCount() {
        count  ;
    }

    public void decreaseCount() {
        count--;
    }
}

public class Main {
    public static void main(String[] args) {
        // Get the single instance of Singleton
        Singleton singleton = Singleton.getInstance();
        singleton.increaseCount();
        singleton.getCount();  // Output: The value of count is: 1

        // Get the same instance of Singleton
        Singleton anotherSingleton = Singleton.getInstance();
        anotherSingleton.decreaseCount();
        anotherSingleton.getCount();  // Output: The value of count is: 0

        // Both singleton and anotherSingleton refer to the same instance
    }
}

  • Builder: In the Builder pattern, we define a way to create an object step-by-step. This pattern also provides flexibility, allowing different versions of the same object to be created using the same construction process.

Key essentials of the builder pattern:

  1. Product: It is the complex object that is being constructed.
  2. Builder Interface: Defines the methods to create different parts of the Product. These methods typically return the builder object itself to allow method chaining.
  3. Concrete Builder:Implements the Builder interface and provides specific implementations for creating parts of the Product.

Example of builder pattern:
This example shows how to use the Builder Design Pattern to make a chocolate spread bread by adding ingredients step by step.

// Product Class
class Bread {
    private String bread;
    private String spread;
    private String chiaSeeds;
    private String pumpkinSeeds;

    public void setBread(String bread) {
        this.bread = bread;
    }

    public void setSpread(String spread) {
        this.spread = spread;
    }

    public void setChiaSeeds(String chiaSeeds) {
        this.chiaSeeds = chiaSeeds;
    }

    public void setPumpkinSeeds(String pumpkinSeeds) {
        this.pumpkinSeeds = pumpkinSeeds;
    }

    @Override
    public String toString() {
        return "Bread with "   spread   ", topped with "   chiaSeeds   " and "   pumpkinSeeds;
    }
}

// Builder Interface
interface BreadBuilder {
    BreadBuilder addBread();
    BreadBuilder addChocolateSpread();
    BreadBuilder addChiaSeeds();
    BreadBuilder addPumpkinSeeds();
    Bread build();
}

// Concrete Builder
class ChocolateBreadBuilder implements BreadBuilder {
    private Bread bread = new Bread();

    @Override
    public BreadBuilder addBread() {
        bread.setBread("Whole grain bread");
        return this;
    }

    @Override
    public BreadBuilder addChocolateSpread() {
        bread.setSpread("Chocolate spread");
        return this;
    }

    @Override
    public BreadBuilder addChiaSeeds() {
        bread.setChiaSeeds("Chia seeds");
        return this;
    }

    @Override
    public BreadBuilder addPumpkinSeeds() {
        bread.setPumpkinSeeds("Pumpkin seeds");
        return this;
    }

    @Override
    public Bread build() {
        return bread;
    }
}

// Client Code
public class Main {
    public static void main(String[] args) {
        // Create a builder and build the chocolate spread bread
        BreadBuilder builder = new ChocolateBreadBuilder();
        Bread myBread = builder.addBread()
                               .addChocolateSpread()
                               .addChiaSeeds()
                               .addPumpkinSeeds()
                               .build();

        // Output the result
        System.out.println(myBread);
    }
}

  • Factory Method: In the Factory Method pattern, we define a way to create objects, but we allow subclasses to decide the specific type of object that will be created.

Key essentials of factory pattern:

  1. Product Interface: Defines the common interface for all products.
  2. Concrete Products: Implement the Product interface.
  3. Creator: Declares the factory method.
  4. Concrete Creators: Implement the factory method to return different concrete products.
// Product Interface
interface Juice {
    void serve();
}

// Concrete Product 1
class OrangeJuice implements Juice {
    @Override
    public void serve() {
        System.out.println("Serving Orange Juice.");
    }
}

// Concrete Product 2
class MangoJuice implements Juice {
    @Override
    public void serve() {
        System.out.println("Serving Mango Juice.");
    }
}

// Creator Abstract Class
abstract class JuiceFactory {
    // Factory method
    public abstract Juice createJuice();
}

// Concrete Creator 1
class OrangeJuiceFactory extends JuiceFactory {
    @Override
    public Juice createJuice() {
        return new OrangeJuice();
    }
}

// Concrete Creator 2
class MangoJuiceFactory extends JuiceFactory {
    @Override
    public Juice createJuice() {
        return new MangoJuice();
    }
}

// Client Code
public class Main {
    public static void main(String[] args) {
        // Create an Orange Juice using its factory
        JuiceFactory orangeJuiceFactory = new OrangeJuiceFactory();
        Juice orangeJuice = orangeJuiceFactory.createJuice();
        orangeJuice.serve();  // Output: Serving Orange Juice.

        // Create a Mango Juice using its factory
        JuiceFactory mangoJuiceFactory = new MangoJuiceFactory();
        Juice mangoJuice = mangoJuiceFactory.createJuice();
        mangoJuice.serve();  // Output: Serving Mango Juice.
    }
}

Structural design patterns

This design patterns mainly focuses on how classes and objects are composed to form larger structures. They focus on the organization and relationships between objects and classes, simplifying the structure, enhancing flexibility, and promoting maintainability.

  • Adapter pattern: In this pattern, we allow objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, enabling them to communicate without altering their existing code.

Key Essentials of the adapter pattern:

  1. Target Interface: It is an interface that will solve the problem (bridging the gap between the incompatible interfaces).
  2. Client: The class or code that interacts with the target interface.
  3. Adaptee: This is the interface which is not compatible with the current client requirements.
  4. Adapter: Implements the target interface and contains an instance of the adaptee. It translates requests from the target interface to the adaptee’s interface, making them compatible.
// Target Interface (Menu)
interface Menu {
    void orderDish(String dish);
}

// Adaptee (Chef)
class Chef {
    public void prepareDish(String dishName) {
        System.out.println("Chef is preparing "   dishName   ".");
    }
}

// Adapter (Waiter)
class Waiter implements Menu {
    private Chef chef;

    public Waiter(Chef chef) {
        this.chef = chef;
    }

    @Override
    public void orderDish(String dish) {
        chef.prepareDish(dish);
    }
}

// Client Code
public class Restaurant {
    public static void main(String[] args) {
        Chef chef = new Chef();
        Menu waiter = new Waiter(chef);

        // Customer places an order via the waiter
        waiter.orderDish("Spaghetti Carbonara");  // Output: Chef is preparing Spaghetti Carbonara.
    }
}

  • Facade pattern: Simplifies the interaction with a complex system by providing a unified interface (facade). Instead of directly calling several different methods across various objects, the client interacts with the facade, which internally manages those operations.

Key essentials of the facade design pattern:

  1. Facade: It is an interface that wraps all the complex subsystem interfaces and delegates the complex tasks to the subsystems that actually perform the work.
  2. Subsystem Classes: These are the classes that acutally perform the work.

An example of facade design pattern:
The example illustrates the Facade Pattern which simplifies the process of washing, drying, and pressing clothes. It hides the complexity of interacting with multiple subsystems behind a single, unified interface.

// Subsystem Classes
class WashingMachine {
    public void wash() {
        System.out.println("Washing clothes.");
    }
}

class Dryer {
    public void dry() {
        System.out.println("Drying clothes.");
    }
}

class Iron {
    public void press() {
        System.out.println("Pressing clothes.");
    }
}

// Facade Class
class LaundryFacade {
    private WashingMachine washingMachine;
    private Dryer dryer;
    private Iron iron;

    public LaundryFacade(WashingMachine washingMachine, Dryer dryer, Iron iron) {
        this.washingMachine = washingMachine;
        this.dryer = dryer;
        this.iron = iron;
    }

    public void doLaundry() {
        System.out.println("Starting the laundry process...");
        washingMachine.wash();
        dryer.dry();
        iron.press();
        System.out.println("Laundry process complete.");
    }
}

// Client Code
public class Main {
    public static void main(String[] args) {
        WashingMachine washingMachine = new WashingMachine();
        Dryer dryer = new Dryer();
        Iron iron = new Iron();

        LaundryFacade laundryFacade = new LaundryFacade(washingMachine, dryer, iron);

        // Use the facade to do the laundry
        laundryFacade.doLaundry();
    }
}

Behavioral design patterns

The patterns that fall under this category mainly deals with communication between objects and how they interact with each other.

  • Iterator pattern: In the Iterator Pattern, we define a way to sequentially access elements of a collection without needing to use conventional methods, such as for loops or direct indexing. Instead, the pattern provides a standard interface (usually methods like next() and hasNext()) to traverse the collection. This approach abstracts the iteration process, allowing the client to navigate through the collection without needing to understand its internal structure or use traditional iteration methods.

Key essentials of this pattern are:

  1. Iterator Interface: We define all the methods such as next(), hasNext(), and currentItem().These are used to traverse the collection.
  2. Concrete Iterator: This is the concrete implementation of the iterator interface.
  3. Aggregate Interface: In this interface,we define methods to create iterators.All the methods returns an instance of the Iterator.
  4. Concrete Aggregate: It's just a concrete implementation of the aggregate interface.

Example of iterator pattern:
This example demostrates a simple usecase of iterators a employees object using iterator pattern.

// Iterator Interface
interface Iterator {
    boolean hasNext();
    Object next();
}

// Aggregate Interface 
interface Aggregate {
    Iterator createIterator();
}

// Employee Class
class Employee {
    public String Name;
    public int Age;
    public String Department;
    public int EmployeeId;

    public Employee(String name, int age, String department, int employeeId) {
        this.Name = name;
        this.Age = age;
        this.Department = department;
        this.EmployeeId = employeeId;
    }
}

// Concrete Aggregate
class EmployeeCollection implements Aggregate {
    private Employee[] employees;

    public EmployeeCollection(Employee[] employees) {
        this.employees = employees;
    }

    @Override
    public Iterator createIterator() {
        return new EmployeeIterator(this.employees);
    }
}

// Concrete Iterator
class EmployeeIterator implements Iterator {
    private Employee[] employees;
    private int position = 0;

    public EmployeeIterator(Employee[] employees) {
        this.employees = employees;
    }

    @Override
    public boolean hasNext() {
        return position 



  • Strategy pattern: In this pattern we define a family of algorithms, and at the runtime we choose the algorithm.Instead of implementing a single algorithm directly, the code receives runtime instructions on which algorithm to use from a family of algorithms. This pattern allows the algorithm to vary independently from the clients that use it.

Key essentials of this pattern are:

1.Strategy Interface: Defines the common interface for all supported algorithms.
2.Concrete Strategies: Implement the Strategy interface with specific algorithms.
3.Context: Uses a Strategy to execute the algorithm.

Example of strategy pattern:
Imagine we are building an encoding system where we may need to use different encoding algorithms depending on the situation. We will demonstrate this system using the Strategy Pattern.

// Strategy Interface
interface EncoderStrategy {
    void encode(String string);
}

// Concrete Strategy for Base64 Encoding
class Base64Encoder implements EncoderStrategy {
    @Override
    public void encode(String string) {
        // Implement Base64 encoding logic here
        System.out.println("This method uses Base64 encoding algorithm for: "   string);
    }
}

// Concrete Strategy for MD5 Encoding
class MD5Encoder implements EncoderStrategy {
    @Override
    public void encode(String string) {
        // Implement MD5 encoding logic here
        System.out.println("This method uses MD5 encoding algorithm for: "   string);
    }
}

// Context Class
class EncoderContext {
    private EncoderStrategy strategy;

    public void setEncoderMethod(EncoderStrategy strategy) {
        this.strategy = strategy;
    }

    public void encode(String string) {
        strategy.encode(string);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        EncoderContext context = new EncoderContext();

        // Use Base64 encoding method
        context.setEncoderMethod(new Base64Encoder());
        context.encode("A34937ifdsuhfweiur");

        // Use MD5 encoding method
        context.setEncoderMethod(new MD5Encoder());
        context.encode("89743297dfhksdhWOJO");
    }
}

Explanation:

  1. Firstly, we define the interface for the
  2. Next, we create concrete implementations of the interfaces that we have defined.
  3. Finally, we use these implementations to observe how a change in the subject also updates its dependents.
  • Observer pattern: behavioral design pattern that establishes a one-to-many dependency between objects. This means that when one object (the subject) changes its state, all its dependent objects (observers) are notified and updated automatically. This pattern is particularly useful for implementing distributed event-handling systems in event-driven software.

Key essentials of this pattern are:

  1. Subject: It is an object which holds the state and informs the observers when it updates it's state.
  2. Observer: An interface or abstract class that defines the update method, which is called when the subject’s state changes.
  3. Concrete Subject: A class that implements the Subject interface and maintains the state of interest to observers.
  4. Concrete Observer: A class that implements the Observer interface and updates its state to match the subject’s state.

Example of observer pattern:
In a stock trading application, the stock ticker acts as the subject. Whenever the price of a stock is updated, various observers—such as investors and regulatory bodies—are notified of the change. This allows them to respond to price fluctuations in real-time.

import java.util.ArrayList;
import java.util.List;

// Observer interface
interface Observer {
    void update(String stockSymbol, double stockPrice);
}

// Subject interface
interface Subject {
    void register(Observer o);
    void remove(Observer o);
    void notify();
}

// Concrete Subject
class Stock implements Subject {
    private List observers;
    private String stockSymbol;
    private double stockPrice;

    public Stock() {
        observers = new ArrayList();
    }

    public void setStock(String stockSymbol, double stockPrice) {
        this.stockSymbol = stockSymbol;
        this.stockPrice = stockPrice;
        notify();
    }

    @Override
    public void register(Observer o) {
        observers.add(o);
    }

    @Override
    public void remove(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notify() {
        for (Observer observer : observers) {
            observer.update(stockSymbol, stockPrice);
        }
    }
}

// Concrete Observer
class StockTrader implements Observer {
    private String traderName;

    public StockTrader(String traderName) {
        this.traderName = traderName;
    }

    @Override
    public void update(String stockSymbol, double stockPrice) {
        System.out.println("Trader "   traderName   " notified. Stock: "   stockSymbol   " is now $"   stockPrice);
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        Stock stock = new Stock();

        StockTrader trader1 = new StockTrader("Niharika");
        StockTrader trader2 = new StockTrader("Goulikar");

        stock.register(trader1);
        stock.register(trader2);

        stock.setStock("Niha", 9500.00);
        stock.setStock("Rika", 2800.00);
    }
}

Explanation:

  • Firstly, we define the interface for the subject which is responsible for sending updates. Similarly, we also define an interface for the observer, which is responsible for receiving updates.
  • Next, we create concrete implementations of the interfaces that we have defined.
  • Finally, we use these implementations to observe how a change in the subject also updates its dependents.
版本聲明 本文轉載於:https://dev.to/niharikaa/design-patterns-a-deep-dive-into-common-design-patterns-31b9?1如有侵犯,請聯絡[email protected]刪除
最新教學 更多>
  • 如何準備您的應用程式以處理黑色星期五的多個請求
    如何準備您的應用程式以處理黑色星期五的多個請求
    一年中最受歡迎的購物日之一是黑色星期五,此時商店的人流經常急劇增加。如果您的應用程式尚未準備好應對這種激增,則可能會導致系統過載、回應時間緩慢甚至中斷。以下是一些關鍵策略,可確保您的應用程式能夠有效地管理更高的需求。 1。對您的應用程式進行負載測試 在黑色星期五高峰之前,進行負載測試以模擬高流量場...
    程式設計 發佈於2024-11-08
  • 如何在 Python 中用逗號連接清單中的字串?
    如何在 Python 中用逗號連接清單中的字串?
    從列表中用逗號連接字符串將字符串列表映射到逗號分隔的字符串是編程中的常見任務。可以採用各種方法來實現此目標,每種方法都有自己的優點和缺點。 一種流行的方法是將 join 方法與映射函數結合使用。此方法需要建立一個中間字串,用作各個字串之間的分隔符號。例如:my_list = ['a', 'b', '...
    程式設計 發佈於2024-11-08
  • C++ 中的「long」、「long int」、「long long」和「long long int」有什麼不同?
    C++ 中的「long」、「long int」、「long long」和「long long int」有什麼不同?
    了解C 中長資料類型的細微差別當您開始從Java 過渡到C 時,您可能遇到過多功能的long 資料型,有多種形式,例如long、long long、long int 和long long int。本文旨在闡明這些數據類型之間的差異並闡明它們的預期用途。 在 C 中,long 和 long int 是...
    程式設計 發佈於2024-11-08
  • 為什麼你應該為開源付費
    為什麼你應該為開源付費
    幾乎每個開發者每天都會使用開源項目,無論是在VS Code 中編寫程式碼、使用TailwindCSS 加速開發,還是使用最受歡迎的PHP 框架Laravel 建立強大的Web 應用程式.我們不要忘記用於建立管理面板的 FilamentPHP。 這些項目不是鬼建的;而是由鬼魂建造的。它們是由人們創造...
    程式設計 發佈於2024-11-08
  • 如何找到一個資料幀中存在但另一個資料幀中不存在的行(比較 df1 和 df2)?
    如何找到一個資料幀中存在但另一個資料幀中不存在的行(比較 df1 和 df2)?
    比較資料幀:尋找中存在但另一個中不存在的行比較資料幀以識別差異對於資料品質保證和合併至關重要營運。在本例中,我們有兩個具有特定結構的資料幀(df1 和 df2),需要確定 df2 中存在但 df1 中不存在的行。 最初,嘗試使用 df1 != df2 比較資料幀,結果是錯誤。此方法僅適用於具有相同行...
    程式設計 發佈於2024-11-08
  • CSS 中的動畫
    CSS 中的動畫
    CSS中的動畫有兩個部分 - @keyframes和animation-*。 @keyframes at 規則 第一部分要求我們定義@keyframes。 這讓我們可以指定在動畫持續時間的不同點應套用的 CSS 樣式。 不同的時間點以百分比值指定。可以指定 0 到 100% 之...
    程式設計 發佈於2024-11-08
  • 使用 React 建立汽車租賃平台
    使用 React 建立汽車租賃平台
    BookCars 是一個面向供應商的汽車租賃平台,具有用於管理車隊和預訂的後端,以及用於租車的前端和行動應用程式。 透過以下解決方案,您可以透過將其託管在具有至少1GB RAM 的Docker Droplet 上,以非常低的成本建立一個針對多個供應商進行優化的完全可自訂的汽車租賃網站,並使用可操作...
    程式設計 發佈於2024-11-08
  • 模擬資料產生器:高效率軟體測試的關鍵
    模擬資料產生器:高效率軟體測試的關鍵
    模拟数据生成在软件测试和开发中发挥着至关重要的作用,使团队能够在不依赖实时数据的情况下模拟真实场景。无论您是测试新功能还是开发 API,模拟数据都有助于简化流程,确保测试一致、可靠,而无需访问生产数据库。 在本文中,我们将深入探讨模拟数据生成器是什么、为什么它们很重要、如何实现它们以及当今开发人员...
    程式設計 發佈於2024-11-08
  • 模擬請求
    模擬請求
    冷靜一點,提交者王,我不會談論 JSON-Server,但它值得留下來! 每個前端都會經歷模擬端點請求的需要,有時是因為後端還沒有完成其工作,有時是為了調試和模擬特定情況,這在日常生活中很常見。 是的,JSON-Server 令人難以置信並且使用起來非常簡單,但是幾天前我遇到了一個非常具體的問題...
    程式設計 發佈於2024-11-08
  • 如何在 PHP 中迭代遍歷和處理子目錄內的檔案?
    如何在 PHP 中迭代遍歷和處理子目錄內的檔案?
    如何在PHP中遍歷子目錄並迭代處理文件在PHP中,遍歷子目錄並迭代處理文件可以使用RecursiveDirectoryIterator和RecursiveIteratorIterator來實作。讓我們了解如何根據需要建立程式碼:// Initializing the path to the main...
    程式設計 發佈於2024-11-08
  • 癮君子 # 何時使用效果、Angular DI 功能、請求快取等
    癮君子 # 何時使用效果、Angular DI 功能、請求快取等
    ?嘿,Angular Addict 夥伴 這是 Angular Addicts Newsletter 的第 30 期,這是一本每月精選的引起我注意的 Angular 資源合集。 (這裡是第29期、28期、27期) ?發佈公告 ?Nx 19.8 更新 ...
    程式設計 發佈於2024-11-08
  • 如何吸引頂尖 Python 開發人員到你的公司
    如何吸引頂尖 Python 開發人員到你的公司
    在竞争激烈的技术领域,吸引顶级 Python 开发人员对于任何希望利用这种多功能编程语言的力量的组织来说至关重要。随着 Python 继续在 Web 开发、数据科学和机器学习等领域占据主导地位,对熟练 Python 开发人员的需求空前高涨。如果您想聘请能够推动创新并为您的项目做出有意义贡献的 Pyt...
    程式設計 發佈於2024-11-08
  • **JavaScript 中 `location = URL` 和 `location.href = URL` 有什麼不同?
    **JavaScript 中 `location = URL` 和 `location.href = URL` 有什麼不同?
    JavaScript:「location = URL」和「location.href = URL」的差異在JavaScript 中,操作網頁的URL 可以透過以下方式實現兩種類似的方法:直接設定location 屬性或設定location.href 屬性。雖然功能可能看起來相同,但這兩種方法之間存在...
    程式設計 發佈於2024-11-08
  • 如何有效地將PHP變數插入字串?
    如何有效地將PHP變數插入字串?
    將PHP 變數插入字串將PHP 變數合併到字串中時,注意語法以確保所需的輸出為至關重要的獲得。為了解決這個問題,讓我們檢查一下提示中顯示的程式碼:目標是包含$ width 變數在寬度樣式屬性中,並確保其後跟“px”。不幸的是,嘗試用空格分隔變數和“px”或將它們連接在一起會導致錯誤。 解決方案1:串...
    程式設計 發佈於2024-11-08
  • 了解 JavaScript 中底線 (`_`) 的使用
    了解 JavaScript 中底線 (`_`) 的使用
    在 JavaScript 中編碼時,您可能會遇到用作變數名稱的下劃線字元 (_),特別是在函數參數中。雖然乍看之下似乎很不尋常,但由於各種原因,這種做法在開發人員中很常見。在這篇文章中,我們將探討底線代表什麼,為什麼要使用它,以及它在現實範例中的顯示方式,例如 coalesceES6 函數。 ...
    程式設計 發佈於2024-11-08

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3