近十万字详解23种设计模式(含真实项目实战)

设计原则

  1. 单一责任原则(Single Responsibility Principle):一个类应该只有一个引起变化的原因,即一个类应该只负责一项职责。
  2. 开闭原则(Open-Closed Principle):软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。意味着我们应该通过扩展来实现新的功能,而不是修改已有的代码。
  3. 里氏替换原则(Liskov Substitution Principle):子类对象可以替换父类对象出现在程序中,而程序的行为不会受到影响。即子类应该能够完全替代父类并保持正确性。
  4. 依赖倒置原则(Dependency Inversion Principle):高层模块不应该依赖于低层模块,两者都应该依赖于抽象。抽象不应该依赖于具体实现细节,而具体实现细节应该依赖于抽象。
  5. 接口隔离原则(Interface Segregation Principle):客户端不应该依赖它不需要的接口。一个类不应该被迫实现它用不到的接口方法,应该将接口拆分为更小和更具体的接口。
  6. 合成/聚合复用原则(Composition/Aggregation Reuse Principle):尽量使用对象组合和聚合,而不是继承关系,以达到代码复用的目的。这种方式更加灵活,减少了类之间的耦合。
  7. 迪米特法则(Law of Demeter):一个对象应该与其他对象保持最小的相互依赖关系,尽量减少对象之间的交互。一个类应该尽可能少地了解其他类的内部结构。

设计模式

01单例模式

什么是单例模式

单例模式是一种创建型设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点来访问该实例。‘

如何实现一个单例

  1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  2. 考虑对象创建时的线程安全问题;
  3. 考虑是否支持延迟加载;
  4. 考虑 getInstance() 性能是否高(是否加锁)

饿汉模式

在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。不过,这样的实现方式不支持延迟加载。以电商系统中的订单管理为例实现

public class OrderManager {
    private static final OrderManager instance = new OrderManager();
    private List<Order> orders;

    private OrderManager() {
        orders = new ArrayList<>();
    }

    public static OrderManager getInstance() {
        return instance;
    }

    public void addOrder(Order order) {
        orders.add(order);
    }

    public void removeOrder(Order order) {
        orders.remove(order);
    }

    public void displayOrders() {
        System.out.println("Order List:");
        for (Order order : orders) {
            System.out.println("- " + order.getId());
        }
    }
}

在上述示例中,我们创建了一个 OrderManager 类,用于管理电商系统中的订单。该类采用饿汉式的单例模式实现,确保整个系统中只有一个订单管理实例。

OrderManager 类具有私有的静态成员变量 instance,它在类加载时就被初始化为单例实例。构造函数被设置为私有,以防止外部直接实例化该类。

getInstance() 方法是获取订单管理实例的入口。由于采用饿汉式实现,实例在类加载时就已经创建好了,因此该方法直接返回现有的实例。

订单管理类提供了 addOrder()removeOrder()displayOrders() 方法,用于添加订单、删除订单和显示订单列表。

懒汉模式

有饿汉式,对应地,就有懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载。

public class ProductManager {
    private static ProductManager instance;
    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public static synchronized ProductManager getInstance() {
        if (instance == null) {
            instance = new ProductManager();
        }
        return instance;
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }
}

我们给 getInstance() 这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低

等问题,会导致性能瓶颈,这种实现方式就不可取了。

双重检测

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。

public class ProductManager {
    private static volatile ProductManager instance;
    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public static ProductManager getInstance() {
        if (instance == null) {
            synchronized (ProductManager.class) {
                if (instance == null) {
                    instance = new ProductManager();
                }
            }
        }
        return instance;
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }
}

在上述示例中,我们对 instance 变量使用了 volatile 关键字,以确保多线程环境下的可见性。

getInstance() 方法中,我们首先检查 instance 是否为空,如果为空,才进入同步块。在同步块内部,再次检查 instance 是否为空,这是为了防止多个线程同时通过第一个检查,然后都进入同步块创建实例。如果在同步块内部,我们发现 instance 仍然为空,才会创建新的实例。

这种双重检验的方式可以在保证懒加载的同时,提供了更好的性能,避免了每次获取实例时都进行同步操作。

静态内部类

public class ProductManager {
    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public static ProductManager getInstance() {
        return SingletonHolder.instance;
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }

    private static class SingletonHolder {
        private static final ProductManager instance = new ProductManager();
    }
}

在上面的示例中,我们通过 ProductManager.getInstance() 获取商品管理器实例,并使用 addProduct() 方法添加商品。最后,我们调用 displayProducts() 方法显示商品列表。

通过静态内部类的单例模式,我们可以确保在整个电商系统中只有一个商品管理器实例,无论在何处获取该实例,都将获得相同的实例。这样可以确保商品管理的一致性和可靠性,并且不会在类加载时就创建实例,实现了懒加载的效果。

枚举

Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性

public enum ProductManager {
    INSTANCE;

    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }
}

扩展-如何实现线程唯一

在单例模式中,"线程唯一"和"进程唯一"是两个概念,描述了单例实例在不同线程和不同进程中的唯一性。

  1. 线程唯一(Thread-Safe):当一个单例类的实例在多线程环境下能够保持唯一,每个线程都共享同一个实例,且线程之间的操作不会引起冲突。在多线程环境下,线程唯一的单例模式需要考虑线程安全性,确保在并发访问时不会导致竞态条件或不一致的状态。
  2. 进程唯一(Process-Safe):当一个单例类的实例在多个进程中能够保持唯一,每个进程都共享同一个实例。在多进程环境下,进程唯一的单例模式需要考虑跨进程的通信和同步机制,确保在多个进程间只有一个实例存在。可以理解QQ和微信2个进程都保证唯一。
public class ProductManager {
    private static final ThreadLocal<ProductManager> INSTANCE = new ThreadLocal<ProductManager>() {
        @Override
        protected ProductManager initialValue() {
            return new ProductManager();
        }
    };

    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public static ProductManager getInstance() {
        return INSTANCE.get();
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }
}

在上述示例中,我们使用了一个 ThreadLocal 变量 INSTANCE 来存储每个线程对应的商品管理器实例。ThreadLocal 提供了 initialValue() 方法,用于初始化每个线程的实例。

getInstance() 方法通过 INSTANCE.get() 获取当前线程的商品管理器实例。每个线程都将获得自己的实例,并与其他线程隔离。

扩展—如何实现集群唯一

"集群唯一"指的是在一个分布式集群环境中,确保单例实例在整个集群中只存在一个,并且能够被所有节点共享和访问。

在集群环境中,可能存在多个应用节点(服务器),每个节点都是独立运行的进程,它们可以通过网络进行通信和协作。当需要保证某个单例对象在整个集群中只有一个实例时,就需要实现集群唯一的单例模式。

实现集群唯一的单例模式需要解决以下两个问题:

  1. 单例实例的创建和管理:在集群中,需要通过某种机制来确保只有一个节点创建单例实例,并将其共享给其他节点。常用的方法是使用分布式缓存,如Redis,将单例对象存储在缓存中,所有节点都可以从缓存中获取实例。
  2. 跨节点通信和同步:当一个节点创建了单例实例后,需要通知其他节点,让它们知道单例实例已经创建并可以使用。这需要使用分布式锁、发布-订阅等机制来进行节点间的通信和同步。

要实现集群唯一的单例模式,我们可以结合使用分布式缓存和锁机制,例如使用Redis作为分布式缓存,并使用分布式锁来保证在集群环境中只有一个商品管理器实例。让我们将其应用于电商系统中的商品管理器。

public class ProductManager {
    private static final String CACHE_KEY = "product_manager_instance";
    private List<Product> products;

    private ProductManager() {
        products = new ArrayList<>();
    }

    public static ProductManager getInstance() {
        // Try to get the instance from the cache
        ProductManager instance = getCachedInstance();
        if (instance != null) {
            return instance;
        }

        // If the instance doesn't exist in the cache, acquire a distributed lock
        if (acquireDistributedLock()) {
            try {
                // Check again if the instance is already created by another thread
                instance = getCachedInstance();
                if (instance != null) {
                    return instance;
                }

                // Create a new instance and cache it
                instance = new ProductManager();
                cacheInstance(instance);
                return instance;
            } finally {
                // Release the distributed lock
                releaseDistributedLock();
            }
        }

        // If failed to acquire the distributed lock, return null or throw an exception
        return null;
    }

    private static ProductManager getCachedInstance() {
        // TODO: Implement cache retrieval logic using Redis or other distributed cache
        // Return the cached instance if it exists, otherwise return null
        return null;
    }

    private static void cacheInstance(ProductManager instance) {
        // TODO: Implement cache storing logic using Redis or other distributed cache
        // Store the instance in the cache for future retrieval
    }

    private static boolean acquireDistributedLock() {
        // TODO: Implement distributed lock acquisition logic using Redis or other distributed lock mechanism
        // Return true if the lock is successfully acquired, otherwise return false
        return false;
    }

    private static void releaseDistributedLock() {
        // TODO: Implement distributed lock release logic using Redis or other distributed lock mechanism
        // Release the acquired lock
    }

    public void addProduct(Product product) {
        products.add(product);
    }

    public void removeProduct(Product product) {
        products.remove(product);
    }

    public void displayProducts() {
        System.out.println("Product List:");
        for (Product product : products) {
            System.out.println("- " + product.getName());
        }
    }
}

在上述示例中,我们使用了分布式缓存和锁机制来实现集群唯一的单例模式。getInstance() 方法首先尝试从缓存中获取商品管理器的实例,如果存在则直接返回。如果缓存中不存在实例,则尝试获取分布式锁。如果成功获取锁,则检查缓存中是否已经存在实例,如果不存在则创建一个新的实例,并将其存储在缓存中。最后释放分布式锁。如果无法获取锁,则返回null或抛出异常。

