简介:Java设计模式和架构图是构建高效、可维护系统的基石。设计模式提供了解决常见编程问题的模板,包括创建型、结构型和行为型模式,而架构图则展示了系统的整体布局和设计风格。本资源包括对23种设计模式的分类和解释,以及30种Java技术框架图的探讨,如MVC、微服务、分层架构、SOA、CQRS、DDD和Event Sourcing等。通过实践这些模式和架构风格,开发者可以编写更优雅、更具扩展性的代码,并在实际项目中做出明智的技术选择。
1. Java设计模式概念与分类
1.1 设计模式简介
设计模式是软件工程中的一套被广泛认可的解决特定问题的最佳实践。它们源自于经验丰富的软件开发人员的智慧,用于应对软件设计和开发过程中遇到的重复问题。设计模式不是直接的代码实现,而是一种解决方案的模板,可以适用于多种不同情况。
1.2 设计模式的重要性
对设计模式的理解和掌握能够使开发人员在软件设计和编码过程中更加高效,减少错误,并增加软件系统的可维护性和可扩展性。设计模式通过提供通用的解决方案框架,可以促进团队沟通,提高开发效率。
1.3 设计模式的分类
设计模式通常被分类为创建型模式、结构型模式和行为型模式。创建型模式涉及对象的创建机制,结构型模式关注对象和类的组合,行为型模式则专注于对象之间的通信。理解这些分类有助于在实际开发中根据问题场景选择合适的模式。
通过上述内容,我们为读者揭开了设计模式的序幕,接下来章节将深入探讨各类设计模式的原理与应用。
2. 23种GOF设计模式详解
2.1 创建型模式
创建型模式关注对象的创建过程,旨在创建对象的同时隐藏创建逻辑,而不是使用new直接实例化对象。这样做可以降低代码之间的耦合度,提高系统的可扩展性和灵活性。
2.1.1 单例模式的原理与应用
单例模式是最简单的设计模式之一。它提供了全局访问点,使得系统中只有一个实例被创建,而且提供一个全局访问点供外部获取该实例。单例模式常用于管理配置信息、工具类等不需要有多个实例的场景。
单例模式的关键在于确保只有一个实例,并提供全局访问它的方法。以下是单例模式的一个简单实现:
public class Singleton {
// 定义私有静态变量,防止被引用
private static Singleton instance;
// 私有构造函数,防止被实例化
private Singleton() {
}
// 提供一个公有静态方法,返回实例对象
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
代码逻辑解读
- 构造方法是私有的,这样就无法通过外部调用构造器来创建类的实例。
-
getInstance
方法为公有静态方法,是外界访问实例的唯一入口。这个方法会首先检查实例是否已经被创建,如果未创建,则创建一个新的实例,否则直接返回已创建的实例。
单例模式还有许多变种,包括懒汉式、饿汉式、双重检查锁定(DCL)、静态内部类、枚举等方式,每种方式在多线程环境下的线程安全性和性能都有所不同。
2.1.2 工厂模式的变种与选择
工厂模式是创建型模式的一种,它通过定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式是一种常用的创建对象的方法,可以用来实现解耦。
工厂模式通常分为三种类型:
- 简单工厂(Simple Factory):用于创建某一类产品,有一个工厂类决定创建出哪一种产品类的实例。
- 工厂方法(Factory Method):定义一个创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂(Abstract Factory):创建一系列相关或依赖对象,不需要指定这些对象具体的类。
工厂方法模式应用
工厂方法模式是一种创建型模式,通过定义一个创建对象的接口,由子类决定要实例化的类是哪一个。当一个类不知道它所需要的对象的类时,可以在工厂方法模式中,这种情况只需要知道具体工厂类是谁就可以创建出需要的对象,而不需要知道这些对象是如何创建以及何时创建。
public interface Product {
}
public class ConcreteProductA implements Product {
}
public class ConcreteProductB implements Product {
}
public interface Creator {
Product factoryMethod();
}
public class ConcreteCreatorA implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductA();
}
}
public class ConcreteCreatorB implements Creator {
@Override
public Product factoryMethod() {
return new ConcreteProductB();
}
}
2.1.3 抽象工厂模式在实际开发中的应用
抽象工厂模式提供了一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。抽象工厂模式是一种创建型设计模式,适用于有多个产品族,且每个产品族中包含多个产品的场景。
该模式的目的是使一个系列的产品相互配套,而不依赖于它们具体的类。抽象工厂模式隐藏了具体类的实例化细节,使得切换不同系列的产品变得非常方便。
抽象工厂模式应用
// 抽象产品
interface AbstractProductA {
}
// 具体产品
class ConcreteProductA1 implements AbstractProductA {
}
class ConcreteProductA2 implements AbstractProductA {
}
// 抽象产品
interface AbstractProductB {
}
// 具体产品
class ConcreteProductB1 implements AbstractProductB {
}
class ConcreteProductB2 implements AbstractProductB {
}
// 抽象工厂
interface AbstractFactory {
AbstractProductA createProductA();
AbstractProductB createProductB();
}
// 具体工厂
class ConcreteFactory1 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public AbstractProductB createProductB() {
return new ConcreteProductB1();
}
}
class ConcreteFactory2 implements AbstractFactory {
@Override
public AbstractProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public AbstractProductB createProductB() {
return new ConcreteProductB2();
}
}
抽象工厂模式的主要优点是它隔离了具体类的生成,使得客户端并不需要知道什么被创建。同时,当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。其缺点在于增加新的产品变得困难,因为增加新的产品时需要修改抽象工厂的接口,这可能会导致所有实现了抽象工厂接口的类都需要进行修改。
3. 创建型模式实践
创建型模式专注于对象创建机制,提供了创建对象的最佳方式。通过创建型模式,可以提高代码的灵活性和可复用性。在本章节中,我们将深入探讨创建型模式的实际应用,特别关注单例模式的线程安全实现、工厂模式在复杂系统中的应用,以及建造者模式在UI组件构建中的应用。
3.1 单例模式的线程安全实现
单例模式确保一个类只有一个实例,并提供一个全局访问点。在多线程环境中,线程安全是实现单例模式时必须考虑的问题。我们将探讨双重检查锁定模式和静态内部类模式,这两种模式在保证线程安全的同时,也提供了高效的实例获取方式。
3.1.1 双重检查锁定模式的优化
双重检查锁定模式(Double-Checked Locking Pattern)是一种延迟初始化技术,用于减少多线程环境下的同步开销。但它的实现并不简单,需要特别注意确保线程安全。
public class Singleton {
// 使用volatile关键字确保实例的可见性和防止指令重排
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;
}
}
参数说明:
-
volatile
:确保instance
的读写操作都是直接针对主内存的,保证了对所有线程的可见性。 -
synchronized
:保证了并发环境下代码块内的操作是原子性的,防止多个线程同时执行初始化代码。
执行逻辑说明:
- 第一次检查
instance
是否已创建,若未创建则进入同步块。 - 同步块内的第二次检查是关键,用于避免在多个线程到达第一个检查点后,都开始初始化对象。
- 只有一个线程会执行实例的创建,其他线程将等待该线程完成初始化后再进行对象的获取。
3.1.2 静态内部类模式的线程安全分析
静态内部类模式是一种简洁且线程安全的实现方式。它利用了类加载机制的特性,确保单例只被创建一次。
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
参数说明:
-
SingletonHolder
:这是一个私有的静态内部类,JVM保证了内部类的加载是线程安全的,并且只有在第一次使用时才会进行初始化。 -
final
:INSTANCE
是final
类型的,这意味着一旦被初始化后,将不能被修改,确保了单例的不变性。
执行逻辑说明:
- 当
Singleton
类被加载时,SingletonHolder
类并不会被初始化,因为它是一个内部类。 - 只有当调用
getInstance()
方法时,SingletonHolder
才会被加载,并且JVM保证了线程安全的类初始化。 - 由于JVM的类初始化机制是线程安全的,因此我们得到的是一个线程安全的单例实例。
在本章的后续部分中,我们将继续探索工厂模式和建造者模式在不同场景下的实践应用,通过具体示例加深对创建型模式的理解。
4. 结构型模式应用
结构型模式关注的是如何组合类和对象以获得更大的结构。本章将深入探讨如何在实际开发中应用常见的结构型设计模式,包括代理模式、装饰器模式以及适配器模式,并讨论它们在服务治理、系统设计和第三方库集成中的实践案例。
4.1 代理模式在服务治理中的实践
代理模式是一种非常流行的结构型设计模式,它允许添加一个额外的抽象层来控制对象的访问。在服务治理中,代理模式可以用来实现负载均衡、权限控制、安全检查等功能。
4.1.1 RPC框架中动态代理的使用
在远程过程调用(RPC)框架中,动态代理模式经常被用来实现客户端与服务端之间的透明通信。以Java中的JDK动态代理为例,当调用远程服务时,可以在客户端动态创建一个代理类,该代理类负责发送网络请求到服务端,并处理响应数据。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxyDemo {
interface HelloInterface {
void sayHello(String name);
}
static class Hello implements HelloInterface {
@Override
public void sayHello(String name) {
System.out.println("Hello, " + name);
}
}
static class DynamicProxy implements InvocationHandler {
private Object originalObj;
DynamicProxy(Object originalObj) {
this.originalObj = originalObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("Before invoking " + method.getName());
Object result = method.invoke(originalObj, args);
System.out.println("After invoking " + method.getName());
return result;
}
public static Object newInstance(Object obj) {
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new DynamicProxy(obj));
}
}
public static void main(String[] args) {
HelloInterface hello = new Hello();
HelloInterface helloProxy = (HelloInterface) DynamicProxy.newInstance(hello);
helloProxy.sayHello("World");
}
}
以上代码演示了如何使用JDK动态代理。 DynamicProxy
类实现了 InvocationHandler
接口,它在目标方法调用前后添加了额外的逻辑。 newInstance
方法利用 Proxy.newProxyInstance
来创建代理实例。
4.1.2 静态代理在访问控制中的应用
静态代理与动态代理不同,它在编译时就已经明确指定了代理类。在访问控制场景下,静态代理可以实现权限校验、日志记录等横切关注点,从而将核心业务逻辑和辅助功能分离。
public class AccessControllerProxy {
private Object service;
public AccessControllerProxy(Object service) {
this.service = service;
}
public void execute() {
// 检查用户权限
boolean hasPermission = checkPermission();
if (hasPermission) {
service.execute(); // 执行服务
} else {
throw new RuntimeException("No permission");
}
}
private boolean checkPermission() {
// 实现权限检查逻辑
return true;
}
}
public class Service {
public void execute() {
System.out.println("Service is running");
}
}
public class Client {
public static void main(String[] args) {
Service realService = new Service();
AccessControllerProxy proxy = new AccessControllerProxy(realService);
proxy.execute();
}
}
在这个简单的示例中, AccessControllerProxy
类代表了静态代理的实现,它在执行核心业务逻辑之前进行权限检查。这种方式可以有效地在不修改业务逻辑类的情况下,实现对服务的访问控制。
表格:动态代理与静态代理的比较
特性 | 动态代理 | 静态代理 |
---|---|---|
代理创建时机 | 运行时动态创建 | 编译时确定 |
代理类 | 由框架在运行时生成 | 明确指定,编写的 |
灵活性 | 更灵活,适用于接口数量多且动态添加场景 | 较静态,适用于已知具体接口和类的场景 |
性能开销 | 性能开销稍大,因为需要动态生成字节码 | 性能较优,无需动态生成字节码 |
适用性 | 灵活、适用于复杂的场景 | 简单、易于理解和维护 |
通过表格可以看出,动态代理提供了更好的灵活性,适用于在运行时需要动态添加代理逻辑的场景。而静态代理则在代码可读性和维护性上有所优势,适用于代理逻辑固定的场景。
4.2 装饰器模式与Java I/O的结合
装饰器模式允许在运行时动态地添加和移除对象的行为。在Java I/O库中,装饰器模式被广泛用于提供灵活、可扩展的流处理机制。 FilterInputStream
和 FilterOutputStream
就是装饰器模式的应用。
4.2.1 装饰器模式增强I/O流功能
通过装饰器模式,我们可以为I/O流添加额外的功能,比如缓冲、数据转换、数据压缩等,而不改变原有流的接口和实现。例如,为了提供缓冲功能,我们可以创建一个 BufferedInputStream
装饰器。
import java.io.*;
public class DecoratorPatternInIO {
public static void main(String[] args) throws IOException {
// 原始输入流
FileInputStream fileInputStream = new FileInputStream("example.txt");
// 使用装饰器增加缓冲功能
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
int data = bufferedInputStream.read();
while (data != -1) {
// 输出读取的字节
System.out.print((char) data);
data = bufferedInputStream.read();
}
// 关闭流
bufferedInputStream.close();
}
}
4.2.2 输入输出流的多层次装饰
在Java I/O中,我们可以组合多个装饰器来实现多层次的装饰。例如,我们可以创建一个过滤流来实现数据的转换,并在上面再添加一个缓冲装饰器。
// 创建一个过滤流,实现数据转换(例如,转换为大写)
InputStream in = new FileInputStream("example.txt");
InputStream upperCaseInputStream = new FilterInputStream(in) {
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase((char) c));
}
};
// 在过滤流的基础上增加缓冲功能
BufferedInputStream bis = new BufferedInputStream(upperCaseInputStream);
通过多层次的装饰,我们可以得到一个具有多个功能的流对象,但对客户端代码来说,它只是简单地操作一个流对象。
4.3 适配器模式在系统兼容性中的作用
适配器模式允许将一个类的接口转换成客户期望的另一个接口。在系统集成中,适配器模式经常被用来解决系统兼容性问题,比如接口适配和类适配。
4.3.1 类适配器与对象适配器的比较
适配器模式主要有两种实现方式:类适配器和对象适配器。类适配器通过继承需要适配的类,而对象适配器则通过组合来实现适配。
特性 | 类适配器 | 对象适配器 |
---|---|---|
实现方式 | 继承被适配类 | 组合被适配类 |
重用性 | 重用性较差,因为使用了继承 | 重用性较好,因为使用了组合 |
灵活性 | 较灵活,可以在子类中覆盖父类的方法 | 较灵活,可以适应不同的被适配类 |
兼容性 | 需要被适配类的源代码 | 不需要被适配类的源代码 |
在类适配器中,由于使用了继承,所以对被适配类的重用性较差。而对象适配器则使用组合方式,可以适应更多种类的被适配对象,提供了更好的灵活性。
4.3.2 适配器模式在第三方库集成中的应用
适配器模式在第三方库的集成中也发挥着重要作用。举个例子,在集成第三方数据库连接池时,我们可能需要将第三方库提供的接口适配到我们的应用程序接口上。
public class DatabaseConnectionPoolAdapter implements MyConnectionPool {
private ThirdPartyConnectionPool thirdPartyPool;
public DatabaseConnectionPoolAdapter(ThirdPartyConnectionPool thirdPartyPool) {
this.thirdPartyPool = thirdPartyPool;
}
@Override
public Connection getConnection() {
// 转换第三方连接池返回的连接对象
return new MyConnectionWrapper(thirdPartyPool.getConnection());
}
@Override
public void releaseConnection(Connection connection) {
// 转换并释放连接
ThirdPartyConnection thirdPartyConnection = ((MyConnectionWrapper) connection).unwrap();
thirdPartyPool.releaseConnection(thirdPartyConnection);
}
private class MyConnectionWrapper implements Connection {
private ThirdPartyConnection thirdPartyConnection;
public MyConnectionWrapper(ThirdPartyConnection thirdPartyConnection) {
this.thirdPartyConnection = thirdPartyConnection;
}
public ThirdPartyConnection unwrap() {
return thirdPartyConnection;
}
// 实现其他Connection接口的方法
}
}
在这个示例中, DatabaseConnectionPoolAdapter
类将第三方的 ThirdPartyConnectionPool
适配到我们自己的 MyConnectionPool
接口。通过这种方式,我们可以保持接口的一致性,同时利用第三方库的功能。
通过上述内容,我们可以看到结构型模式在服务治理、系统设计和第三方库集成中的具体应用。这些模式提供了扩展性和灵活性,使我们能够应对各种复杂的系统集成和开发需求。
5. 行为型模式运用
行为型设计模式关注对象之间的通信,它们是如何相互协作以完成任务或实现某些行为的。在本章节中,我们将深入探讨行为型模式在实际开发中的具体运用,以及如何通过这些模式优化代码结构,提高系统的可维护性和可扩展性。
5.1 观察者模式在事件驱动架构中的应用
观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象。当主题对象的状态发生变化时,所有依赖于它的观察者都会收到通知并自动更新。
5.1.1 事件发布订阅机制的实现
在事件驱动的架构中,发布-订阅模型是实现观察者模式的一种常用方式。在这种模型中,对象通过主题订阅者模式注册自己的兴趣,并在主题发生变化时得到通知。
// 定义一个主题接口
public interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// 实现一个具体的主题
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(this);
}
}
// 触发主题状态改变的方法
public void someBusinessLogic() {
// ...
// 业务逻辑执行后,通知所有观察者
notifyObservers();
}
}
// 定义观察者接口
public interface Observer {
void update(Subject subject);
}
// 实现一个具体的观察者
public class ConcreteObserver implements Observer {
@Override
public void update(Subject subject) {
// 实现更新逻辑
System.out.println("观察者得到通知,主题发生了变化。");
}
}
在上述代码中, Subject
接口定义了三个方法: registerObserver
用于注册观察者, removeObserver
用于移除观察者, notifyObservers
用于通知所有注册的观察者。 Observer
接口定义了一个 update
方法,用于接收主题状态改变的通知。 ConcreteSubject
类实现了 Subject
接口,并在状态变化时调用 notifyObservers
方法,通知所有的观察者。 ConcreteObserver
类实现了 Observer
接口,并定义了 update
方法的具体实现。
5.1.2 观察者模式与微服务架构的联动
观察者模式在微服务架构中的应用也非常广泛。通过发布-订阅机制,微服务之间可以实现松耦合的通信机制,提高系统的可伸缩性和灵活性。
例如,假设我们有一个用户服务和一个通知服务。当用户服务中的用户数据发生变化时,我们希望通知服务能够立即得到通知,并执行相关的通知逻辑。这里就可以使用观察者模式。
在微服务架构中,通常会有一个事件总线(Event Bus)来管理所有的事件,服务可以订阅和发布事件到这个事件总线。
// 事件总线接口
public interface EventBus {
void publish(String eventName, Object data);
void subscribe(String eventName, Handler handler);
}
// 实现事件总线
public class EventBusImpl implements EventBus {
private Map<String, List<Handler>> handlersMap = new HashMap<>();
@Override
public void publish(String eventName, Object data) {
List<Handler> handlers = handlersMap.getOrDefault(eventName, new ArrayList<>());
for (Handler handler : handlers) {
handler.handle(data);
}
}
@Override
public void subscribe(String eventName, Handler handler) {
handlersMap.computeIfAbsent(eventName, k -> new ArrayList<>()).add(handler);
}
}
// 事件处理接口
public interface Handler {
void handle(Object data);
}
// 用户服务
public class UserService {
private EventBus eventBus;
public UserService(EventBus eventBus) {
this.eventBus = eventBus;
eventBus.subscribe("userCreated", this::notifyUserCreated);
}
public void createUser() {
// 创建用户的业务逻辑
// ...
// 创建用户后发布事件
eventBus.publish("userCreated", new User());
}
private void notifyUserCreated(Object data) {
// 发送创建用户通知的逻辑
System.out.println("通知用户创建事件");
}
}
在上述代码中,我们定义了一个简单的事件总线 EventBus
接口以及实现类 EventBusImpl
。服务可以通过 subscribe
方法订阅感兴趣的事件,并通过 publish
方法发布事件。这里 UserService
服务在创建用户后,会发布一个 userCreated
事件,通知服务可以订阅这个事件,并在事件发生时执行相应的通知逻辑。
5.2 策略模式在算法动态选择中的运用
策略模式是一种行为设计模式,它定义了一组算法,并将每个算法封装起来,使它们可以互换使用。策略模式让算法的变化独立于使用算法的客户。
5.2.1 策略模式与多态性
策略模式依赖于多态性,使得不同的算法可以被不同的对象使用,并且在运行时选择不同的算法实现。
// 定义一个策略接口
public interface Strategy {
int execute(int a, int b);
}
// 实现一个具体策略
public class ConcreteStrategyAdd implements Strategy {
@Override
public int execute(int a, int b) {
return a + b;
}
}
// 实现另一个具体策略
public class ConcreteStrategyMultiply implements Strategy {
@Override
public int execute(int a, int b) {
return a * b;
}
}
// 上下文类,持有一个策略对象
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(int a, int b) {
return strategy.execute(a, b);
}
}
在上述代码中, Strategy
接口定义了一个 execute
方法,用于执行算法。 ConcreteStrategyAdd
和 ConcreteStrategyMultiply
类实现了 Strategy
接口,分别提供加法和乘法的算法实现。 Context
类持有一个 Strategy
类型的属性,通过这个属性来调用不同的策略实现。我们可以通过 setStrategy
方法在运行时改变策略。
5.2.2 动态策略选择与业务规则的解耦
策略模式使得我们可以在运行时根据业务规则的需要动态选择策略,这样做可以将业务规则与策略实现解耦,使业务规则的修改和扩展变得非常灵活。
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context(new ConcreteStrategyAdd());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context.setStrategy(new ConcreteStrategyMultiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
}
}
在 StrategyPatternDemo
类的 main
方法中,我们创建了一个 Context
对象,并传入了加法策略 ConcreteStrategyAdd
。然后我们执行策略,并打印结果。之后我们通过 setStrategy
方法更改了策略为乘法策略 ConcreteStrategyMultiply
,再次执行策略并打印结果。这种方式允许在运行时动态选择策略,符合策略模式的设计意图。
5.3 模板方法模式在框架开发中的应用案例
模板方法模式是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并将某些步骤推迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
5.3.1 模板方法模式与钩子方法的结合
模板方法模式通常和钩子方法结合使用,以控制子类的扩展点。
// 定义一个抽象类作为算法的模板
public abstract class AbstractClass {
// 基本方法
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
// 模板方法
public void templateMethod() {
primitiveOperation1();
primitiveOperation2();
hookMethod();
}
// 钩子方法,可以被子类重写
protected void hookMethod() {
// 这是一个空方法,子类可以重写它,也可以不重写
}
}
// 实现模板中的基本方法
public class ConcreteClass extends AbstractClass {
@Override
protected void primitiveOperation1() {
System.out.println("具体操作1的实现");
}
@Override
protected void primitiveOperation2() {
System.out.println("具体操作2的实现");
}
@Override
protected void hookMethod() {
// 对钩子方法的具体实现
System.out.println("子类对钩子方法的实现");
}
}
在上述代码中, AbstractClass
类定义了一个 templateMethod
方法,该方法中定义了算法的步骤。 primitiveOperation1
和 primitiveOperation2
是基本方法,需要子类实现。 hookMethod
是一个钩子方法,子类可以选择重写它或者保留默认行为。 ConcreteClass
类实现了抽象类中的基本方法,并重写了钩子方法。
5.3.2 实现不改变父类代码的子类扩展
模板方法模式的一个重要优点是它允许在不修改父类代码的情况下,子类可以扩展功能。这意味着可以保持算法的稳定性,同时又允许子类提供算法的变体。
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
在 Client
类中,我们创建了 ConcreteClass
的实例,并调用 templateMethod
方法。输出结果将显示具体操作1和操作2的实现,以及子类实现的钩子方法。
通过这种方式,我们成功地在子类中扩展了父类的功能,而没有修改父类中的任何代码。这使得我们能够轻松地维护和扩展模板方法模式的类层次结构,同时也保持了代码的整洁和稳定性。
6. Java架构图概述
6.1 MVC架构风格的原理与应用
MVC(Model-View-Controller)架构风格是一种广泛应用于Web和桌面应用程序设计中的模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种分离关注点的设计,使得代码易于维护、扩展和测试。
6.1.1 MVC架构的组件与职责
- 模型(Model) :模型代表应用程序的核心数据结构。它负责维护数据状态,并提供数据访问逻辑,通常是使用JavaBean或ORM框架实现。
- 视图(View) :视图负责展示数据模型给用户。在Web应用中,通常是一个JSP或Thymeleaf页面,用于渲染模型数据,并提供用户交互界面。
- 控制器(Controller) :控制器是处理输入和命令的中心点,负责调用模型和视图去完成用户请求。控制器通过接收输入,选择模型中的数据,并选择视图来显示数据给用户。
6.1.2 MVC在Web应用开发中的实践
在Java Web开发中,Spring MVC是一个非常流行的MVC实现。以下是Spring MVC在Web应用开发中的关键组件和其工作流程:
- DispatcherServlet :作为中央控制器,接收请求并将它们分配给正确的处理器(控制器)。
- HandlerMapping :配置请求路径和控制器方法之间的映射关系。
- Controller :编写业务逻辑和处理请求,并返回ModelAndView对象。
- ViewResolver :解析视图名称为视图实现。
使用Spring MVC,开发者可以快速地实现MVC架构,例如:
@Controller
public class GreetingController {
@GetMapping("/greeting")
public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
model.addAttribute("name", name);
return "greeting"; // 返回的是视图的名称
}
}
6.2 微服务架构细节
微服务架构是将单体应用程序拆分成一组小的服务,每个服务运行在自己的进程中,并使用轻量级的通信机制(通常是HTTP RESTful API)进行通信。
6.2.1 微服务架构的设计原则
微服务架构的设计原则强调了服务自治、业务能力模型和松散耦合。其主要设计原则包括:
- 服务拆分 :将应用程序拆分成独立的、松耦合的服务。
- 基础设施自动化 :使用Docker和Kubernetes等工具自动化部署和管理服务。
- 服务自治 :每个服务可以独立部署、扩展和升级,不影响其他服务。
6.2.2 服务拆分、治理与监控策略
在微服务架构中,服务的拆分是一个复杂的过程,需要考虑许多因素,包括服务的边界、数据库拆分和事务管理。
服务治理是微服务架构中的关键组件,它包括服务发现、负载均衡、配置管理等。例如,Netflix开源了Eureka作为服务注册与发现的组件,而Zuul则是作为API网关,处理服务路由和负载均衡。
服务监控是确保微服务健康运行的重要环节。Prometheus和Grafana可以用来收集和展示微服务的运行数据和性能指标。
6.3 分层架构方法
分层架构是另一种流行的架构风格,它通过将系统逻辑分解为多个层次来实现关注点分离。
6.3.1 分层架构的优势与挑战
分层架构的主要优势是:
- 关注点分离 :允许不同层次独立发展,更易于理解和维护。
- 复用性 :每一层可以实现通用的功能,供整个应用或多个应用使用。
- 灵活性 :可以单独更改或升级层次,不影响其他层次。
然而,分层架构也面临挑战,如过度设计可能会导致不必要的复杂性,性能开销,以及对开发人员的严格要求,需要他们清晰地理解每个层次的职责和接口。
6.3.2 实现高效、清晰的业务逻辑分层
实现高效、清晰的业务逻辑分层,关键在于定义清晰的层次界限和定义良好的API契约。常见的分层结构包括表示层、业务层、持久层和数据访问层。
示例代码说明了如何定义不同层次的接口:
// 定义业务层接口
public interface OrderService {
Order createOrder(Order order);
}
// 定义持久层接口
public interface OrderRepository {
Order save(Order order);
}
// 实现持久层接口
public class JpaOrderRepository implements OrderRepository {
// 实现保存订单的逻辑
}
// 实现业务层接口
public class OrderServiceImpl implements OrderService {
private OrderRepository orderRepository;
public OrderServiceImpl(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public Order createOrder(Order order) {
return orderRepository.save(order);
}
}
6.4 SOA和CQRS模式
SOA(面向服务的架构)和CQRS(命令查询职责分离)是两种架构模式,它们可以独立使用,也可以结合起来。
6.4.1 SOA架构中的服务集成与交互
SOA是一种设计模式,旨在将应用程序的不同功能单元(服务)分离开来,它们通过网络(如HTTP RESTful API)进行通信和集成。
SOA的关键组件包括:
- 服务注册中心 :服务可以在这里注册和发现。
- 服务消费者 :使用服务的应用程序。
- 服务提供者 :提供服务的应用程序。
在Java中,Apache Camel是实现企业集成模式的流行框架,它提供了丰富的路由和转换组件,用于服务集成和交互。
6.4.2 CQRS模式在复杂业务处理中的优势
CQRS模式是一种架构模式,它将读取(查询)和写入(命令)操作分离。这种分离可以优化系统性能、增强可伸缩性和简化复杂业务处理。
CQRS模式的优势包括:
- 清晰的职责划分 :关注于命令处理和查询处理的分离。
- 优化性能和伸缩性 :读和写操作可以根据需要独立地优化和伸缩。
- 简化并发模型 :通过分离写模型和读模型,可以更简单地管理并发性。
6.5 DDD和Event Sourcing架构
领域驱动设计(DDD)和事件溯源(Event Sourcing)是现代软件设计中的高级架构模式,它们特别适用于复杂业务逻辑和高度变化的领域。
6.5.1 领域驱动设计(DDD)的核心概念
DDD是一个围绕核心业务逻辑建模和设计软件的概念框架。它强调模型的丰富性,并提供了一种将业务需求转化为软件解决方案的方法。
DDD的核心概念包括:
- 领域 :业务问题的特定部分或功能区域。
- 领域模型 :业务领域的核心概念和规则的抽象表示。
- 聚合根 :作为领域模型的高层次封装,管理对象之间的交互。
通过聚合根,可以构建清晰的业务领域边界,从而简化复杂逻辑。
6.5.2 事件溯源(Event Sourcing)的实践案例
事件溯源是一种保存状态变更事件来重建状态的设计模式。它与DDD配合使用时,可以创建一个更加透明和可靠的系统。
事件溯源的优势包括:
- 清晰的历史记录 :所有的变更都被记录下来,容易审计和回溯。
- 更高的灵活性 :状态可以基于历史事件重新计算,易于处理复杂的业务规则。
- 支持CQRS :事件溯源经常与命令查询职责分离(CQRS)结合,以优化读写操作。
例如,一个在线购物车可能使用事件溯源来记录用户添加或移除商品的事件。这种模式可以使得购物车系统的状态在任何时间点上都可以被重建。
简介:Java设计模式和架构图是构建高效、可维护系统的基石。设计模式提供了解决常见编程问题的模板,包括创建型、结构型和行为型模式,而架构图则展示了系统的整体布局和设计风格。本资源包括对23种设计模式的分类和解释,以及30种Java技术框架图的探讨,如MVC、微服务、分层架构、SOA、CQRS、DDD和Event Sourcing等。通过实践这些模式和架构风格,开发者可以编写更优雅、更具扩展性的代码,并在实际项目中做出明智的技术选择。