单例模式(Singleton Pattern)
单例模式是最常用的设计模式之一。它可以确保在整个应用程序中,某个类只有一个实例存在,并提供一种访问这个实例的全局访问点。单例模式在需要限制某些类的实例数量时非常有用。 它通常用于需要全局访问的资源,如配置文件、日志记录器、数据库连接等。
应用场景
- 日志记录器 在一个应用程序中,通常会有多个模块或类需要记录日志。为了避免创建多个日志记录器实例,使用单例模式可以确保只有一个日志记录器实例,从而避免重复记录日志并提高应用程序的性能。
- 数据库连接 在一个应用程序中,如果需要频繁地与数据库交互,使用单例模式可以确保只有一个数据库连接实例,从而减少数据库连接的数量,提高应用程序的性能。
- 系统配置 在一个应用程序中,通常会有一些全局的配置参数,如数据库连接字符串、服务器地址、缓存大小等。使用单例模式可以确保只有一个配置实例,从而方便管理和修改配置参数。
代码实现
懒汉式
public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数,防止外部实例化
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
静态内部类方式
SingletonHolder是一个静态内部类,它包含一个静态的INSTANCE成员变量,用于存储单例对象。在第一次调用getInstance方法时,静态内部类会被加载,从而创建单例对象。这种方式既兼顾了线程安全又兼顾了延迟加载的需求。
public class Singleton {
private Singleton() {
// 私有构造函数,防止外部实例化
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
}
饿汉式
饿汉式在类加载时就创建了单例对象,所以不存在线程安全问题。不过,这种方式可能会导致不必要的资源浪费,因为单例对象的创建可能在应用程序启动时就完成了,而有些应用场景中可能并不需要使用单例对象。
public class Singleton {
// 在类加载时就创建单例对象
private static Singleton instance = new Singleton();
// 将构造函数设为私有,禁止外部创建实例
private Singleton() {}
// 提供获取单例对象的方法
public static Singleton getInstance() {
return instance;
}
}
双重检查锁
它可以在保证线程安全的同时实现延迟加载
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
枚举方式
使用枚举实现单例模式的好处是,可以避免反射和序列化攻击。因为枚举类型的构造函数是私有的,所以无法使用反射来创建实例;而且枚举类型的实例在序列化和反序列化时会自动处理好,所以也无法通过序列化和反序列化来破坏单例。
public enum Singleton {
INSTANCE;
public void doSomething() {
// TODO: 实现单例对象的功能
}
}
使用小结
- 对线程安全和性能要求较高,可以考虑使用饿汉式或双重检查锁方式实现单例模式。这两种方式都能保证线程安全,而且在大多数情况下性能也比较好。
- 如果你对线程安全要求不是很高,或者希望在第一次访问时才创建单例对象,可以考虑使用懒汉式或者静态内部类方式。这两种方式都是延迟加载的,只有在需要时才会创建单例对象。懒汉式不是线程安全的,需要通过加锁等方式来保证线程安全;而静态内部类方式则是天生线程安全的,不需要额外的处理。
- 希望实现简单、代码少,且不需要考虑线程安全和延迟加载的问题,可以考虑使用枚举方式。这种方式不仅代码简单,而且天生线程安全、单例对象创建和调用都很方便。
总之,选择哪种实现方式需要根据具体需求来决定,需要综合考虑线程安全、性能、代码复杂度、延迟加载等因素。
工厂模式(Factory Pattern)
工厂模式是一种创建型模式,它可以为开发人员提供一种在不直接实例化对象的情况下创建对象的方法。工厂模式通过提供一个通用的接口和一组实现,来隐藏具体实现的细节,从而降低了代码的耦合度和依赖性。
应用场景
- 对象的创建过程比较复杂,需要进行封装:如果创建一个对象需要进行复杂的初始化过程,或者需要从多个地方获取数据才能创建对象,那么使用工厂模式可以将这些过程封装起来,让客户端代码更加简洁和易于理解。
- 需要动态扩展或修改对象的创建过程:如果需要增加或修改某个对象的创建过程,而又不希望对客户端代码产生影响,那么使用工厂模式可以很方便地实现这个需求。
- 需要统一管理对象的创建:如果需要统一管理对象的创建过程,或者需要对创建的对象进行某些统一的处理,那么使用工厂模式可以很好地实现这个需求。
- 需要根据不同的条件创建不同的对象:如果需要根据不同的条件来创建不同类型的对象,那么使用工厂模式可以很方便地实现这个需求。
代码实现
通过一个工厂类来封装对象的创建过程,客户端只需要告诉工厂类需要创建哪种类型的对象即可。将对象的创建过程与客户端代码分离开来,使代码更加灵活和易于扩展
// 定义产品接口
public interface Product {
void operation();
}
// 具体产品类A
public class ConcreteProductA implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductA operation.");
}
}
// 具体产品类B
public class ConcreteProductB implements Product {
@Override
public void operation() {
System.out.println("ConcreteProductB operation.");
}
}
// 工厂类
public class SimpleFactory {
public static Product createProduct(String type) {
if ("A".equals(type)) {
return new ConcreteProductA();
} else if ("B".equals(type)) {
return new ConcreteProductB();
} else {
throw new IllegalArgumentException("Invalid product type.");
}
}
}
复制代码
客户端可以通过调用SimpleFactory.createProduct方法来创建不同类型的产品对象
Product productA = SimpleFactory.createProduct("A");
productA.operation(); // 输出 "ConcreteProductA operation."
Product productB = SimpleFactory.createProduct("B");
productB.operation(); // 输出 "ConcreteProductB operation."
使用小结
在Java中,工厂模式广泛应用于各种框架和类库中,例如JDBC中的DataSource工厂、Spring框架中的Bean工厂、MyBatis框架中的SqlSessionFactory等等。
观察者模式(Observer Pattern)
观察者模式是一种行为型模式,它定义了对象之间的一种一对多的依赖关系。在这种模式中,一个对象发生变化时,所有依赖于它的对象都会得到通知并自动更新。观察者模式可以帮助开发人员创建可扩展的应用程序,减少对象之间的直接依赖关系。
应用场景
- 事件处理机制:Java中的Swing GUI框架就是基于观察者模式实现的,当用户与组件交互时,组件会向注册的监听器发送事件通知,以触发相应的事件处理方法。
- 日志记录:Java中的日志系统也是基于观察者模式实现的,当日志发生变化时,它会通知所有注册的观察者,例如文件输出流、控制台输出流等,从而实现日志的输出和记录。
- 用户界面设计:在Java中,用户界面设计中的许多元素都可以使用观察者模式实现,例如菜单项、按钮、文本框等,当用户与这些元素交互时,它们会向注册的监听器发送事件通知,以触发相应的事件处理方法。
- 多线程编程:在Java中,观察者模式还可以用于多线程编程中,当一个线程发生了某些变化时,它可以向其他线程发送通知,以实现线程间的协作和同步。
代码实现
这个示例中,ConcreteSubject 实现了 Subject 接口,它维护了一个 observers 列表,用于保存注册的观察者对象。当被观察者发生变化时,它会遍历观察者列表,调用每个观察者的 update 方法。
ConcreteObserver 实现了 Observer 接口,它可以接收来自被观察者的通知,并执行相应的操作。
在测试类 ObserverPatternDemo 中,我们创建了一个具体的被观察者对象 ConcreteSubject,并注册了两个具体的观察者对象 observer1 和 observer2。当被观察者发生变化时,它会通知所有注册的观察者对象,并调用它们的 update 方法。
// 观察者接口
interface Observer {
void update(String message);
}
// 被观察者接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String message);
}
// 具体的被观察者实现类
class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
// 具体的观察者实现类
class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
// 测试类
public class ObserverPatternDemo {
public static void main(String[] args) {
Subject subject = new ConcreteSubject();
Observer observer1 = new ConcreteObserver("Observer1");
Observer observer2 = new ConcreteObserver("Observer2");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.notifyObservers("Hello, World!");
}
}
使用小结
观察者模式的优点在于它提供了一种松耦合的方式,让观察者和主题之间的依赖关系变得更加灵活,同时也可以使得程序更易于扩展和维护。
观察者模式的应用场景包括:当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时;当一个对象的改变需要同时改变其他对象的时候;当一个对象的改变需要通知其他对象而又不希望与被通知对象形成紧耦合关系时。
适配器模式(Adapter Pattern)
适配器模式是一种结构型模式,它可以将一个类的接口转换成客户端所期望的另一种接口。适配器模式可以帮助开发人员在不修改现有代码的情况下,将不兼容的类组合在一起。适配器模式包括以下几个组成部分:
- 目标接口(Target Interface):客户端期望的接口。
- 适配器(Adapter):充当两个不兼容接口之间的桥梁,使得它们可以互相通信。
- 适配者(Adaptee):需要被适配的对象,它的接口与目标接口不兼容。
- 客户端(Client):使用目标接口的对象。
应用场景
- 当需要将一个已有的类或接口与另一个不兼容的类或接口进行协同工作时。
- 当需要对一个已有的类或接口进行修改,以满足客户端的需求时,但是不希望修改该类或接口的源代码。
- 当需要重新使用一个已有的类或接口,但是不能直接使用该类或接口的方法时。
代码实现
在这个示例中,我们有一个目标接口 Target 和一个不兼容的适配者 Adaptee,我们需要创建一个适配器 Adapter 来让它们能够一起工作。
适配器实现了目标接口 Target,并在构造函数中接受一个适配者对象 Adaptee,然后在实现目标接口的 request 方法中调用适配者的 specificRequest 方法。
在客户端中,我们创建了一个适配者对象 adaptee,并将其传递给适配器的构造函数创建一个适配器对象 adapter。最后,我们使用目标接口 Target 中定义的方法 request 来访问适配器,从而调用适配者的方法。
// 目标接口
interface Target {
void request();
}
// 适配者
class Adaptee {
void specificRequest() {
System.out.println("Adaptee specificRequest.");
}
}
// 适配器
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
// 客户端
public class AdapterPatternDemo {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target target = new Adapter(adaptee);
target.request();
}
}
使用小结
适配器模式是一种非常有用的设计模式,在 JDK 中被广泛应用,可以提供一致的接口,比如
- Java IO 流是一个常见的适配器模式的例子。它提供了一组标准的接口来访问各种类型的数据源,包括文件、网络连接、内存等等。每个数据源都有自己的接口,但是 Java IO 流可以将这些不同的接口转换为标准的接口,从而提供一致的访问方式。
- Java Servlet API 也是一个常见的适配器模式的例子。它定义了一组接口来处理 HTTP 请求和响应,包括 doGet()、doPost()、doPut() 等等。每个 Servlet 都必须实现这些接口,但是用户只需要实现其中的一部分即可。这些 Servlet 之间的适配工作由 Servlet 容器完成。
装饰器模式(Decorator Pattern)
装饰器模式是一种结构型模式,它可以允许开发人员在不修改现有对象的情况下,动态地添加新功能。装饰器模式通过将一个对象包装在另一个对象中来扩展它的行为,从而提高了代码的灵活性和可重用性。
应用场景
- 当需要在不修改现有对象结构的前提下增加新的功能或特性时,可以使用装饰器模式。这样可以保持原有代码的稳定性和兼容性,同时也可以增加代码的灵活性和可扩展性。
- 当需要动态地向对象添加或删除功能时,可以使用装饰器模式。这样可以在运行时动态地添加或删除功能,而不需要修改现有的代码。
- 当需要为多个对象添加相同的功能时,可以使用装饰器模式。这样可以将相同的功能封装在装饰器中,以便于复用和管理。
代码实现
该示例代码中,Shape 是一个接口,定义了一个 draw 方法,表示绘制图形的操作。Circle 是一个实现 Shape 接口的类,表示一个圆形。
ShapeDecorator 是一个装饰器抽象类,实现了 Shape 接口,并包含一个 Shape 类型的变量 decoratedShape,表示要装饰的对象。RedShapeDecorator 是一个具体的装饰器类,继承了 ShapeDecorator 类,并实现了 draw 方法,在绘制图形时添加了一个红色的边框。
在 main 方法中,我们创建了原始对象 Circle,以及两个装饰器对象 RedShapeDecorator,分别装饰了 Circle 和 Rectangle 对象。通过调用 draw 方法,我们可以看到对象被动态地添加了一个红色的边框,而不需要修改原有的代码。
// 定义接口
interface Shape {
void draw();
}
// 实现接口
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Shape: Circle");
}
}
// 装饰器抽象类
abstract class ShapeDecorator implements Shape {
protected Shape decoratedShape;
public ShapeDecorator(Shape decoratedShape){
this.decoratedShape = decoratedShape;
}
public void draw(){
decoratedShape.draw();
}
}
// 具体装饰器类
class RedShapeDecorator extends ShapeDecorator {
public RedShapeDecorator(Shape decoratedShape) {
super(decoratedShape);
}
@Override
public void draw() {
decoratedShape.draw();
setRedBorder(decoratedShape);
}
private void setRedBorder(Shape decoratedShape){
System.out.println("Border Color: Red");
}
}
// 测试代码
public class DecoratorPatternDemo {
public static void main(String[] args) {
// 创建原始对象
Shape circle = new Circle();
// 创建装饰器对象
Shape redCircle = new RedShapeDecorator(new Circle());
Shape redRectangle = new RedShapeDecorator(new Rectangle());
// 调用方法
System.out.println("Circle with normal border");
circle.draw();
System.out.println("\nCircle of red border");
redCircle.draw();
System.out.println("\nRectangle of red border");
redRectangle.draw();
}
}
使用小结
在实际应用中,装饰器模式经常用于图形界面(GUI)开发、输入/输出流处理、缓存机制、日志记录等领域,可以有效地提高程序的可扩展性和可维护性。比如
- 在 Java 中,装饰器模式被广泛应用于 Java IO 流中,以提供各种不同的功能,如缓存、压缩、加密等等。例如,可以使用 BufferedReader 来缓存读取文件的数据,使用 GZIPOutputStream 来压缩数据,使用 CipherOutputStream 来加密数据等等。
- Java Swing 组件是一个经典的装饰器模式的例子。它允许在运行时动态地向组件添加功能,如边框、背景、文本等等。例如,可以使用 BorderFactory 来向组件添加边框,使用 Color 来设置组件的背景颜色,使用 Font 来设置组件的字体等等。
- 在 Spring 框架中,装饰器模式被广泛应用于实现 AOP。AOP通过代理模式和装饰器模式实现。JDK 动态代理和 CGLIB 动态代理两种方式实现代理模式,使用装饰器模式对目标对象进行包装,从而实现通知 (Advice) 的织入。例如,可以使用 @Transactional 来添加事务处理的功能,使用 @Cacheable 来添加缓存处理的功能,等等。
抽象工厂模式(Abstract Factory Pattern)
抽象工厂模式是一种创建型设计模式,它提供了一种创建相关或依赖对象的接口,而无需指定它们的具体类。它的基本思想是:定义一个用于创建一系列相关或相互依赖对象的接口,而不需要指定他们的具体类。
在Java中,抽象工厂模式通常包括以下几个角色:
- AbstractFactory:抽象工厂,声明了创建产品对象的方法。
- ConcreteFactory:具体工厂,实现了创建产品对象的方法。
- AbstractProduct:抽象产品,声明了产品对象的共性接口。
- Product:具体产品,实现了抽象产品中的抽象方法,构成产品族。
- Client:客户端,通过调用工厂类的方法创建产品对象。
抽象工厂和工厂模式都是创建对象的设计模式,它们的主要区别什么呢?
- 目的不同:工厂模式用于创建一类产品对象的实例,而抽象工厂模式用于创建一组相关的产品对象实例。
- 实现方式不同:工厂模式中只有一个工厂类,该类负责创建所有的产品对象;而抽象工厂模式中有多个工厂类,每个工厂类负责创建一组相关的产品对象。
- 范围不同:工厂模式通常用于创建单个对象,而抽象工厂模式通常用于创建一组相关的对象
使用场景
- 当需要创建一组相关的产品对象,这些产品对象之间有共同的约束条件,需要一起使用时,可以使用抽象工厂模式。
- 当系统需要独立于产品的创建,组装和表示时,可以使用抽象工厂模式
- 当系统需要支持多个不同的产品族,且不希望依赖于具体产品类时,可以使用抽象工厂模式。
- 当系统需要在运行时切换不同的产品族时,可以使用抽象工厂模式。
- 当需要遵循“开闭原则”,即系统需要扩展新的产品族时,不需要修改已有代码,可以使用抽象工厂模式。
代码实现
假设有一个在线商店需要提供两种支付方式:信用卡支付和网银支付,每种支付方式又包含两种具体的实现:Visa 信用卡支付和 MasterCard 信用卡支付,以及支付宝网银支付和微信网银支付。
首先,定义支付方式的接口:
// 信用卡支付接口
public interface CreditCardPayment {
void pay(double amount);
}
// 网银支付接口
public interface OnlineBankingPayment {
void pay(double amount);
}
接着,定义具体的实现:
// Visa 信用卡支付
public class VisaCreditCardPayment implements CreditCardPayment {
@Override
public void pay(double amount) {
System.out.println("Visa credit card payment: #34; + amount);
}
}
// MasterCard 信用卡支付
public class MasterCardCreditCardPayment implements CreditCardPayment {
@Override
public void pay(double amount) {
System.out.println("MasterCard credit card payment: #34; + amount);
}
}
// 支付宝网银支付
public class AlipayOnlineBankingPayment implements OnlineBankingPayment {
@Override
public void pay(double amount) {
System.out.println("Alipay online banking payment: ¥" + amount);
}
}
// 微信网银支付
public class WeChatOnlineBankingPayment implements OnlineBankingPayment {
@Override
public void pay(double amount) {
System.out.println("WeChat online banking payment: ¥" + amount);
}
}
然后,定义抽象工厂类
// 抽象支付工厂
public abstract class PaymentFactory {
public abstract CreditCardPayment createCreditCardPayment();
public abstract OnlineBankingPayment createOnlineBankingPayment();
}
定义具体的支付工厂类:
// Visa 信用卡支付工厂
public class VisaPaymentFactory extends PaymentFactory {
@Override
public CreditCardPayment createCreditCardPayment() {
return new VisaCreditCardPayment();
}
@Override
public OnlineBankingPayment createOnlineBankingPayment() {
return new AlipayOnlineBankingPayment();
}
}
// MasterCard 信用卡支付工厂
public class MasterCardPaymentFactory extends PaymentFactory {
@Override
public CreditCardPayment createCreditCardPayment() {
return new MasterCardCreditCardPayment();
}
@Override
public OnlineBankingPayment createOnlineBankingPayment() {
return new WeChatOnlineBankingPayment();
}
}
最后,使用抽象工厂来创建支付对象
public class Client {
public static void main(String[] args) {
// 选择 Visa 信用卡支付工厂
PaymentFactory paymentFactory = new VisaPaymentFactory();
// 创建 Visa 信用卡支付对象
CreditCardPayment creditCardPayment = paymentFactory.createCreditCardPayment();
creditCardPayment.pay(100);
// 创建支付宝网银支付对象
OnlineBankingPayment onlineBankingPayment = paymentFactory.createOnlineBankingPayment();
onlineBankingPayment.pay(200);
// 选择 MasterCard 信用卡支付工厂
PaymentFactory paymentFactory2 = new MasterCardPaymentFactory();
// 创建 MasterCard 信用卡支付对象
CreditCardPayment creditCardPayment2 = paymentFactory2.createCreditCardPayment();
creditCardPayment2.pay(100);
// 创建微信网银支付对象
OnlineBankingPayment onlineBankingPayment2 = paymentFactory2.createOnlineBankingPayment();
onlineBankingPayment2.pay(200);
}
使用小结
Java 抽象工厂模式在很多框架和应用程序中都有广泛的应用。以下是一些具体的应用:
- Java 数据库连接框架 JDBC 中,使用抽象工厂模式来创建连接对象,例如 Connection、Statement 等。
- Java 中的 XML 处理器 DOM 和 SAX,也使用抽象工厂模式来创建解析器和生成器对象。
- Java 中的 Java Cryptography Architecture (JCA) 也使用抽象工厂模式,用于创建加密算法和密钥生成器对象。
总之,Java 抽象工厂模式可以在许多应用程序和框架中找到,它可以帮助您更好地组织和管理代码,提高代码的可扩展性和灵活性。
状态模式(State Pattern)
Java 状态模式是一种行为设计模式,它允许对象在内部状态改变时改变它的行为。状态模式通过将状态封装成一个对象来实现这一点,从而使得一个对象的行为取决于它的状态对象,而不是取决于对象本身。
使用场景
- 当一个对象的行为取决于它的状态,并且该对象的状态可能在运行时发生改变时,可以使用状态模式。
- 当一个对象需要根据不同的状态采取不同的行动时,可以使用状态模式。
- 当一个对象的代码中包含大量与状态相关的条件语句时,可以使用状态模式来简化代码。
代码实现
假设有一个订单对象,订单状态包括 "新建"、"处理中" 和 "完成" 三种状态,订单状态会随着订单处理的不同而改变。
首先,我们需要定义订单状态的接口:
public interface OrderState {
void processOrder(Order order);
}
然后,我们定义订单状态的具体实现类,分别对应三种不同的状态
public class NewOrder implements OrderState {
public void processOrder(Order order) {
// 处理新建状态下的订单
System.out.println("Processing new order.");
order.setState(new ProcessingOrder());
}
}
public class ProcessingOrder implements OrderState {
public void processOrder(Order order) {
// 处理处理中状态下的订单
System.out.println("Processing order in progress.");
order.setState(new CompletedOrder());
}
}
public class CompletedOrder implements OrderState {
public void processOrder(Order order) {
// 处理完成状态下的订单
System.out.println("Processing completed order.");
}
}
最后,我们定义订单对象,并在订单对象中实现状态的切换:
public class Order {
private OrderState state;
public Order() {
state = new NewOrder();
}
public void setState(OrderState state) {
this.state = state;
}
public void processOrder() {
state.processOrder(this);
}
}
使用上面定义的订单对象和订单状态对象来处理订单了:
Order order = new Order();
order.processOrder(); // Output: Processing new order.
order.processOrder(); // Output: Processing order in progress.
order.processOrder(); // Output: Processing completed order.
使用了状态模式来实现订单对象的状态转换,可以动态地改变订单对象的状态,并且无需修改订单对象本身的代码。使代码更加灵活和易于维护。
使用小结
状态模式在 Java 中的应用非常广泛,比如,线程池也是一个常见的状态机。通过使用状态模式,我们可以轻松地管理线程池的状态,并实现线程池状态的动态变更。
策略模式(Strategy Pattern)
Java 策略模式是一种行为型设计模式,它定义了一系列的算法,将每个算法都封装起来,并使它们可以互相替换,从而使算法的变化不会影响到使用算法的客户端。这种模式可以使算法的变化更加灵活和可控,同时也可以提高代码的可读性和可维护性。
使用场景
- 当一个对象具有多种行为或算法,并且需要在运行时动态选择其中一种时,策略模式可以派上用场。
- 当需要对同一种行为或算法进行多种实现时,我们可以使用策略模式。
- 当需要减少大量的 if/else 语句时,我们可以使用策略模式来优化代码。
代码实现
假设有一个电商网站,它需要根据不同的促销策略来计算订单的价格。促销策略包括打折、满减、直降等等。
首先,我们定义一个促销策略接口,其中包含一个计算订单价格的方法
public interface PromotionStrategy {
double calculatePrice(double price);
}
然后,我们实现具体的促销策略,例如打折、满减和直降策略:
public class DiscountPromotionStrategy implements PromotionStrategy {
private double discount;
public DiscountPromotionStrategy(double discount) {
this.discount = discount;
}
public double calculatePrice(double price) {
return price * (1 - discount);
}
}
public class FullReductionPromotionStrategy implements PromotionStrategy {
private double threshold;
private double reduction;
public FullReductionPromotionStrategy(double threshold, double reduction) {
this.threshold = threshold;
this.reduction = reduction;
}
public double calculatePrice(double price) {
return price >= threshold ? price - reduction : price;
}
}
public class DirectReductionPromotionStrategy implements PromotionStrategy {
private double reduction;
public DirectReductionPromotionStrategy(double reduction) {
this.reduction = reduction;
}
public double calculatePrice(double price) {
return price - reduction;
}
}
最后,我们定义一个订单类,其中包含一个 PromotionStrategy 对象和一个 calculatePrice 方法:
public class Order {
private double price;
private PromotionStrategy promotionStrategy;
public Order(double price, PromotionStrategy promotionStrategy) {
this.price = price;
this.promotionStrategy = promotionStrategy;
}
public double calculatePrice() {
return promotionStrategy.calculatePrice(price);
}
}
创建一个订单,并指定不同的促销策略来计算订单价格:
Order order = new Order(100, new DiscountPromotionStrategy(0.1));
double price = order.calculatePrice(); // 90
order = new Order(200, new FullReductionPromotionStrategy(150, 50));
price = order.calculatePrice(); // 150
order = new Order(300, new DirectReductionPromotionStrategy(50));
price = order.calculatePrice(); // 250
使用小结
策略模式是 Java 中经常使用的一种设计模式,它可以在很多场景中使用,比如:
- 可以使用策略模式定义多种日志记录方式(例如控制台输出、文件输出、网络传输等)并动态选择使用哪种方式进行日志记录。
- 可以使用策略模式定义多种支付方式(例如微信支付、支付宝支付、银行卡支付等)并让用户动态选择使用哪种支付方式进行支付。
- 可以使用策略模式定义多种加密算法(例如对称加密、非对称加密、哈希算法等)并让用户动态选择使用哪种加密算法进行数据加密。
通过使用策略模式,我们可以更灵活地实现不同的功能需求,并让用户根据实际情况选择最合适的策略进行操作
模板方法模式(Template Method Pattern)
Java 模板方法模式是一种行为型设计模式,它定义了一个算法的骨架,并将一些步骤延迟到子类中实现。模板方法模式使得子类可以在不改变算法结构的情况下重定义算法中的某些步骤。
使用场景
- 算法骨架固定:如果一个算法的基本结构已经固定,但具体的实现步骤可能因为不同的场景而不同,这个时候可以使用模板方法模式。
- 实现代码复用:如果有多个类的某些方法结构相似,但是实现细节不同,这个时候可以将这些相同的结构抽象到父类中,由子类来实现不同的细节。
- 简化代码实现:模板方法模式可以将复杂的代码实现分离成几个简单的步骤,从而降低代码实现的难度和复杂度。
- 框架和库的设计:模板方法模式是设计框架和库的重要方式之一,它可以提供统一的接口和标准的实现流程,方便用户进行扩展和定制
代码实现
AbstractClass 是一个抽象类,它定义了算法的骨架,其中 templateMethod() 是模板方法,它定义了算法的流程,由一些抽象方法 primitiveOperation1() 和 primitiveOperation2() 组成。
ConcreteClass 是 AbstractClass 的具体子类,它实现了抽象方法,定义了具体的算法细节。在客户端使用时,创建 ConcreteClass ,然后调用其 templateMethod() 方法,即可完成算法的执行。
// 抽象类,定义算法骨架
public abstract class AbstractClass {
// 模板方法,定义算法流程
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
}
// 抽象方法1,由子类实现
public abstract void primitiveOperation1();
// 抽象方法2,由子类实现
public abstract void primitiveOperation2();
}
// 具体子类,实现具体的算法细节
public class ConcreteClass extends AbstractClass {
@Override
public void primitiveOperation1() {
System.out.println("ConcreteClass.primitiveOperation1()");
}
@Override
public void primitiveOperation2() {
System.out.println("ConcreteClass.primitiveOperation2()");
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
需要注意的是,在模板方法模式中,模板方法通常被声明为 final,以防止子类对其进行重写。同时,由于模板方法是一个抽象方法,因此在实现时需要注意不同抽象方法的实现顺序,以确保算法的正确性。
使用小结
很多框架类的设计都采用了模板方法模式,例如 Spring 中的 JdbcTemplate,其中定义了一套执行 SQL 的流程,并由子类实现具体的 SQL 语句
命令模式(Command Pattern)
Java命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而使您可以将不同的请求参数化,将它们放入队列中,或者将请求记录在日志中,以及支持可撤销的操作。这种模式的主要组成部分包括命令接口、具体命令、调用者和接收者。
使用场景
- 当需要将请求发送者和接收者解耦时,可以使用命令模式,因为命令对象充当请求发送者和接收者之间的媒介。
- 当需要支持命令的撤销(undo)操作时,可以使用命令模式,因为每个命令对象都可以保存执行所需的状态。
- 当需要在不同的时间指定、排队或记录请求时,可以使用命令模式,因为命令对象可以像其他对象一样传递、存储和操作。
- 当需要实现日志记录功能时,可以使用命令模式,因为命令对象可以保存操作的历史记录,以便将来需要时进行检查和恢复。
- 当需要将一组简单的操作组合成更复杂的操作时,可以使用命令模式,因为您可以将多个命令组合成一个复合命令。
代码实现
假设有一个电视机,它具有开机、关机和切换频道等操作
定义命令接口和具体命令类
public interface Command {
void execute();
}
public class TvOnCommand implements Command {
private TV tv;
public TvOnCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.on();
}
}
public class TvOffCommand implements Command {
private TV tv;
public TvOffCommand(TV tv) {
this.tv = tv;
}
public void execute() {
tv.off();
}
}
public class TvChangeChannelCommand implements Command {
private TV tv;
private int channel;
public TvChangeChannelCommand(TV tv, int channel) {
this.tv = tv;
this.channel = channel;
}
public void execute() {
tv.changeChannel(channel);
}
}
定义接收者类
public class TV {
public void on() {
System.out.println("TV is on");
}
public void off() {
System.out.println("TV is off");
}
public void changeChannel(int channel) {
System.out.println("TV channel is changed to " + channel);
}
}
定义调用者类
public class RemoteController {
private Command onCommand;
private Command offCommand;
private Command changeChannelCommand;
public RemoteController(Command onCommand, Command offCommand, Command changeChannelCommand) {
this.onCommand = onCommand;
this.offCommand = offCommand;
this.changeChannelCommand = changeChannelCommand;
}
public void turnOn() {
onCommand*ex.e**cute();
}
public void turnOff() {
offCommand*ex.e**cute();
}
public void changeChannel(int channel) {
changeChannelCommand*ex.e**cute();
}
}
使用命令模式
TV tv = new TV();
Command onCommand = new TvOnCommand(tv);
Command offCommand = new TvOffCommand(tv);
Command changeChannelCommand = new TvChangeChannelCommand(tv, 2);
RemoteController remoteController = new RemoteController(onCommand, offCommand, changeChannelCommand);
remoteController.turnOn(); // 输出 "TV is on"
remoteController.changeChannel(5); // 输出 "TV channel is changed to 5"
remoteController.turnOff(); // 输出 "TV is off"
上述代码中,RemoteController 充当了调用者角色,TvOnCommand、TvOffCommand 和 TvChangeChannelCommand 充当了具体命令角色,而 TV 充当了接收者角色。
当 RemoteController 调用某个命令的 execute() 方法时,具体命令将通过接收者TV来执行相应的操作。通过这种方式,调用者和接收者之间的耦合得以解除。
使用小结
- 可以使用命令模式来实现撤销和恢复操作。例如,在编辑器中,可以使用命令模式来实现对文本的撤销和恢复操作。
- 可以使用命令模式来实现日志记录。例如,在Web应用中,可以将每个请求封装成一个命令对象,并将命令对象记录到日志文件中。
- 可以使用命令模式来实现消息队列。例如,在JMS(Java消息服务)中,可以将每个消息封装成一个命令对象,并将命令对象加入到消息队列中。
原型模式(Prototype Pattern)
Java原型模式(Prototype Pattern)是一种创建型设计模式,其目的是通过复制现有对象来创建新的对象。
使用场景
- 当对象创建的过程比较耗时或者比较复杂,例如需要进行复杂的计算或者涉及到网络请求等操作,可以使用原型模式来避免重复的初始化过程。
- 当需要创建的对象需要和其他对象进行协同工作时,例如需要创建一个包含多个对象的组合对象,可以使用原型模式来复制一个已有的组合对象,然后进行修改来创建新的组合对象。
- 当需要动态地增加或者删除一些对象时,可以使用原型模式来复制一个已有的对象,然后进行修改来创建新的对象。
- 当需要保护对象的复杂状态时,例如当一个对象的创建需要大量的数据初始化时,可以使用原型模式来保护这些数据,避免因为对象的复制而产生意外的副作用。
代码实现
// 定义一个原型接口
interface Prototype {
public Prototype clone();
}
// 具体的原型类
class ConcretePrototype implements Prototype {
public Prototype clone() {
return new ConcretePrototype();
}
}
// 客户端代码
class Client {
public static void main(String[] args) {
Prototype prototype = new ConcretePrototype();
Prototype clone = prototype.clone();
}
}
使用小结
- Java中的Object类实现了Cloneable接口,这就意味着Java中的任何对象都可以实现原型模式。通过实现Cloneable接口,并重写Object类中的clone()方法,可以实现原型模式。例如 ArrayList、HashMap 等集合类都实现了Cloneable 接口,可以通过复制现有对象来创建新的对象。
- Java中的线程池也是使用了原型模式,线程池中的每个线程都是从原型线程中复制而来,而不是每次创建新的线程。
建造者模式(Builder Pattern)
Java建造者模式(Builder Pattern)是一种创建型设计模式,它通过将一个复杂的对象的创建过程分解成多个简单的步骤,并将这些步骤封装到一个Builder对象中,从而可以灵活地创建不同的对象
使用场景
- 当需要创建复杂的对象,并且对象的构建过程包含多个步骤时,可以使用建造者模式
- 当需要创建不同配置的对象时,可以使用建造者模式。
- 当需要创建可变的对象时,可以使用建造者模式。
代码实现
public class User {
private String name;
private String email;
private int age;
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
}
public static class Builder {
private String name;
private String email;
private int age;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setEmail(String email) {
this.email = email;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public User build() {
return new User(this);
}
}
}
建造者模式创建User对象
User user = new User.Builder()
.setName("John")
.setEmail("john@example.com")
.setAge(30)
.build();
使用小结
- StringBuilder 类就是一个建造者模式的典型应用。它允许使用者逐步构建一个字符串,并最终返回构建好的字符串。
- Guava 库是一个常用的Java库,其中的 ImmutableList.Builder 类也是一个典型的建造者模式应用。它允许使用者逐步构建一个不可变的列表,并最终返回构建好的列表。
总之,建造者模式在Java中的应用非常广泛,特别是在构建复杂对象时,它可以使构建过程更加灵活、可扩展和可维护。
桥接模式(Bridge Pattern)
Java桥接模式(Bridge Pattern)是一种结构型设计模式,它将一个对象的抽象部分与它的实现部分分离,使它们可以独立地变化。桥接模式的目的是将抽象与实现解耦,从而实现系统的灵活性和可扩展性。
使用场景
- 当一个类存在多个实现版本时,可以使用桥接模式将其分离开来,从而使得这些实现版本可以独立地变化。
- 当需要将一个抽象部分与它的实现部分分离开来,以便它们可以独立地进行修改和扩展时,可以使用桥接模式。
- 当需要在抽象类中定义抽象方法,以便在不同的实现类中实现具体的行为时,可以使用桥接模式。
- 当需要在运行时动态地改变一个对象的实现时,可以使用桥接模式。
- 当需要将一个大类拆分成多个独立的层级时,可以使用桥接模式
代码实现
假设有一个图形绘制应用程序,它支持绘制不同颜色的图形。图形可以是矩形、圆形等等。我们可以使用桥接模式来将颜色和图形分离开来。
首先,我们定义一个颜色接口 Color 和它的两个实现类 Red 和 Blue
public interface Color {
public void applyColor();
}
public class Red implements Color {
@Override
public void applyColor() {
System.out.println("Applying red color");
}
}
public class Blue implements Color {
@Override
public void applyColor() {
System.out.println("Applying blue color");
}
}
然后,我们定义一个抽象的图形类 Shape,它包含一个颜色接口的引用
public abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
public abstract void draw();
}
接下来,定义两个实现类 Rectangle 和 Circle,它们继承自 Shape 类
public class Rectangle extends Shape {
public Rectangle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("Drawing rectangle. ");
color.applyColor();
}
}
public class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public void draw() {
System.out.print("Drawing circle. ");
color.applyColor();
}
}
现在,我们可以使用这些类来绘制不同颜色的图形
Color red = new Red();
Color blue = new Blue();
Shape rectangle = new Rectangle(red);
rectangle.draw();//Drawing rectangle. Applying red color
Shape circle = new Circle(blue);
circle.draw();//Drawing circle. Applying blue color
Shape 类充当桥接模式中的抽象部分,Color 接口充当实现部分。使用桥接模式,我们可以轻松地添加新的图形和颜色类,而不会影响现有的代码。
使用小结
一个经典的桥接模式例子是 JDBC API。在 JDBC API 中,DriverManager 充当桥接模式中的抽象部分,它负责管理多个 JDBC 驱动程序的实现部分。开发人员可以通过 DriverManager 类来连接数据库,而无需了解底层数据库驱动程序的具体实现。
过滤器模式(Filter Pattern)
Java 过滤器模式(Filter Pattern)是一种结构型设计模式,它允许你使用不同的标准来过滤一组对象,从而去除其中不需要的元素。
使用场景
- 当你需要从一个集合中过滤出一部分元素时。
- 当你需要根据不同的标准来过滤同一个集合时。
- 当你需要在一个集合中根据不同标准来组合过滤器时。
代码实现
假设有一个 Person 类,包含姓名、年龄、性别等属性,我们可以定义一个过滤器接口 Filter,其中包含一个过滤方法 filter,用于过滤出符合某种条件的人。
public interface Filter {
List<Person> filter(List<Person> persons);
}
定义实现了 Filter 接口的具体过滤器
public class MaleFilter implements Filter {
@Override
public List<Person> filter(List<Person> persons) {
return persons.stream()
.filter(person -> person.getGender().equals("MALE"))
.collect(Collectors.toList());
}
}
public class FemaleFilter implements Filter {
@Override
public List<Person> filter(List<Person> persons) {
return persons.stream()
.filter(person -> person.getGender().equals("FEMALE"))
.collect(Collectors.toList());
}
}
public class AgeFilter implements Filter {
@Override
public List<Person> filter(List<Person> persons) {
return persons.stream()
.filter(person -> person.getAge() > 18)
.collect(Collectors.toList());
}
}
最后,我们可以定义一个 FilterChain 类,用于组合多个过滤器
public class FilterChain implements Filter {
private List<Filter> filters;
public FilterChain(List<Filter> filters) {
this.filters = filters;
}
@Override
public List<Person> filter(List<Person> persons) {
List<Person> result = persons;
for (Filter filter : filters) {
result = filter.filter(result);
}
return result;
}
}
这样,我们就可以使用过滤器来过滤出符合不同条件的人了
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("Tom", 20, "MALE"));
persons.add(new Person("Lucy", 19, "FEMALE"));
persons.add(new Person("John", 17, "MALE"));
persons.add(new Person("Lily", 21, "FEMALE"));
Filter maleFilter = new MaleFilter();
Filter femaleFilter = new FemaleFilter();
Filter ageFilter = new AgeFilter();
FilterChain maleAndAgeFilter = new FilterChain(Arrays.asList(maleFilter, ageFilter));
FilterChain femaleAndAgeFilter = new FilterChain(Arrays.asList(femaleFilter, ageFilter));
System.out.println("Male and age filter:");
maleAndAgeFilter.filter(persons).forEach(System.out::println);
//Person(name=Tom, age=20, gender=MALE)
//Person(name=Lily, age=21, gender=FEM
System.out.println("Female and age filter:");
femaleAndAgeFilter.filter(persons).forEach(System.out::println);
//Person(name=Lily, age=21, gender=FEMALE)
}
使用小结
滤器模式的核心是 Filter 接口,该接口包含了一个方法 filter(),用于对对象进行过滤。根据具体实现,filter() 方法可以接受一个对象作为参数,然后根据特定的标准判断该对象是否应该被过滤掉。过滤器模式在Java中可以应用于许多场景,例如:
- Web开发中,对HTTP请求进行过滤,例如身份验证,日志记录等。
- 在Spring框架中,过滤器可以用于过滤请求并对请求进行预处理,例如对请求参数进行验证等。
- Java 中的 Stream API 提供了丰富的过滤器方法,如 filter()、distinct()、map() 等
过滤器模式可以用于任何需要对数据进行过滤和处理的场景。