在具体的实现中,需要根据实际情况使用分布式缓存(如Redis)和分布式锁(如Redis的分布式锁或Zookeeper的分布式锁)来完成相关逻辑。示例中的方法 `getCachedInstance

02工厂模式

什么是工厂模式

工厂设计模式是一种常用的创建型设计模式,它通过封装对象的创建过程,使得代码更加灵活、可扩展和易于维护。在电商系统中,工厂设计模式可以应用于各个业务模块,实现对象的创建和管理,提高系统的可维护性和可测试性。本文将详细介绍工厂设计模式在电商系统中的应用,以订单处理为例进行阐述。

电商系统订单创建实现工厂模式

  1. 定义订单接口 首先,我们需要定义一个订单接口,作为所有订单类型的公共接口。该接口包含了订单的基本操作和属性。
public interface Order {
    void process();
    // 其他订单操作方法
}

  1. 创建订单实现类 接下来,我们创建不同类型的订单实现类,它们都实现了订单接口,具体实现各自的业务逻辑。
 public class NormalOrder implements Order {
    @Override
    public void process() {
        // 处理普通订单的逻辑
        System.out.println("Processing normal order...");
    }
}

public class GroupOrder implements Order {
    @Override
    public void process() {
        // 处理团购订单的逻辑
        System.out.println("Processing group order...");
    }
}

public class DiscountOrder implements Order {
    @Override
    public void process() {
        // 处理优惠订单的逻辑
        System.out.println("Processing discount order...");
    }
}

  1. 创建订单工厂类 订单工厂类负责根据不同的订单类型创建相应的订单实例。它将根据传入的订单类型参数,选择合适的订单实现类进行实例化,并返回订单接口的实例
public class OrderFactory {
    public Order createOrder(String orderType) {
        if (orderType.equalsIgnoreCase("Normal")) {
            return new NormalOrder();
        } else if (orderType.equalsIgnoreCase("Group")) {
            return new GroupOrder();
        } else if (orderType.equalsIgnoreCase("Discount")) {
            return new DiscountOrder();
        } else {
            throw new IllegalArgumentException("Invalid order type: " + orderType);
        }
    }
}

  1. 使用订单工厂创建订单 在订单处理的代码中,我们可以使用订单工厂来创建不同类型的订单实例,实现业务的解耦和灵活性。
public class OrderProcessor {
    private OrderFactory orderFactory;

    public OrderProcessor(OrderFactory orderFactory) {
        this.orderFactory = orderFactory;
    }

    public void processOrder(String orderType) {
      Order order = orderFactory.createOrder(orderType);
      order.process();
    }
}
       

在上述代码中,OrderProcessor类负责处理订单,它通过依赖注入OrderFactory来创建不同类型的订单实例,然后调用订单的process方法进行订单处理。

  1. 使用
public class Main {
    public static void main(String[] args) {
        OrderFactory orderFactory = new OrderFactory();
        OrderProcessor orderProcessor = new OrderProcessor(orderFactory);
        
        // 创建普通订单
        orderProcessor.processOrder("Normal");

        // 创建团购订单
        orderProcessor.processOrder("Group");

        // 创建优惠订单
        orderProcessor.processOrder("Discount");
    }
}

03构建者模式

什么是构建者

构建者模式(Builder Pattern)是一种对象创建型设计模式,旨在通过将构建过程与表示分离,使得同样的构建过程可以创建不同的表示形式。它允许我们按照特定的步骤构建复杂对象,而无需关注具体的构建细节。通过使用构建者模式,我们可以灵活地组合和构建不同的对象。

  1. 构建者模式包含以下主要角色:
  • 产品(Product):表示被构建的复杂对象。通常包含多个属性和方法。
  • 抽象构建者(Builder):定义了构建产品对象的抽象接口。通常包含设置产品属性的方法和获取构建结果的方法。
  • 具体构建者(Concrete Builder):实现了抽象构建者接口,具体实现了构建产品对象的方法。它负责实现构建过程中的每一步细节。
  • 指导者(Director):负责调用具体构建者来构建产品对象。它不直接与产品交互,而是通过抽象构建者来构建产品。

使用场景

  • 当需要构建的对象具有复杂的内部结构,且构建过程需要多个步骤时,可以使用构建者模式将构建过程拆分成多个独立的步骤。
  • 当需要创建不同表示形式的对象时,可以使用构建者模式。通过使用不同的具体构建者,可以构建出不同属性和行为的对象。
  • 当需要灵活地组合和构建对象时,可以使用构建者模式。通过定义不同的具体构建者和指导者,可以按需组合构建对象。

电商系统中的应用

在电商系统中,我们经常需要创建复杂的对象,如商品对象。商品对象可能包含多个属性,如商品名称、价格、描述、库存等。构建商品对象的过程可能涉及多个步骤,如设置商品名称、设置商品价格、设置商品描述等。这时候,可以使用构建者模式来实现商品对象的构建。

public class Product {
    private String name;
    private double price;
    private String description;
    private int stock;

    private Product(Builder builder) {
        this.name = builder.name;
        this.price = builder.price;
        this.description = builder.description;
        this.stock = builder.stock;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    public String getDescription() {
        return description;
    }

    public int getStock() {
        return stock;
    }

    public static class Builder {
        private String name;
        private double price;
        private String description;
        private int stock;

        public Builder(String name, double price) {
            this.name = name;
            this.price = price;
        }

        public Builder setDescription(String description) {
            this.description = description;
            return this;
        }

        public Builder setStock(int stock) {
            this.stock = stock;
            return this;
        }

        public Product build() {
            return new Product(this);
        }
    }
}


在上述代码中,Product类是被构建的复杂对象,它包含了多个属性和对应的getter方法。内部定义了一个静态内部类Builder,用于构建Product对象。Builder类具有和Product类相同的属性,并提供了设置属性的方法。最后,通过build()方法返回构建好的Product对象。

public class Main {
    public static void main(String[] args) {
        Product product = new Product.Builder("iPhone", 999.99)
                .setDescription("The latest iPhone model")
                .setStock(100)
                .build();
                
        System.out.println("Product Name: " + product.getName());
        System.out.println("Price: $" + product.getPrice());
        System.out.println("Description: " + product.getDescription());
        System.out.println("Stock: " + product.getStock());
    }
}

通过上述示例代码,我们可以使用构建者模式创建商品对象,通过链式调用设置商品的属性。这样可以避免构造函数参数过多的问题,并且使得代码更加清晰和可读

04_原型模式

什么是原型模式?

原型模式是一种创建型设计模式,它通过复制现有对象来创建新对象,而不是通过构造函数进行创建。在原型模式中,我们首先创建一个原型对象,然后通过复制原型来创建新的对象。这种方式不仅避免了重复创建相似对象的麻烦,还可以提高对象的创建效率。

原型模式的应用场景

原型模式适用于以下情况:

  1. 对象的创建成本高昂:如果创建一个对象需要复杂的计算或者与外部资源的交互,那么使用原型模式可以避免重复执行这些操作,提高性能。
  2. 需要避免构造函数的复杂性:当一个对象的构造函数参数较多或者构造过程比较复杂时,可以使用原型模式来避免构造函数的复杂性。
  3. 需要保持对象状态的一致性:有时我们需要创建一系列状态相同或相似的对象,使用原型模式可以保持这些对象的状态一致。

原型模式的实现方式

在实现原型模式时,有两种常见的方式:浅拷贝和深拷贝。

浅拷贝(Shallow Copy):浅拷贝会复制对象的所有成员变量,包括基本类型和引用类型。但对于引用类型的成员变量,只复制引用地址,而不复制引用指向的对象本身。这意味着原型对象和复制对象会共享同一个引用对象,对引用对象的修改会影响到原型对象和复制对象。

深拷贝(Deep Copy):深拷贝会复制对象的所有成员变量,包括基本类型和引用类型,并且对于引用类型的成员变量,会递归地进行复制,复制出一个全新的对象。这样原型对象和复制对象就完全独立,互不影响。

下面是以订单生成为例,实现一个基于原型设计模式的示例

浅拷贝

首先,我们定义一个订单类 Order,它包含了订单的一些基本信息,例如订单号、商品列表和收货地址等。

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

class Order implements Cloneable {
    private String orderId;
    private List<String> productList;
    private String shippingAddress;

    public Order(String orderId, List<String> productList, String shippingAddress) {
        this.orderId = orderId;
        this.productList = productList;
        this.shippingAddress = shippingAddress;
    }

    public String getOrderId() {
        return orderId;
    }

    public List<String> getProductList() {
        return productList;
    }

    public String getShippingAddress() {
        return shippingAddress;
    }

    @Override
    public Order clone() throws CloneNotSupportedException {
        return new Order(orderId, productList, shippingAddress);
    }
}

在上面的示例中,Order类实现了 Cloneable 接口,并重写了 clone() 方法。在 clone() 方法中,我们使用浅拷贝的方式复制了订单对象,并创建了一个新的订单对象。

接下来,我们可以使用原型对象生成新的订单。下面是一个示例:

import java.util.Arrays;

public class OrderPrototypeExample {
    public static void main(String[] args) {
        // 创建一个原型订单对象
        Order prototypeOrder = new Order("123456", Arrays.asList("Product A", "Product B"), "Shipping Address");

        try {
            // 克隆原型订单对象生成新的订单
            Order newOrder = prototypeOrder.clone();

            // 修改新订单的一些属性
            newOrder.setOrderId("789012");
            newOrder.getProductList().add("Product C");
            newOrder.setShippingAddress("New Shipping Address");

            // 输出原型订单和新订单的信息
            System.out.println("Prototype Order: " + prototypeOrder.getOrderId() + ", " +
                    prototypeOrder.getProductList() + ", " + prototypeOrder.getShippingAddress());
            System.out.println("New Order: " + newOrder.getOrderId() + ", " +
                    newOrder.getProductList() + ", " + newOrder.getShippingAddress());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

深拷贝

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

class Order implements Cloneable {
    private String orderId;
    private List<String> productList;
    private String shippingAddress;

    public Order(String orderId, List<String> productList, String shippingAddress) {
        this.orderId = orderId;
        this.productList = productList;
        this.shippingAddress = shippingAddress;
    }

    public String getOrderId() {
        return orderId;
    }

    public List<String> getProductList() {
        return productList;
    }

    public String getShippingAddress() {
        return shippingAddress;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public void setProductList(List<String> productList) {
        this.productList = productList;
    }

    public void setShippingAddress(String shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    @Override
    public Order clone() throws CloneNotSupportedException {
        // 创建新的订单对象
        Order clonedOrder = (Order) super.clone();

        // 深拷贝商品列表
        List<String> clonedProductList = new ArrayList<>();
        for (String product : productList) {
            clonedProductList.add(product);
        }
        clonedOrder.setProductList(clonedProductList);

        return clonedOrder;
    }
}

接下来,我们可以使用原型对象生成新的订单。下面是一个示例:

import java.util.Arrays;

public class OrderPrototypeExample {
    public static void main(String[] args) {
        // 创建一个原型订单对象
        Order prototypeOrder = new Order("123456", Arrays.asList("Product A", "Product B"), "Shipping Address");

        try {
            // 克隆原型订单对象生成新的订单
            Order newOrder = prototypeOrder.clone();

            // 修改新订单的一些属性
            newOrder.setOrderId("789012");
            newOrder.getProductList().add("Product C");
            newOrder.setShippingAddress("New Shipping Address");

            // 输出原型订单和新订单的信息
            System.out.println("Prototype Order: " + prototypeOrder.getOrderId() + ", " +
                    prototypeOrder.getProductList() + ", " + prototypeOrder.getShippingAddress());
            System.out.println("New Order: " + newOrder.getOrderId() + ", " +
                    newOrder.getProductList() + ", " + newOrder.getShippingAddress());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

我们重写了 clone() 方法,实现了深拷贝。在深拷贝中,除了复制对象的引用外,还会复制对象本身的属性,以确保新对象与原对象完全独立。

clone() 方法中,我们首先调用 super.clone() 方法创建一个新的订单对象 clonedOrder。然后,我们通过遍历原订单对象的商品列表,逐个复制商品到新的列表中,最后设置给新的订单对象。

这样,通过深拷贝,新的订单对象将拥有独立的商品列表,而不会与原订单对象共享相同的引用。

请注意,在深拷贝中,如果订单类中包含其他引用类型的属性,我们需要对这些属性也进行深拷贝,确保新对象中的所有属性都是独立的。

通过使用原型模式,我们可以避免重复创建相似对象的开销,提高代码的效率和性能。同时,原型模式还能保持对象状态的一致性,使得对象的复制更加灵活和可控。

05代理模式

什么代理模式

代理模式是一种结构型设计模式,它允许我们创建一个代理对象,控制对实际对象的访问。代理对象通常充当客户端和实际对象之间的中介,隐藏了实际对象的实现细节,并提供了额外的功能。代理模式可以分为静态代理和动态代理两种形式。

  1. 静态代理:静态代理是在编译时就已经确定代理关系的代理形式。在电商系统中,我们可以通过实现代理接口来创建代理类,代理类中持有实际对象的引用,并在方法调用前后添加额外的逻辑,如权限验证、日志记录等。静态代理的缺点是每个被代理类都需要创建一个对应的代理类,导致代码冗余。
  2. 动态代理:动态代理是在运行时根据需要创建代理对象的代理形式。在Java中,我们可以使用Java的反射机制和动态代理类来实现动态代理。通过动态代理,我们可以在运行时动态地创建代理对象,无需提前编写代理类。在电商系统中,动态代理可以用于实现横切关注点的处理、延迟加载、性能优化等功能。

静态代理

// 定义一个权限接口
public interface OrderService {
    void createOrder();
}

// 实际的订单服务类
public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder() {
        System.out.println("创建订单");
    }
}

// 权限代理类
public class OrderServiceProxy implements OrderService {
    private OrderService orderService;

    public OrderServiceProxy(OrderService orderService) {
        this.orderService = orderService;
    }

    @Override
    public void createOrder() {
        // 权限验证逻辑
        if (checkUserPermission()) {
            orderService.createOrder();
        } else {
            throw new SecurityException("无权限执行该操作");
        }
    }

    private boolean checkUserPermission() {
        // 权限验证逻辑实现
        // ...
        return true; // 假设权限验证通过
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl(); // 创建实际的订单服务对象
        OrderServiceProxy orderServiceProxy = new OrderServiceProxy(orderService); // 创建订单服务代理对象

        orderServiceProxy.createOrder(); // 调用代理对象的方法
    }
}

在上述代码中,我们首先定义了一个权限接口OrderService,包含了创建订单的方法createOrder()。然后,我们实现了实际的订单服务类OrderServiceImpl,其中实现了订单的创建逻辑。

接下来,我们创建了一个权限代理类OrderServiceProxy,它实现了OrderService接口,并持有一个实际订单服务对象的引用。在createOrder()方法中,我们添加了权限验证的逻辑。只有当用户拥有足够权限时,才会调用实际订单服务对象的createOrder()方法,否则会抛出安全异常。

在主函数中,我们先创建了实际的订单服务对象orderService,然后将其传入权限代理类OrderServiceProxy的构造函数中创建了代理对象orderServiceProxy。最后,我们通过调用代理对象的createOrder()方法来创建订单。

通过使用代理模式,我们可以将权限验证这一横切关注点与核心业务逻辑分离,提高了系统的可扩展性和安全性。同时,这种设计模式使得代码结构更加清晰,易于维护和扩展。

动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个订单服务接口
public interface OrderService {
    void createOrder();
    void updateOrder();
}

// 实际的订单服务类
public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder() {
        System.out.println("创建订单");
    }

    @Override
    public void updateOrder() {
        System.out.println("更新订单");
    }
}

// 日志代理类
public class LoggingProxy implements InvocationHandler {
    private Object target;

    public LoggingProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用方法:" + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("方法调用完成");
        return result;
    }
}

// 使用示例
public class Main {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceImpl(); // 创建实际的订单服务对象

        // 创建动态代理对象
        OrderService loggingProxy = (OrderService) Proxy.newProxyInstance(
                orderService.getClass().getClassLoader(),
                orderService.getClass().getInterfaces(),
                new LoggingProxy(orderService)
        );

        loggingProxy.createOrder(); // 调用代理对象的方法
        loggingProxy.updateOrder();
    }
}

在上述代码中,我们首先定义了一个订单服务接口OrderService,其中包含了创建订单和更新订单的方法。

接下来,我们实现了实际的订单服务类OrderServiceImpl,其中实现了创建订单和更新订单的具体逻辑。

然后,我们创建了一个日志代理类LoggingProxy,它实现了InvocationHandler接口。在invoke()方法中,我们在方法调用前输出方法名,然后使用反射调用实际订单服务对象的对应方法,最后在方法调用完成后输出方法调用完成的信息。

在主函数中,我们创建了实际的订单服务对象orderService,然后通过调用Proxy.newProxyInstance()方法创建了一个动态代理对象loggingProxy。该方法接收目标对象的类加载器、目标对象实现的接口和代理对象的调用处理器作为参数。最后,我们可以通过调用代理对象的方法来实现日志记录功能。

06桥接模式

什么是桥接模式

桥接模式(Bridge Pattern)是一种软件设计模式,它通过将抽象部分和实现部分分离,使它们可以独立地变化。桥接模式的核心思想是将继承关系转化为关联关系,以便于系统的扩展和维护。

在桥接模式中,抽象部分指的是一个抽象类或接口,定义了具体业务逻辑的高层次抽象,而实现部分指的是实现了具体细节的类或接口。通过将抽象部分和实现部分分离,我们可以在运行时动态地选择具体的实现,并且可以独立地对抽象部分和实现部分进行扩展。

桥接模式的主要优点包括:

  1. 解耦抽象和实现:桥接模式将抽象部分和实现部分分离,使它们可以独立地变化,减少它们之间的依赖关系。
  2. 扩展性强:通过桥接模式,可以方便地扩展和变化抽象部分和实现部分,不会影响到彼此的变化。
  3. 提高系统灵活性:桥接模式使得抽象部分和实现部分可以独立地进行修改,从而提高系统的灵活性和可维护性。

JDBC 驱动的桥接模式

Class.forName("com.mysql.jdbc.Driver");//加载及注册JDBC驱动程序
String url = "jdbc:mysql://localhost:3306/sample_db?user=root&password=your_pas 
Connection con = DriverManager.getConnection(url); 
Statement stmt = con.createStatement(); 
String query = "select * from test"; 
esultSet rs=stmt.executeQuery(query);
while(rs.next()) { 
  rs.getString(1); 
  rs.getInt(2); 
}

如果我们想要把 MySQL 数据库换成 Oracle 数据库,只需要把第一行代码中的com.mysql.jdbc.Driver 换成 oracle.jdbc.driver.OracleDriver 就可以了。那如此优雅的数据库切换是如何实现的呢?

我们先从 com.mysql.jdbc.Driver 这个类的代码看起。

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

结合 com.mysql.jdbc.Driver 的代码实现,我们可以发现,当执Class.forName(“com.mysql.jdbc.Driver”) 这条语句的时候,实际上是做了两件事情。第一件事情是要求 JVM 查找并加载指定的 Driver 类,第二件事情是执行该类的静态代码,也就是将 MySQL Driver 注册到 DriverManager 类中。现在,我们再来看一下,DriverManager 类是干什么用的。具体的代码如下所示。当我们把具体的 Driver 实现类(比如,com.mysql.jdbc.Driver)注册到 DriverManager 之后,后续所有对 JDBC 接口的调用,都会委派到对具的 Driver 实现类来执行。而 Driver 实类都实现了相同的接口(java.sql.Driver ),这也是可以灵活切换 Driver 的原因。

桥接模式的定义是“将抽象和实现解耦,让它们可以独立变化”。那弄懂定义中“抽象”和“实现”两个概念,就是理解桥接模式的关键。那在 JDBC 这个例子中,什么是“抽象”?什么是“实现”呢?实际上,JDBC 本身就相当于“抽象”。注意,这里所说的“抽象”,指的并非“抽象类”或“接口”,而是跟具体的数据库无关的、被抽象出来的一套“类库”。具体的Driver(比如,com.mysql.jdbc.Driver)就相当于“实现”。注意,这里所说的“实现”,也并非指“接口的实现类”,而是跟具体数据库相关的一套“类库”。JDBC 和Driver 独立开发通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。

07装饰模式

什么是装饰模式

装饰模式是一种常用的设计模式,通过动态地为对象添加额外的功能,而无需修改其原始类结构。本文将深入探讨装饰模式的核心概念、优点和应用场景,并通过实例演示如何在Java中实现装饰模式。

核心概念

  1. 抽象构件(Component):定义对象的基本接口,可以是一个抽象类或接口。在装饰模式中,抽象构件既可以表示原始对象,也可以表示装饰器。
  2. 具体构件(ConcreteComponent):实现抽象构件接口的具体类,是被装饰的原始对象。
  3. 装饰器(Decorator):实现抽象构件接口,并持有一个抽象构件对象的引用。装饰器可以在调用原始对象的方法前后添加额外的功能。
  4. 具体装饰器(ConcreteDecorator):扩展装饰器类的功能,可以通过在原始对象的方法前后添加额外的行为来实现。

优点

  1. 扩展性强:装饰模式通过动态地组合对象,可以灵活地扩展对象的功能,而无需修改现有代码。
  2. 遵循开闭原则:装饰模式允许在不修改现有代码的情况下添加新的功能,符合开闭原则。
  3. 可以多次装饰:由于装饰器和抽象构件都实现了相同的接口,因此可以通过多次装饰来添加多个不同的功能。

应用场景

  1. 需要动态地为对象添加功能,而不影响其原始类结构。
  2. 需要在不改变现有代码的情况下,对对象的行为进行扩展。
  3. 需要在运行时动态地组合和配置对象的功能。

示例演示

我们以电商系统中的商品促销功能为例进行,在电商系统中,我们经常需要对商品进行促销活动,如打折、满减等。我们可以使用装饰模式来实现这些促销活动的动态添加和组合。

首先,定义一个抽象的商品接口:

public interface Product {
    String getName();
    double getPrice();
}

接下来,实现具体的商品类:

public class ConcreteProduct implements Product {
    private String name;
    private double price;

    public ConcreteProduct(String name, double price) {
        this.name = name;
        this.price = price;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getPrice() {
        return price;
    }
}

然后,定义一个抽象的促销装饰器类:

public abstract class PromotionDecorator implements Product {
    protected Product product;

    public PromotionDecorator(Product product) {
        this.product = product;
    }

    @Override
    public String getName() {
        return product.getName();
    }

    @Override
    public double getPrice() {
        return product.getPrice();
    }
}

接下来,实现具体的促销装饰器类,例如打折装饰器和满减装饰器:

public class DiscountDecorator extends PromotionDecorator {
    private double discount;

    public DiscountDecorator(Product product, double discount) {
        super(product);
        this.discount = discount;
    }

    @Override
    public double getPrice() {
        double originalPrice = super.getPrice();
        return originalPrice * (1 - discount);
    }
}

public class ReductionDecorator extends PromotionDecorator {
    private double threshold;
    private double reduction;

    public ReductionDecorator(Product product, double threshold, double reduction) {
        super(product);
        this.threshold = threshold;
        this.reduction = reduction;
    }

    @Override
    public double getPrice() {
        double originalPrice = super.getPrice();
        if (originalPrice >= threshold) {
            return originalPrice - reduction;
        }
        return originalPrice;
    }
}

最后,我们可以通过组合不同的装饰器来实现不同类型的商品促销:

public class Main {
    public static void main(String[] args) {
        Product product = new ConcreteProduct("iPhone", 1000.0);

        // 打折促销
        PromotionDecorator discountDecorator = new DiscountDecorator(product, 0.2);
        System.out.println("Discounted Price: " + discountDecorator.getPrice());

        // 满减促销
        PromotionDecorator reductionDecorator = new ReductionDecorator(product, 800.0, 100.0);
        System.out.println("Reduced Price: " + reductionDecorator.getPrice());

        // 组合促销
        PromotionDecorator comboDecorator = new ReductionDecorator(new DiscountDecorator(product, 0.2), 800.0, 100.0);
        System.out.println("Discounted and Reduced Price: " + comboDecorator.getPrice());
    }
}

08 适配器模式

适配器模式是一种常用的设计模式,它可以将不兼容的接口转换为客户端期望的接口,从而让不兼容的组件能够协同工作

核心概念

  1. 目标接口(Target):定义客户端期望的接口。
  2. 源接口(Adaptee):已存在但不兼容的接口。
  3. 适配器(Adapter):实现目标接口,并持有一个源接口对象的引用。适配器将客户端的请求转发给源接口对象,并根据需要进行适配转换。

优点

  1. 兼容性增强:适配器模式可以将不兼容的接口转换为兼容的接口,从而使不同组件能够协同工作。
  2. 可复用性高:适配器模式将适配逻辑封装在适配器中,使得适配逻辑可以被多个客户端复用。
  3. 灵活性强:适配器模式可以动态地添加、修改和删除适配器,从而满足不同的需求。

应用场景

  1. 需要将一个现有类或接口转换为另一个接口以满足客户端的需求。
  2. 需要复用一些已经存在的类,但是它们的接口与系统要求的接口不兼容。
  3. 需要将多个类的接口统一,以便可以使用它们的共同接口进行操作。

示例演示

假设我们有一个电商系统,其中有两个不兼容的支付接口:Alipay(支付宝)和 WeChatPay(微信支付)。客户端希望能够统一调用这两个支付接口,而无需关心具体的实现细节。这时,我们可以使用适配器模式来解决这个问题。

首先,定义目标接口 Payment:

public interface Payment {
    void pay(double amount);
}

实现两个不兼容的支付接口:

public class Alipay {
    public void doPayment(double amount) {
        System.out.println("Using Alipay to pay: " + amount);
    }
}

public class WeChatPay {
    public void makePayment(double amount) {
        System.out.println("Using WeChat Pay to pay: " + amount);
    }
}

接下来,创建适配器类 PaymentAdapter,实现目标接口 Payment,并在适配器中持有一个不兼容接口的引用:

public class PaymentAdapter implements Payment {
    private Alipay alipay;

    public PaymentAdapter(Alipay alipay) {
        this.alipay = alipay;
    }

    @Override
    public void pay(double amount) {
        alipay.doPayment(amount);
    }
}

最后,在客户端中使用适配器来调用支付接口:

public class Client {
    public static void main(String[] args) {
        Payment alipayPayment = new PaymentAdapter(new Alipay());
        alipayPayment.pay(100.0);
    }
}

加餐:代理、桥接、装饰器、适配器 4 种设计模式的区别

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

09 门面模式

门面模式是一种结构性设计模式,它提供了一个统一的高层接口,用于简化复杂系统的访问和使用。本文将详细介绍门面模式的概念、结构和使用场景,并通过一个电商系统的例子来演示如何使用门面模式简化系统的访问过程。

结构

门面模式由以下几个核心组件构成:

  • 门面(Facade):提供了一个统一的高层接口,隐藏了底层系统的复杂性。它知道哪些子系统负责处理特定的请求,并将请求委派给适当的子系统进行处理。
  • 子系统(SubSystem):实现了实际的功能和业务逻辑。每个子系统都专注于完成特定的任务,并为门面提供了特定的接口。

下图展示了门面模式的结构:

  ------------------------------------
 |               Facade               |
 |------------------------------------|
 | + method1()                        |
 | + method2()                        |
 | + method3()                        |
  ------------------------------------
                    |
  ------------------------------------
 |            SubSystem A              |
 |------------------------------------|
 | + methodA1()                       |
 | + methodA2()                       |
  ------------------------------------
                    |
  ------------------------------------
 |            SubSystem B              |
 |------------------------------------|
 | + methodB1()                       |
 | + methodB2()                       |
  ------------------------------------
                    |
  ------------------------------------
 |            SubSystem C              |
 |------------------------------------|
 | + methodC1()                       |
 | + methodC2()                       |
  ------------------------------------

使用场景

  1. 当系统具有复杂的子系统和模块,并且需要简化客户端与系统之间的交互时,可以使用门面模式。它提供了一个统一的接口,隐藏了底层系统的复杂性,使客户端更加方便地使用系统功能。
  2. 当需要将底层系统与客户端代码解耦时,可以使用门面模式。它通过提供一个抽象的接口,将客户端与底层系统的实现细节分离开来,使系统更加灵活和可维护。
  3. 当需要对系统进行扩展或修改时,门面模式可以起到一定的保护作用。由于客户端只与门面接口进行交互,当系统发生变化时,只需修改门面的实现,而不影响客户端代码。

电商系统中的应用

假设我们正在开发一个电商系统,其中包含了用户管理、订单管理和库存管理等多个子系统。为了简化客户端对系统的访问和使用,我们可以使用门面模式。

首先,我们定义一个电商门面(EcommerceFacade)作为整个系统的统一接口。它提供了一系列方法,例如用户注册、下单和查询库存等

public class EcommerceFacade {
    private UserManager userManager;
    private OrderManager orderManager;
    private InventoryManager inventoryManager;

    public EcommerceFacade() {
        userManager = new UserManager();
        orderManager = new OrderManager();
        inventoryManager = new InventoryManager();
    }

    public void registerUser(String username, String password) {
        userManager.registerUser(username, password);
    }

    public void placeOrder(String productId, int quantity) {
        if (inventoryManager.checkStock(productId, quantity)) {
            orderManager.createOrder(productId, quantity);
            inventoryManager.reduceStock(productId, quantity);
        } else {
            System.out.println("Insufficient stock for product: " + productId);
        }
    }

    public int checkStock(String productId) {
        return inventoryManager.getStock(productId);
    }
}

在上述代码中,电商门面(EcommerceFacade)封装了用户管理、订单管理和库存管理等子系统,并提供了注册用户、下单和查询库存等高层接口。

客户端只需要通过电商门面来进行操作,而无需关心底层子系统的具体实现。

public class Client {
    public static void main(String[] args) {
        EcommerceFacade ecommerceFacade = new EcommerceFacade();
        
        ecommerceFacade.registerUser("John", "123456");
        
        ecommerceFacade.placeOrder("P001", 2);
        
        int stock = ecommerceFacade.checkStock("P001");
        System.out.println("Stock for product P001: " + stock);
    }
}

通过以上代码,我们可以看到客户端通过电商门面(EcommerceFacade)进行用户注册、下单和查询库存的操作,而无需了解底层子系统的实现细节。这样可以大大简化客户端的代码,并提高了系统的可维护性和可扩展性。

10 组合模式

什么是组合模式

在软件开发中,我们经常会遇到对象之间存在部分-整体的关系。例如,一个文件夹包含多个文件,一个组织包含多个部门等。为了方便地处理这种层次关系,并统一处理单个对象和组合对象,我们可以使用组合模式。

组合模式(Composite Pattern)是一种结构性设计模式,它允许我们将对象组合成树状结构,形成部分-整体的层次关系。通过组合模式,我们可以用统一的方式处理单个对象和组合对象,而不需要对它们进行区分。这样可以简化代码的设计和使用。

结构

组合模式由以下几个核心组件构成:

  • 组件(Component):定义了组合对象和叶子对象的共同行为。它可以是接口或抽象类,提供了统一的接口用于访问组合对象和叶子对象。
  • 叶子对象(Leaf):表示组合对象中的叶子节点,它没有子节点。它实现了组件接口,并定义了自己的行为。
  • 组合对象(Composite):表示组合对象中的非叶子节点,它包含了子节点。它实现了组件接口,并可以执行一系列操作,例如添加子节点、删除子节点和遍历子节点等。

使用场景

  1. 当存在部分-整体的层次关系,并且希望统一处理单个对象和组合对象时,可以使用组合模式。它提供了一致的方式访问对象,并简化了代码的设计和使用。
  2. 当需要对对象进行递归组合和遍历操作时,组合模式非常有用。它可以让我们以统一的方式处理组合对象和叶子对象,无需进行类型判断。
  3. 当希望将多个对象组织成树状结构,并对整个结构进行操作时,可以使用组合模式。它提供了方便的方式来管理和操作对象的层次结构。

电商系统中的应用

假设我们正在开发一个电商系统,其中包含了商品分类和商品管理等功能。商品分类由多个子分类和商品组成,而商品组则由多个商品组成。为了统一处理商品分类和商品组的操作,我们可以使用组合模式。

首先,我们定义一个组件接口(Component),用于访问组合对象和叶子对象的行为。

public interface Component {
    void operation();
}

然后,我们实现叶子对象(Leaf)和组合对象(Composite)。

public class Category implements Component {
    private String name;

    public Category(String name) {
        this.name = name;
    }

    @Override
    public void operation() {
        System.out.println("Category: " + name);
    }
}

public class Group implements Component {
    private List<Component> components = new ArrayList<>();

    public void add(Component component) {
        components.add(component);
    }

    public void remove(Component component) {
        components.remove(component);
    }

    public Component getChild(int index) {
        return components.get(index);
    }

    @Override
    public void operation() {
        System.out.println("Group:");

        for (Component component : components) {
            component.operation();
        }
    }
}

11_享元模式

什么是享元模式?

享元模式是一种结构型设计模式,旨在有效地支持大量细粒度对象的共享。它通过共享对象的公共状态,减少了对象的存储开销,并在需要时动态地提供个性化的特征。这样可以有效地节省内存,并提高系统性能。

享元模式的角色 享元模式中有以下几个角色:

  • Flyweight(享元):定义共享对象的接口,包含对共享状态的操作方法。
  • ConcreteFlyweight(具体享元):实现享元接口,并实现共享状态的方法。
  • FlyweightFactory(享元工厂):负责创建和管理享元对象,提供对共享对象的访问和复用。

电商系统中的享元模式

在电商系统中,优惠券是一种常见的促销工具,用于吸引用户购买商品。每个优惠券都有一些共享的信息,例如优惠券代码、折扣金额和适用范围等。使用享元模式,我们可以共享这些公共信息,并在需要时提供个性化的特征,例如优惠券的有效期和使用限制。

首先,我们定义一个接口 Coupon,它包含优惠券的公共方法。

public interface Coupon {
    void applyCoupon(String productId);
}

然后,我们创建具体的享元类 ConcreteCoupon,实现了 Coupon 接口。

public class ConcreteCoupon implements Coupon {
    private String couponCode;
    private double discountAmount;
    private String applicableRange;

    public ConcreteCoupon(String couponCode, double discountAmount, String applicableRange) {
        this.couponCode = couponCode;
        this.discountAmount = discountAmount;
        this.applicableRange = applicableRange;
    }

    @Override
    public void applyCoupon(String productId) {
        System.out.println("Applying coupon " + couponCode + " to product " + productId +
                ". Discount: " + discountAmount + ", Applicable Range: " + applicableRange);
    }
}

接下来,我们创建享元工厂类 CouponFactory,负责管理和提供享元对象。

import java.util.HashMap;
import java.util.Map;

public class CouponFactory {
    private static Map<String, Coupon> coupons = new HashMap<>();

    public static Coupon getCoupon(String couponCode, double discountAmount, String applicableRange) {
        String key = couponCode + discountAmount + applicableRange;
        Coupon coupon = coupons.get(key);

        if (coupon == null) {
            coupon = new ConcreteCoupon(couponCode, discountAmount, applicableRange);
            coupons.put(key, coupon);
        }

        return coupon;
    }
}

在享元工厂中,我们使用一个哈希表 coupons 来存储已创建的优惠券对象。当需要获取优惠券对象时,我们首先检查是否已经存在该优惠券对象,如果存在,则直接返回;如果不存在,则创建一个新的优惠券对象并存储在哈希表中。

现在,我们可以使用享元模式来管理和应用优惠券对象。以下是一个示例:

public class CouponManagementExample {
    public static void main(String[] args) {
        Coupon coupon1 = CouponFactory.getCoupon("CODE123", 10.0, "Category A");
        Coupon coupon2 = CouponFactory.getCoupon("CODE123", 10.0, "Category A");

        // coupon1 和 coupon2 引用同一个享元对象,因为它们的公共信息相同
        System.out.println(coupon1 == coupon2); // 输出:true

        coupon1.applyCoupon("Product A");
        coupon2.applyCoupon("Product B");
    }
}

在上面的示例中,我们通过 CouponFactory 获取了两个优惠券对象

享元模式 vs 单例

在单例模式中,一个类只能创建一个对象,而在享元模式中,一个类可以创建多个对象,每个对象被多处代码引用共享。实际上,享元模式有点类似于之前讲到的单例的变体:多例。区别两种设计模式,享元模式是为了对象复用,节省内存,而应用多例模式是为了限制对象的个数。

享元模式 vs 缓存

在享元模式的实现中,我们通过工厂类来“缓存”已经创建好的对象。这里的“缓存”实际上是“存储”的意思,跟我们平时所说的“数据库缓存”“CPU 缓存”“MemCache 缓存”是两回事。我们平时所讲的缓存,主要是为了提高访问效率,而非复用。

享元模式 vs 缓存

池化技术中的“复用”可以理解为“重复使用”,主要目的是节省时间(比如从数据库池中取一个连接,不需要重新创建)。在任意时刻,每一个对象、连接、线程,并不会被多处使用,而是被一个使用者独占,当使用完成之后,放回到池中,再由其他使用者重复利用。享元模式中的“复用”可以理解为“共享使用”,在整个生命周期中,都是被所有使用者共享的,主要目的是节省空间。

12 观察者模式

在单例模式中,一个类只能创建一个对象,而在享元模式中,一个类可以创建多个对象,每个对象被多处代码引用共享。实际上,享元模式有点类似于之前讲到的单例的变体:多例。区别两种设计模式,享元模式是为了对象复用,节省内存,而应用多例模式是为了限制对象的个数。

享元模式 vs 缓存

在享元模式的实现中,我们通过工厂类来“缓存”已经创建好的对象。这里的“缓存”实际上是“存储”的意思,跟我们平时所说的“数据库缓存”“CPU 缓存”“MemCache 缓存”是两回事。我们平时所讲的缓存,主要是为了提高访问效率,而非复用。

享元模式 vs 缓存

池化技术中的“复用”可以理解为“重复使用”,主要目的是节省时间(比如从数据库池中取一个连接,不需要重新创建)。在任意时刻,每一个对象、连接、线程,并不会被多处使用,而是被一个使用者独占,当使用完成之后,放回到池中,再由其他使用者重复利用。享元模式中的“复用”可以理解为“共享使用”,在整个生命周期中,都是被所有使用者共享的,主要目的是节省空间。

12 观察者模式

观察者模式是一种行为性设计模式,它定义了一对多的依赖关系,当一个对象(称为主题)的状态发生改变时,所有依赖于它的对象(称为观察者)都会自动收到通知并进行相应的更新。

结构

  • 主题(Subject):定义了观察者的注册、移除和通知方法。它维护了一个观察者列表,并在状态发生改变时通知所有观察者。
  • 观察者(Observer):定义了更新方法,用于接收主题的通知并进行相应的更新操作。
  • 具体主题(ConcreteSubject):实现了主题接口。它维护了一个状态,并在状态发生改变时通知所有注册的观察者。
  • 具体观察者(ConcreteObserver):实现了观察者接口。它在接收到主题通知时进行相应的更新操作。
  ------------------------------------
 |             Subject                |
 |------------------------------------|
 | + registerObserver(Observer)       |
 | + removeObserver(Observer)         |
 | + notifyObservers()                |
  ------------------------------------
                    |
  ------------------------------------
 |            Observer                |
 |------------------------------------|
 | + update()                         |
  ------------------------------------
                    |
  ------------------------------------
 |        ConcreteSubject             |
 |------------------------------------|
 | - state                           |
 | + setState()                       |
  ------------------------------------
                    |
  ------------------------------------
 |       ConcreteObserver             |
 |------------------------------------|
 | + update()                         |
  ------------------------------------

使用场景

  1. 当一个对象的改变需要同时影响其他对象,并且不希望显式地耦合对象之间的关系时,可以使用观察者模式。观察者模式使得主题和观察者之间的依赖关系变得松散,可以动态地添加或移除观察者。
  2. 当一个对象需要通知多个其他对象,而这些对象对通知的顺序和内容有不同的需求时,观察者模式可以很好地满足这种需求。观察者模式允许灵活地管理观察者列表,以确保每个观察者都能接收到适合自身的通知。
  3. 当一个对象需要和其他对象进行解耦,并且希望在系统中添加新的观察者时不影响现有代码时,观察者模式是一个合适的选择。观察者模式提供了一种可扩展的机制,可以动态地添加新的观察者。

购物车实例

假设我们正在开发一个电商系统,其中包含购物车功能。购物车中的商品发生变化时,我们希望自动更新购物车总价。为了实现这一需求,我们可以使用观察者模式。

首先,我们定义主题接口(Subject)和观察者接口(Observer):

// 主题接口
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 观察者接口
interface Observer {
    void update(float totalPrice);
}

然后,我们实现具体主题类(ConcreteSubject)和具体观察者类(ConcreteObserver):

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

// 具体主题类
class ShoppingCart implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float totalPrice;

    public void setTotalPrice(float totalPrice) {
        this.totalPrice = totalPrice;
        notifyObservers();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

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

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(totalPrice);
        }
    }
}

// 具体观察者类
class CartTotalPriceObserver implements Observer {
    @Override
    public void update(float totalPrice) {
        System.out.println("Cart Total Price updated: " + totalPrice);
    }
}

在上述代码中,购物车(ShoppingCart)是具体主题类,购物车总价是状态的一部分。当购物车总价发生变化时,购物车会通知所有注册的观察者。观察者接收到通知后会更新购物车总价。

我们可以通过以下示例代码来演示观察者模式的使用:

public class ObserverPatternExample {
    public static void main(String[] args) {
        ShoppingCart shoppingCart = new ShoppingCart();

        Observer cartTotalPriceObserver1 = new CartTotalPriceObserver();
        Observer cartTotalPriceObserver2 = new CartTotalPriceObserver();

        shoppingCart.registerObserver(cartTotalPriceObserver1);
        shoppingCart.registerObserver(cartTotalPriceObserver2);

        shoppingCart.setTotalPrice(100.0f);
        shoppingCart.setTotalPrice(150.0f);

        shoppingCart.removeObserver(cartTotalPriceObserver2);

        shoppingCart.setTotalPrice(200.0f);
    }
}


从输出结果可以看出,购物车总价发生变化时,所有观察者都接收到了通知,并进行了相应的更新。

通过观察者模式,我们实现了购物车和购物车总价之间的松耦合,购物车不需要直接调用购物车总价的更新方法,而是通过观察者模式来通知所有观察者。

13 模板模式

什么是模板方法

模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,将算法的具体步骤延迟到子类中实现。模板方法模式使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。这种模式通过提供一个抽象类或接口来定义算法的骨架,然后让子类来实现具体的步骤,从而实现算法的定制化。

电商系统中的应用

假设我们正在开发一个电商系统,其中有不同种类的商品,例如服装、电子产品、食品等。我们需要实现一个抽象的商品类(Product),并定义一个用于展示商品信息的方法(displayProductInfo())。不同的商品类可以继承抽象商品类,并根据自身的特点实现展示商品信息的具体步骤。

首先,我们定义一个抽象的商品类(Product):

// 抽象商品类
abstract class Product {
    public void displayProductInfo() {
        System.out.println("Product Category: " + getCategory());
        System.out.println("Product Name: " + getName());
        System.out.println("Product Price: " + getPrice());
        displayAdditionalInfo();
    }

    public abstract String getCategory();

    public abstract String getName();

    public abstract double getPrice();

    public abstract void displayAdditionalInfo();
}


在抽象商品类中,我们定义了展示商品信息的模板方法displayProductInfo(),并将一些通用的步骤实现在抽象类中,例如展示商品的分类、名称和价格。同时,我们也定义了一些抽象方法,用于子类实现自己的特定步骤,例如获取商品的分类、名称、价格以及展示其他额外信息。

然后,我们可以创建具体的商品类,例如服装类(Clothing)和电子产品类(Electronics):

// 服装类
class Clothing extends Product {
    @Override
    public String getCategory() {
        return "Clothing";
    }

    @Override
    public String getName() {
        return "T-shirt";
    }

    @Override
    public double getPrice() {
        return 29.99;
    }

    @Override
    public void displayAdditionalInfo() {
        System.out.println("Material: Cotton");
        System.out.println("Size: M");
    }
}

// 电子产品类
class Electronics extends Product {
    @Override
    public String getCategory() {
        return "Electronics";
    }

    @Override
    public String getName() {
        return "Smartphone";
    }

    @Override
    public double getPrice() {
        return 699.99;
    }

    @Override
    public void displayAdditionalInfo() {
        System.out.println("Brand: Samsung");
        System.out.println("Screen Size: 6.5 inches");
    }
   }

在具体的商品类中,我们重写了抽象商品类中的抽象方法,并实现了自己的特定步骤,例如获取商品的分类、名称、价格以及展示其他额外信息。

最后,我们可以使用模板方法模式来展示商品信息:

public class Main {
    public static void main(String[] args) {
        Product clothing = new Clothing();
        clothing.displayProductInfo();

        System.out.println("--------------------------------");

        Product electronics = new Electronics();
        electronics.displayProductInfo();
    }
}

通过模板方法模式,我们成功地定义了商品信息展示的骨架,并允许不同的商品类根据自己的需求来实现具体的展示步骤。这种设计模式使得系统具有高度的可扩展性和灵活性,同时提供了代码的重用性和维护性。

14 策略模式

什么是策略模式

策略模式是一种行为型设计模式,它定义了一系列算法或行为,并将它们封装在各自的类中,使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。这种模式提供了一种灵活的方式来选择或切换算法,而不需要修改客户端的代码。

电商系统中的策略模式

假设我们正在开发一个电商系统,其中包含商品促销的功能。系统中有多种促销策略,例如满减、折扣、赠品等。我们希望能够灵活地选择或切换不同的促销策略,而不需要修改购物车或订单处理的代码。

为了实现这一需求,我们可以使用策略模式。首先,我们定义一个促销策略接口(PromotionStrategy):

// 促销策略接口
interface PromotionStrategy {
    void applyPromotion();
}

然后,我们实现具体的促销策略类,例如满减策略(DiscountPromotionStrategy)、折扣策略(PercentagePromotionStrategy)、赠品策略(GiftPromotionStrategy)等:

// 满减策略
class DiscountPromotionStrategy implements PromotionStrategy {
    @Override
    public void applyPromotion() {
        System.out.println("Applying Discount Promotion");
    }
}

// 折扣策略
class PercentagePromotionStrategy implements PromotionStrategy {
    @Override
    public void applyPromotion() {
        System.out.println("Applying Percentage Promotion");
    }
}

// 赠品策略
class GiftPromotionStrategy implements PromotionStrategy {
    @Override
    public void applyPromotion() {
        System.out.println("Applying Gift Promotion");
    }
}

在上述代码中,每个具体的促销策略类都实现了促销策略接口,并实现了applyPromotion()方法来应用相应的促销策略。

接下来,我们定义一个促销活动类(PromotionActivity),用于执行促销活动:

class PromotionActivity {
    private PromotionStrategy promotionStrategy;

    public PromotionActivity(PromotionStrategy promotionStrategy) {
        this.promotionStrategy = promotionStrategy;
    }

    public void executePromotion() {
        promotionStrategy.applyPromotion();
    }
}

在促销活动类中,我们通过构造函数接收一个具体的促销策略对象,并在executePromotion()方法中调用该策略对象的applyPromotion()方法来应用促销策略。

最后,我们可以通过以下示例代码来演示策略模式的使用:

public class Main {
    public static void main(String[] args) {
        PromotionStrategy discountStrategy = new DiscountPromotionStrategy();
        PromotionActivity promotionActivity1 = new PromotionActivity(discountStrategy);
        promotionActivity1.executePromotion();

        PromotionStrategy percentageStrategy = new PercentagePromotionStrategy();
        PromotionActivity promotionActivity2 = new PromotionActivity(percentageStrategy);
        promotionActivity2.executePromotion();

        PromotionStrategy giftStrategy = new GiftPromotionStrategy();
        PromotionActivity promotionActivity3 = new PromotionActivity(giftStrategy);
        promotionActivity3.executePromotion();
    }
}

运行以上代码,我们可以看到不同的促销策略被灵活地应用:

Applying Discount Promotion
Applying Percentage Promotion
Applying Gift Promotion

通过策略模式,我们成功地将促销策略的变化独立出来,使得系统能够灵活地选择或切换不同的促销策略,而不需要修改购物车或订单处理的代码。

15 责任链模式

什么是责任链模式

责任链模式是一种行为型设计模式,它将请求的发送者和接收者解耦,并将多个对象组成一条链式结构,每个对象都有机会处理请求,直到有一个对象能够处理该请求为止。责任链模式可以有效地避免请求的发送者和接收者之间的耦合关系,同时允许多个对象都有机会处理请求。

在电商系统的应用

假设我们正在开发一个电商系统,其中有一个退货申请的流程。当用户发起退货申请时,系统需要经过一系列的处理步骤,例如验证退货申请、审核退货申请、处理退货申请等。我们可以使用责任链模式来设计和实现这个退货申请的处理流程。

首先,我们定义一个抽象的处理器(Handler):

// 抽象处理器
abstract class Handler {
    protected Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public abstract void handleRequest(ReturnRequest request);
}

在抽象处理器中,我们定义了一个后继处理器(successor)并提供了设置后继处理器的方法。同时,我们也定义了一个抽象的处理请求方法handleRequest(),用于具体处理请求的逻辑。

然后,我们可以创建具体的处理器,例如验证处理器(ValidationHandler)、审核处理器(ReviewHandler)和处理处理器(ProcessingHandler):

// 验证处理器
class ValidationHandler extends Handler {
    @Override
    public void handleRequest(ReturnRequest request) {
        // 进行退货申请的验证逻辑
        // ...

        if (request.isValid()) {
            System.out.println("Return request is valid.");
            if (successor != null) {
                successor.handleRequest(request);
            }
        } else {
            System.out.println("Return request is invalid.");
        }
    }
}

// 审核处理器
class ReviewHandler extends Handler {
    @Override
    public void handleRequest(ReturnRequest request) {
        // 进行退货申请的审核逻辑
        // ...

        if (request.isReviewed()) {
            System.out.println("Return request is reviewed.");
            if (successor != null) {
                successor.handleRequest(request);
            }
        } else {
            System.out.println("Return request is not reviewed.");
        }
    }
}

// 处理处理器
class ProcessingHandler extends Handler {
    @Override
    public void handleRequest(ReturnRequest request) {
        // 进行退货申请的处理逻辑
        // ...

        System.out.println("Return request is processed.");
    }
}

在具体的处理器中,我们重写了抽象处理器中的处理请求方法handleRequest(),并根据具体的处理逻辑判断是否继续传递请求给后继处理器。

最后,我们可以将处理器链接起来形成一条责任链:

public class Main {
    public static void main(String[] args) {
        Handler validationHandler = new ValidationHandler();
        Handler reviewHandler = new ReviewHandler();
        Handler processingHandler = new ProcessingHandler();

        validationHandler.setSuccessor(reviewHandler);
        reviewHandler.setSuccessor(processingHandler);

        ReturnRequest request = new ReturnRequest();
        validationHandler.handleRequest(request);
    }
}

通过使用责任链模式,我们成功地将请求的发送者和接收者解耦,并允许多个处理器有机会处理请求。这种设计模式可以简化系统的组织结构,提高系统的可扩展性和灵活性,并使得代码具有更好的可维护性和可重用性。

16_状态模式

什么是状态模式?

状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变它的行为。该模式的核心思想是将不同的状态封装成独立的类,并使这些类实现同一个接口,从而使得状态的变化可以通过切换不同的状态对象来实现。

在订单状态管理中的应用

在电商系统中,订单的状态通常包括已创建、已确认、已支付和已取消等。首先,我们需要定义一个订单状态接口(OrderState),该接口包含了订单状态的公共方法。

public interface OrderState {
    void confirmOrder(Order order);
    void cancelOrder(Order order);
    void payOrder(Order order);
    //其他状态
}

接下来,我们创建具体的订单状态类,实现订单状态接口。我们以订单已创建状态为例进行演示。

public class OrderCreatedState implements OrderState {
    @Override
    public void confirmOrder(Order order) {
        // 订单已创建状态下,确认订单的具体实现
        System.out.println("订单已确认");
        
        // 执行订单确认操作,如发送确认邮件、扣减库存等
        sendConfirmationEmail(order);
        reduceInventory(order);
        
        // 将订单状态设置为已确认状态
        order.setState(new OrderConfirmedState());
    }

    @Override
    public void cancelOrder(Order order) {
        // 订单已创建状态下,取消订单的具体实现
        System.out.println("订单已取消");
        
        // 执行订单取消操作,如释放库存、取消支付等
        releaseInventory(order);
        cancelPayment(order);
        
        // 将订单状态设置为已取消状态
        order.setState(new OrderCancelledState());
    }

    @Override
    public void payOrder(Order order) {
        // 订单已创建状态下,支付订单的具体实现
        System.out.println("请先确认订单");
    }
    
    private void sendConfirmationEmail(Order order) {
        // 发送确认邮件的具体实现
        System.out.println("发送确认邮件至:" + order.getCustomerEmail());
    }
    
    private void reduceInventory(Order order) {
        // 扣减库存的具体实现
        System.out.println("扣减库存:" + order.getProduct() + " - " + order.getQuantity());
    }
    
    private void releaseInventory(Order order) {
        // 释放库存的具体实现
        System.out.println("释放库存:" + order.getProduct() + " - " + order.getQuantity());
    }
    
    private void cancelPayment(Order order) {
        // 取消支付的具体实现
        System.out.println("取消支付:" + order.getAmount());
    }
}


// 其他状态的实现比如:public class OrderCancelState implements OrderState {}

在上述代码中,我们创建了一个OrderCreatedState类来表示订单已创建状态。在确认订单时,我们执行了一些具体的业务逻辑,如发送确认邮件和扣减库存。在取消订单时,我们释放了库存并取消了支付。这些具体的操作根据实际业务需求来定义。

最后,我们需要在订单类中使用状态模式。订单类(Order)将包含一个当前订单状态的成员变量,并提供相应的方法来管理订单状态的转换。

public class Order {
    private OrderState currentState;
    
    public void setState(OrderState state) {
        this.currentState = state;
    }
    
    public void confirmOrder() {
        currentState.confirmOrder(this);
    }
    
    public void cancelOrder() {
        currentState.cancelOrder(this);
    }
    
    public void payOrder() {
        currentState.payOrder(this);
    }
    
    // 其他订单相关方法

通过以上设计,我们可以根据具体的业务需求定义和管理订单的不同状态,并在不同状态下执行相应的操作。使用状态模式,我们将不同状态下的行为逻辑封装到各自的状态类中,从而实现代码的解耦和可维护性的提升。

总结

本文介绍了如何使用状态模式来管理订单状态转换。通过将每个状态封装成独立的类并实现同一个接口,我们可以实现订单状态的切换和相应行为的执行。状态模式使得代码更加清晰、可扩展和易于维护。

希望本文能帮助刚入门的读者理解和应用状态模式在订单状态管理中的作用。如果你有任何疑问或需要进一步讨论,欢迎留言。谢谢!

17 迭代器模式

什么是迭代器模式

在软件开发中,经常会遇到需要遍历聚合对象(例如列表、集合)中的元素的情况。传统的遍历方式是通过使用索引或者迭代器接口直接访问聚合对象中的元素。但这种方式会使得遍历算法和聚合对象的实现紧密耦合在一起,迭代器模式的出现就是为了解决这个问题。

迭代器模式提供了一种统一的访问聚合对象元素的方式,通过定义一个迭代器接口,可以独立地遍历不同类型的聚合对象。迭代器模式将遍历算法封装在迭代器中,使得遍历算法和聚合对象的实现相互独立,可以独立地变化和演化。

在电商系统的应用

假设我们正在开发一个电商系统,其中有一个商品列表,我们需要遍历这个列表并进行一些操作,例如统计商品的数量、计算商品的总价等。我们可以使用迭代器模式来实现对商品列表的遍历。

首先,我们定义一个迭代器接口(Iterator):

// 迭代器接口
interface Iterator {
    boolean hasNext();

    Object next();
}

在迭代器接口中,我们定义了两个方法,hasNext()用于判断是否还有下一个元素,next()用于获取下一个元素。

然后,我们定义一个聚合对象(ProductList)并实现迭代器接口:

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

// 聚合对象
class ProductList implements Iterator {
    private List<String> products;
    private int position;

    public ProductList() {
        products = new ArrayList<>();
        position = 0;
    }

    public void addProduct(String product) {
        products.add(product);
    }

    @Override
    public boolean hasNext() {
        return position < products.size();
    }

    @Override
    public Object next() {
        if (hasNext()) {
            String product = products.get(position);
            position++;
            return product;
        }
        return null;
    }
}

在聚合对象中,我们使用一个内部的列表来存储商品,并记录当前遍历的位置。我们实现了迭代器接口中的方法,hasNext()用于判断是否还有下一个商品,next()用于获取下一个商品。

最后,我们可以使用迭代器来遍历商品列表:

public class Main {
    public static void main(String[] args) {
        ProductList productList = new ProductList();
        productList.addProduct("Product 1");
        productList.addProduct("Product 2");
        productList.addProduct("Product 3");

        Iterator iterator = productList;

        while (iterator.hasNext()) {
            String product = (String) iterator.next();
            System.out.println("Product: " + product);
        }
    }
}

以上代码中,我们创建了一个商品列表并添加了一些商品。然后,我们获取迭代器并使用迭代器遍历商品列表,并输出每个商品的名称。

通过迭代器模式,我们实现了对商品列表的遍历操作,并将遍历算法与聚合对象解耦,使得聚合对象的实现和遍历算法可以独立变化。

18 访问者模式

public class Main {
    public static void main(String[] args) {
        ProductList productList = new ProductList();
        productList.addProduct("Product 1");
        productList.addProduct("Product 2");
        productList.addProduct("Product 3");

        Iterator iterator = productList;

        while (iterator.hasNext()) {
            String product = (String) iterator.next();
            System.out.println("Product: " + product);
        }
    }
}

以上代码中,我们创建了一个商品列表并添加了一些商品。然后,我们获取迭代器并使用迭代器遍历商品列表,并输出每个商品的名称。

通过迭代器模式,我们实现了对商品列表的遍历操作,并将遍历算法与聚合对象解耦,使得聚合对象的实现和遍历算法可以独立变化。

18 访问者模式

什么是访问者模式

在软件开发中,经常会遇到需要对数据结构中的元素进行不同的操作的情况。如果直接在数据结构中定义这些操作,就会导致数据结构的修改和扩展变得困难,同时也破坏了数据结构的封装性。

访问者模式提供了一种解决方案,它将数据结构和操作分离开来,通过定义一个访问者接口和一组具体的访问者对象,来实现对数据结构的不同操作。数据结构中的元素可以通过接受访问者的方法来接受访问者对象的访问,从而完成不同的操作。

在电商系统的应用

假设我们正在开发一个电商系统,其中有一个订单数据结构,包含了订单的信息,例如订单号、商品列表等。我们希望能够对订单进行不同的操作,例如计算订单总价、统计订单中特定商品的数量等。这时,我们可以使用访问者模式来实现对订单的不同操作。

首先,我们定义一个访问者接口(Visitor):

// 访问者接口
interface Visitor {
    void visit(Order order);
}

在访问者接口中,我们定义了一个访问方法 visit(),用于对订单进行访问。

然后,我们定义一个订单类(Order):

// 订单类
class Order {
    private String orderNumber;
    private List<String> products;

    public Order(String orderNumber, List<String> products) {
        this.orderNumber = orderNumber;
        this.products = products;
    }

    public String getOrderNumber() {
        return orderNumber;
    }

    public List<String> getProducts() {
        return products;
    }

    // 接受访问者的方法
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

在订单类中,我们实现了接受访问者的方法 accept(),该方法调用访问者的访问方法并将自身作为参数传递给访问者。

接下来,我们定义两个具体的访问者类:计算订单总价的访问者和统计特定商品数量的访问者。

// 计算订单总价的访问者
class PriceVisitor implements Visitor {
    private double totalPrice;

    public void visit(Order order) {
        List<String> products = order.getProducts();
        for (String product : products) {
            // 假设每个商品的价格都为10元
            totalPrice += 10.0;
        }
    }

    public double getTotalPrice() {
        return totalPrice;
    }
}

// 统计特定商品数量的访问者
class CountVisitor implements Visitor {
    private int totalCount;

    public void visit(Order order) {
        List<String> products = order.getProducts();
        for (String product : products) {
            if (product.equals("iPhone")) {
                totalCount++;
            }
        }
    }

    public int getTotalCount() {
        return totalCount;
    }
}

在具体的访问者类中,我们实现了访问者接口的访问方法,并根据需要对订单进行具体的操作。

最后,我们在客户端代码中使用访问者模式:

public class Main {
    public static void main(String[] args) {
        List<String> products = new ArrayList<>();
        products.add("iPhone");
        products.add("iPad");
        products.add("MacBook");

        Order order = new Order("123456", products);

        Visitor priceVisitor = new PriceVisitor();
        order.accept(priceVisitor);
        System.out.println("Total Price: " + ((PriceVisitor) priceVisitor).getTotalPrice());

        Visitor countVisitor = new CountVisitor();
        order.accept(countVisitor);
        System.out.println("Total Count of iPhone: " + ((CountVisitor) countVisitor).getTotalCount());
    }
}

在客户端代码中,我们创建了一个订单对象,并创建了两个具体的访问者对象:计算订单总价的访问者和统计特定商品数量的访问者。然后,我们通过调用订单对象的 accept() 方法,将访问者对象传递给订单对象,并实现对订单的不同操作。

通过访问者模式,我们实现了对订单的不同操作,并将操作与订单对象分离开来,使得操作可以独立变化。这样,当需要添加新的操作时,只需要添加新的访问者类即可,而无需修改订单类。

19 备忘录模式

什么是备忘录模式

备忘录模式是一种行为型设计模式,它用于保存和恢复对象的状态,而无需破坏对象的封装性。备忘录模式通过定义备忘录类(Memento)来保存对象的内部状态,同时提供了一个负责人类(Caretaker)来管理备忘录对象。通过使用备忘录模式,我们可以实现对象状态的保存和恢复,使得对象可以在不同的时间点恢复到之前的状态。

在电商系统的应用

假设我们正在开发一个电商系统,其中有一个商品类(Product),包含了商品的信息,例如商品名称、价格等。我们希望能够在用户浏览商品时,能够保存用户浏览过的商品状态,并在需要时恢复到之前的状态。

首先,我们定义一个备忘录类(ProductMemento):

// 备忘录类
class ProductMemento {
    private String name;
    private double price;

    public ProductMemento(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

在备忘录类中,我们保存了商品的名称和价格,以便在需要时恢复商品的状态。

然后,我们定义一个负责人类(ProductCaretaker),负责管理备忘录对象:

// 负责人类
class ProductCaretaker {
    private ProductMemento memento;

    public void save(ProductMemento memento) {
        this.memento = memento;
    }

    public ProductMemento restore() {
        return memento;
    }
}

在负责人类中,我们提供了保存和恢复备忘录对象的方法。

接下来,我们修改商品类(Product),使其能够创建备忘录对象、保存状态和恢复状态:

// 商品类
class Product {
    private String name;
    private double price;

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    // 创建备忘录对象
    public ProductMemento createMemento() {
        return new ProductMemento(name, price);
    }

    // 从备忘录对象恢复状态
    public void restoreMemento(ProductMemento memento) {
        this.name = memento.getName();
        this.price = memento.getPrice();
    }
}

在商品类中,我们添加了创建备忘录对象的方法 createMemento() 和从备忘录对象恢复状态的方法 restoreMemento()

最后,我们在客户端代码中使用备忘录模式:

public class Main {
    public static void main(String[] args) {
        ProductCaretaker caretaker = new ProductCaretaker();

        // 创建商品对象
        Product product = new Product("iPhone", 999.99);

        // 保存商品状态
        caretaker.save(product.createMemento());

        // 修改商品状态
        product.setName("iPad");
        product.setPrice(799.99);

        // 恢复商品状态
        product.restoreMemento(caretaker.restore());

        // 打印商品信息
        System.out.println("Name: " + product.getName());
        System.out.println("Price: " + product.getPrice());
    }
}

在客户端代码中,我们创建了一个商品对象,并保存了商品的初始状态。然后,我们修改了商品的状态,再通过负责人对象恢复商品的状态,并打印出商品的名称和价格。

通过备忘录模式,我们实现了保存和恢复商品状态的功能,并且保持了商品类的封装性。备忘录模式可以在需要保存和恢复对象状态的场景中使用,例如编辑器的撤销/重做功能、游戏的存档功能等。使用备忘录模式可以使得对象状态的管理更加灵活和可扩展,同时也能提高代码的可维护性和可读性。

20 命令模式

什么是命令模式

命令模式是一种行为型设计模式,它将请求封装成对象,以便发送者和接收者能够解耦。在命令模式中,请求被封装成一个命令对象,该对象包含了执行该请求的方法。发送者负责创建和传递命令对象,而接收者负责执行命令对象所包含的方法。通过使用命令模式,我们可以实现请求的参数化、操作的可撤销和重做等功能。

在电商系统的应用

假设我们正在开发一个电商系统,其中有一个订单类(Order),包含了订单的信息,例如订单号、商品列表等。我们希望能够实现一些操作,如创建订单、取消订单等,同时还希望能够支持撤销和重做操作。

首先,我们定义一个命令接口(Command):

// 命令接口
interface Command {
    void execute();
    void undo();
}

在命令接口中,我们定义了两个方法 execute()undo(),分别用于执行命令和撤销命令。

然后,我们实现具体的命令类,比如创建订单命令(CreateOrderCommand)、取消订单命令(CancelOrderCommand)等:

// 创建订单命令
class CreateOrderCommand implements Command {
    private Order order;

    public CreateOrderCommand(Order order) {
        this.order = order;
    }

    public void execute() {
        // 执行创建订单操作
        order.create();
    }

    public void undo() {
        // 执行撤销创建订单操作
        order.cancel();
    }
}

// 取消订单命令
class CancelOrderCommand implements Command {
    private Order order;

    public CancelOrderCommand(Order order) {
        this.order = order;
    }

    public void execute() {
        // 执行取消订单操作
        order.cancel();
    }

    public void undo() {
        // 执行撤销取消订单操作
        order.create();
    }
}

在具体的命令类中,我们持有订单对象,并在 execute() 方法中执行相应的操作,而在 undo() 方法中执行相应的撤销操作。

接下来,我们定义一个订单管理类(OrderManager),负责接收和处理命令对象:

// 订单管理类
class OrderManager {
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand() {
        command.execute();
    }

    public void undoCommand() {
        command.undo();
    }
}

在订单管理类中,我们持有一个命令对象,并提供了执行命令和撤销命令的方法。

最后,我们可以在客户端代码中使用命令模式:

public class Main {
    public static void main(String[] args) {
        Order order = new Order("12345", "Product A");
        OrderManager orderManager = new OrderManager();

        // 创建订单
        Command createOrderCommand = new CreateOrderCommand(order);
        orderManager.setCommand(createOrderCommand);
        orderManager.executeCommand();

        // 撤销创建订单
        orderManager.undoCommand();

        // 取消订单
        Command cancelOrderCommand = new CancelOrderCommand(order);
        orderManager.setCommand(cancelOrderCommand);
        orderManager.executeCommand();

        // 撤销取消订单
        orderManager.undoCommand();
    }
}

在客户端代码中,我们创建了一个订单对象,并实例化了订单管理类。然后,我们创建了创建订单命令和取消订单命令,并通过订单管理类执行这些命令。通过命令模式,我们可以很容易地添加新的命令或修改现有的命令,而不需要修改原有的业务逻辑。

21 解释器模式

什么是解释器模式

解释器模式是一种行为型设计模式,它属于行为型设计模式中的一种,用于解释和执行特定语言的语法或表达式。解释器模式通过将语言的语法规则表示为一个抽象语法树,并定义相应的解释器来解释和执行该语法树。

在解释器模式中,我们将语言的语法规则表示为一组抽象语法树的节点(非终结符)和终结符。每个节点都定义了自己的解释方法,用于解释和执行与该节点相关的语法规则。解释器模式的核心思想是将语言的语法规则分解为简单的表达式,并逐个解释执行这些表达式,最终得到结果。

电商系统的应用

假设我们正在开发一个电商系统,其中需要实现一种新的促销活动语言,用于描述各种促销活动规则。这个促销活动语言可以描述如下的语法规则:

  • discount:表示打折活动,后面跟随一个浮点数,表示折扣比例。
  • coupon:表示优惠券活动,后面跟随一个字符串,表示优惠券的编码。

我们希望能够根据用户输入的促销活动语言来解释和执行相应的促销活动。

首先,我们定义一个抽象表达式接口(Expression):

// 抽象表达式接口
interface Expression {
    void interpret(Context context);
}

在抽象表达式接口中,我们定义了一个 interpret() 方法,用于解释和执行表达式。

然后,我们实现具体的表达式类,比如打折活动表达式(DiscountExpression)和优惠券活动表达式(CouponExpression):

// 打折活动表达式
class DiscountExpression implements Expression {
    private double discount;

    public DiscountExpression(double discount) {
        this.discount = discount;
    }

    public void interpret(Context context) {
        // 执行打折活动操作
        context.setDiscount(discount);
    }
}

// 优惠券活动表达式
class CouponExpression implements Expression {
    private String couponCode;

    public CouponExpression(String couponCode) {
        this.couponCode = couponCode;
    }

    public void interpret(Context context) {
        // 执行优惠券活动操作
        context.setCoupon(couponCode);
    }
}

在具体的表达式类中,我们实现了 interpret() 方法来执行相应的促销活动操作。

最后,我们定义一个上下文类(Context),用于保存解释器执行的结果:

// 上下文类
class Context {
    private double discount;
    private String coupon;

    public double getDiscount() {
        return discount;
    }

    public void setDiscount(double discount) {
        this.discount = discount;
    }

    public String getCoupon() {
        return coupon;
    }

    public void setCoupon(String coupon) {
        this.coupon = coupon;
    }
}

在上下文类中,我们定义了一些变量来保存解释器执行的结果。

最后,我们可以在客户端代码中使用解释器模式:

public class Main {
    public static void main(String[] args) {
        String promotion = "discount 0.2"; // 打折活动语句

        // 创建上下文对象
        Context context = new Context();

        // 解析促销活动语句
        String[] parts = promotion.split(" ");
        String keyword = parts[0];
        String value = parts[1];

        // 创建相应的表达式对象
        Expression expression;
        if (keyword.equals("discount")) {
            double discount = Double.parseDouble(value);
            expression = new DiscountExpression(discount);
        } else if (keyword.equals("coupon")) {
            expression = new CouponExpression(value);
        } else {
            throw new IllegalArgumentException("Unsupported promotion keyword: " + keyword);
        }

        // 执行促销活动
        expression.interpret(context);

        // 输出结果
        System.out.println("Discount: " + context.getDiscount());
        System.out.println("Coupon: " + context.getCoupon());
    }
}

在客户端代码中,我们首先将促销活动语句进行拆分,得到关键字和值。然后根据关键字创建相应的表达式对象,并执行解释器的 interpret() 方法来执行相应的促销活动操作。最后,我们可以从上下文对象中获取结果并输出。

通过使用解释器模式,我们可以将复杂的促销活动语言解析和执行的逻辑封装起来,从而提高系统的可维护性和扩展性。无论是开发电商系统还是其他需要解释和执行特定语言的系统,解释器模式都是一个有用的设计模式。

22 中介模式

什么是中介模式

中介模式是一种行为型设计模式,它属于行为型设计模式中的一种。中介模式通过引入一个中介者对象,来降低多个对象之间的耦合度,使得它们可以独立地进行交互。中介者对象充当了对象之间的调解者,协调它们的交互,从而避免了对象之间的直接依赖关系。

在中介模式中,对象之间的交互通过中介者对象进行,而不是直接相互引用。每个对象都持有中介者对象的引用,并通过中介者对象来与其他对象进行通信。当一个对象发生变化时,它只需要通知中介者对象,而不需要直接与其他对象进行通信。中介者对象负责将这些通知传递给其他相关的对象,并协调它们的交互。

电商系统应用

假设我们正在开发一个电商系统,其中包含了买家(Buyer)、卖家(Seller)和支付系统(Payment System)三个角色。当买家下单购买商品时,需要卖家确认订单,并且通过支付系统完成支付操作。在这个场景下,我们可以使用中介模式来实现买家、卖家和支付系统之间的交互。

首先,我们定义一个中介者接口(Mediator):

// 中介者接口
interface Mediator {
    void confirmOrder(Buyer buyer);
    void completePayment(Seller seller);
}

在中介者接口中,我们定义了两个方法:confirmOrder() 用于卖家确认订单,completePayment() 用于买家完成支付操作。

然后,我们实现具体的中介者类(PaymentMediator):

// 中介者类
class PaymentMediator implements Mediator {
    private Buyer buyer;
    private Seller seller;

    public void setBuyer(Buyer buyer) {
        this.buyer = buyer;
    }

    public void setSeller(Seller seller) {
        this.seller = seller;
    }

    public void confirmOrder(Buyer buyer) {
        // 卖家确认订单
        seller.confirmOrder();
    }

    public void completePayment(Seller seller) {
        // 买家完成支付操作
        buyer.completePayment();
    }
}

在中介者类中,我们持有买家和卖家对象的引用,并实现了中介者接口中定义的方法。当买家需要确认订单时,中介者对象调用卖家的 confirmOrder() 方法;当买家完成支付操作时,中介者对象调用买家的 completePayment() 方法。

接下来,我们定义买家类(Buyer)和卖家类(Seller):

// 买家类
class Buyer {
    private Mediator mediator;

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void confirmOrder() {
        System.out.println("买家确认订单");
        // 通知中介者对象,买家已确认订单
        mediator.confirmOrder(this);
    }

    public void completePayment() {
        System.out.println("买家完成支付操作");
        // 通知中介者对象,买家已完成支付操作
        mediator.completePayment(this);
    }
}

// 卖家类
class Seller {
    private Mediator mediator;

    public void setMediator(Mediator mediator) {
        this.mediator = mediator;
    }

    public void confirmOrder() {
        System.out.println("卖家确认订单");
    }
}

买家类和卖家类分别实现了对应的行为方法,并持有中介者对象的引用。当买家确认订单时,买家对象调用中介者对象的 confirmOrder() 方法;当买家完成支付操作时,买家对象调用中介者对象的 completePayment() 方法。卖家类只需要实现确认订单的行为。

最后,我们可以在客户端代码中使用中介模式:

public class Main {
    public static void main(String[] args) {
        // 创建中介者对象
        Mediator mediator = new PaymentMediator();

        // 创建买家和卖家对象
        Buyer buyer = new Buyer();
        Seller seller = new Seller();

        // 设置买家和卖家的中介者
        buyer.setMediator(mediator);
        seller.setMediator(mediator);

        // 买家确认订单
        buyer.confirmOrder();

        // 卖家确认订单
        // 注意:这里是模拟卖家收到买家确认订单的通知,实际中可以使用消息队列等方式来实现
        seller.confirmOrder();

        // 买家完成支付操作
        buyer.completePayment();
    }
}

在客户端代码中,我们首先创建中介者对象和买家、卖家对象,并将中介者对象设置给它们。然后,通过买家对象调用 confirmOrder() 方法来触发买家确认订单的操作。买家对象通知中介者对象,中介者对象再调用卖家对象的 confirmOrder() 方法来完成卖家确认订单的操作。最后,通过买家对象调用 completePayment() 方法来触发买家完成支付操作。

通过使用中介模式,我们可以将多个对象之间的交互逻辑封装到中介者对象中,从而降低对象之间的耦合度,并提供一种松耦合的方式来协调对象之间的交互。中介模式适用于多个对象之间存在复杂的交互关系的场景,通过引入中介者对象来简化交.

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值