ホームページ  >  記事  >  Java  >  デザインパターン : 一般的なデザインパターンの詳細

デザインパターン : 一般的なデザインパターンの詳細

PHPz
PHPzオリジナル
2024-09-06 06:00:36714ブラウズ

DESIGN PATTERNS : A Deep Dive into Common Design Patterns

デザインパターンとは何ですか?

デザイン パターンは、複雑な問題に対する解決策です。デザイン パターンとは、特定のデザイン上の問題を解決する方法でクラスとインターフェイスを作成することです。通常、システムを設計しているときにいくつかの問題に遭遇しますが、それらの問題には一連の設計パターンがあります。デザイン パターンは通常、クラス、インターフェイス、およびそれらのクラス間の関係を含むテンプレートです。

デザインパターンの種類:

創造的なデザインパターン:

これらのタイプのパターンは、特定の状況と互換性のある方法でオブジェクトの作成を処理します。
作成レベルでは、システムの特定の部分を独立して作成するか、一緒に構成して、柔軟性と互換性を確保する方法を決定できます。
このカテゴリに分類されるデザイン パターンのリストは次のとおりです:

  • シングルトン: この設計パターンでは、インスタンスが 1 つだけあり、そのインスタンスがアプリケーション全体で使用されます。

シングルトン設計パターンの要点:

  1. プライベート コンストラクター: クラスのインスタンスが 1 つだけ作成されるようにしたいため、コンストラクターをプライベートとしてマークすることは非常に重要です。
  2. プライベート静的インスタンス: クラス メモリ内にオブジェクトのインスタンスが 1 つだけあることを保証したいため、プライベート アクセス修飾子を使用します。
  3. パブリック静的メソッド (アクセス): これは、単一インスタンスへのグローバル アクセス ポイントです。このメソッドは基本的に、インスタンスが存在しない場合はインスタンスを作成し、すでに存在する場合は同じインスタンスを返します。

シングルトン設計パターンの例

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: Builder パターンでは、オブジェクトを段階的に作成する方法を定義します。このパターンは柔軟性も提供し、同じオブジェクトの異なるバージョンを同じ構築プロセスを使用して作成できるようにします。

ビルダー パターンの重要な要素:

  1. 製品: これは構築中の複雑なオブジェクトです。
  2. Builder Interface: 製品のさまざまな部分を作成するメソッドを定義します。これらのメソッドは通常、メソッド チェーンを可能にするためにビルダー オブジェクト自体を返します。
  3. コンクリート ビルダー:ビルダー インターフェイスを実装し、製品のパーツを作成するための特定の実装を提供します。

ビルダーパターンの例:
この例では、ビルダー デザイン パターンを使用して、材料を段階的に追加してチョコレート スプレッド パンを作成する方法を示します。

// 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);
    }
}

  • ファクトリ メソッド: ファクトリ メソッド パターンでは、オブジェクトを作成する方法を定義しますが、作成されるオブジェクトの特定のタイプをサブクラスが決定できるようにします。

ファクトリー パターンの重要な要素:

  1. 製品インターフェース: すべての製品の共通インターフェースを定義します。
  2. 具体的な製品: 製品インターフェイスを実装します。
  3. 作成者: ファクトリ メソッドを宣言します。
  4. コンクリート作成者: さまざまなコンクリート製品を返すファクトリ メソッドを実装します。
// 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.
    }
}

構造設計パターン

この設計パターンは、クラスとオブジェクトがどのように構成されてより大きな構造を形成するかに主に焦点を当てています。これらは、オブジェクトとクラス間の組織と関係に焦点を当て、構造を簡素化し、柔軟性を高め、保守性を促進します。

  • アダプター パターン: このパターンでは、互換性のないインターフェイスを持つオブジェクトが連携できるようにします。これは、互換性のない 2 つのインターフェイス間のブリッジとして機能し、既存のコードを変更せずに通信できるようにします。

アダプター パターンの重要な要素:

  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 < employees.length;
    }

    @Override
    public Object next() {
        return hasNext() ? employees[position++].Name : null;
    }
}

// Client Code
public class Main {
    public static void main(String[] args) {
        // Creating employee array
        Employee[] employees = {
            new Employee("John", 28, "Engineering", 101),
            new Employee("Jane", 32, "Marketing", 102),
            new Employee("Tom", 25, "Sales", 103)
        };

        // Creating employee collection and iterator
        EmployeeCollection employeeCollection = new EmployeeCollection(employees);
        Iterator iterator = employeeCollection.createIterator();

        // Iterating through employees
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

  • 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<Observer> 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.

以上がデザインパターン : 一般的なデザインパターンの詳細の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。