设计模式是指在特定的应用场景中,经过多次实践和总结,提炼出来的可以重复使用的代码组织方式和设计思想。
编程范式
编程范式是指一种通用的编程思想、风格或方法,它描述了如何组织和结构化计算机程序。
- 面向过程编程(Procedural Programming):以顺序或流程控制为核心,通过函数或子程序实现任务分解,使得程序结构更清晰,易于维护和扩展。
- 面向对象编程(Object-Oriented Programming):以类和对象为核心,通过封装、继承、多态等机制来组织代码实现代码复用和灵活性,提高代码的可读性和可维护性。
设计模式
设计模式是指在特定的应用场景中,经过多次实践和总结,提炼出来的可以重复使用的代码组织方式和设计思想。常见的设计模式有以下几种:
创建型模式:负责对象的创建过程,包括工厂方法、单例、原型、建造者等。
结构型模式:负责对象的组合和关系管理,包括适配器、桥接、装饰器、外观、享元、代理等。
行为型模式:负责对象之间的通信和协作,包括模板方法、策略、命令、职责链、状态、观察者、中介者、访问者、备忘录、迭代器等。
1.工厂模式
在工厂模式中,我们使用一个工厂类来封装对象的创建过程,并隐藏对象的具体实现细节,从而使客户端代码只需与工厂类进行交互,而无需直接与对象进行交互。其主要目的是为了解决对象创建过程中的复杂性和灵活性问题。
工厂模式简单来说就是将对象的创建过程封装起来,实现创建者和调用者的分离。可以分为简单工厂模式、工厂方法模式、抽象工厂模式等
假设我们有一个汽车工厂,可以生产不同类型的汽车,比如轿车、越野车和商务车。我们需要通过工厂来创建这些车辆,并向客户端提供统一的接口。
首先,我们需要定义一个汽车接口,它包含了所有汽车应该具备的方法:
1
2
3 public interface Car {
void drive();
}然后,我们实现三个不同类型的汽车类,分别是 SedanCar(轿车)、OffRoadCar(越野车):
1
2
3
4
5
6
7
8
9
10
11
12
13 public class SedanCar implements Car {
public void drive() {
System.out.println("Driving a sedan car.");
}
}
public class OffRoadCar implements Car {
public void drive() {
System.out.println("Driving an off-road car.");
}
}接着,我们需要定义一个汽车工厂类,用于创建不同类型的车辆。在简单工厂模式中,我们通常使用静态方法来创建对象:
1
2
3
4
5
6
7
8
9
10
11
12 public class CarFactory {
public static Car createCar(String type) {
switch (type) {
case "sedan":
return new SedanCar();
case "offroad":
return new OffRoadCar();
default:
throw new IllegalArgumentException("Invalid car type: " + type);
}
}
}最后,我们可以在客户端中使用汽车工厂来创建不同类型的车辆:
1
2
3
4
5
6
7
8 public class Client {
public static void main(String[] args) {
Car sedan = CarFactory.createCar("sedan");
sedan.drive();
Car offroad = CarFactory.createCar("offroad");
offroad.drive();
}
}
2.单例模式
用于确保在应用程序中只有一个类的实例,并且提供对该实例的全局访问点。在单例模式中,类会维护一个静态成员变量来存储唯一的实例。这个实例通常是通过一个私有的构造函数来创建的,防止外部代码创建新的实例。为了访问这个唯一实例,单例类还提供了一个公共的静态方法。
单例模式在需要限制特定类的实例数量时非常有用。它也可以用来管理共享资源,例如数据库连接或文件系统句柄。
以下是一个简单的 Java 示例,说明如何实现单例模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 public class Singleton {
private static Singleton instance;
private Singleton() {
// 私有构造函数,防止外部代码创建新的实例
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}在这个示例中,
Singleton
类维护了一个静态成员变量instance
,用于存储唯一的实例。私有的构造函数确保外部代码无法创建新的实例。getInstance()
方法提供了对唯一实例的全局访问点,并在第一次被调用时创建该实例。使用该示例,可以通过以下方式获取
Singleton
的唯一实例:
1 Singleton singleton = Singleton.getInstance();如果需要使用多线程环境中的单例模式,需要考虑线程安全问题。可以使用同步关键字或者使用双重校验锁来解决这个问题。
3.观察者模式
它定义了一种对象间的一对多依赖关系,当一个对象的状态发生改变时,它的所有依赖者都会收到通知并自动更新。
在观察者模式中,被观察者(又称为主题)维护一个列表来追踪其依赖者(观察者)并通知它们。当主题的状态发生改变时,它会遍历这个列表并调用每个观察者的更新方法,从而使得观察者可以根据最新的状态进行相应的处理。
观察者模式的核心思想是松耦合,因为被观察者和观察者之间没有显式的依赖关系。这使得我们可以方便地添加或删除观察者,同时也可以轻松地扩展观察者和被观察者的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 // 观察者接口
public interface Observer {
void update();
}
// 被观察者接口
public interface Subject {
void attach(Observer observer); // 添加观察者
void detach(Observer observer); // 删除观察者
void notifyObservers(); // 通知观察者
}
// 具体被观察者实现
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private String state;
public void setState(String state) {
this.state = state;
notifyObservers();
}
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update();
}
}
}
// 具体观察者实现
public class ConcreteObserver implements Observer {
private String name;
private ConcreteSubject subject;
public ConcreteObserver(String name, ConcreteSubject subject) {
this.name = name;
this.subject = subject;
}
public void update() {
System.out.println(name + " received the updated state: " + subject.getState());
}
}在这个示例中,
ConcreteSubject
实现了Subject
接口,并维护了一个观察者列表。当它的状态发生改变时,它会遍历这个列表并调用每个观察者的update()
方法。
ConcreteObserver
实现了Observer
接口,并在构造函数中接收要观察的主题对象。当主题状态发生改变时,它会收到通知并进行相应的处理。可以通过以下方式使用该示例:
1
2
3
4
5
6
7
8 ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver("Observer 1", subject);
ConcreteObserver observer2 = new ConcreteObserver("Observer 2", subject);
subject.attach(observer1);
subject.attach(observer2);
subject.setState("new state");在这个示例中,我们创建了一个
ConcreteSubject
对象和两个ConcreteObserver
对象。然后我们将这两个观察者添加到主题的观察者列表中,并改变主题的状态。当主题状态发生改变时,两个观察者都会收到通知并进行相应的处理。
4.策略模式
策略模式(Strategy Pattern)是一种行为型设计模式,它允许在运行时动态地改变对象的算法行为。该模式定义了一系列算法,将每个算法封装起来,并且使它们可以相互替换。
策略模式的核心思想是将变化的部分抽象出来,然后通过组合和委托的方式将其注入到某个对象中,从而实现算法的灵活性和复用性。这样当需要改变算法时,只需要替换其中的策略对象即可,而不需要修改原有的代码。
以下是使用Java编写的策略模式示例代码:
首先,我们定义一个策略接口,该接口包含了所有具体策略类需要实现的方法:
1
2
3 public interface PaymentStrategy {
public void pay(double amount);
}然后,我们定义两个具体的策略类,它们分别实现了
PaymentStrategy
接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 public class CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String dateOfExpiry;
public CreditCardStrategy(String name, String cardNumber, String cvv, String dateOfExpiry) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.dateOfExpiry = dateOfExpiry;
}
public void pay(double amount) {
System.out.println(amount + " paid with credit/debit card.");
}
}
public class PaypalStrategy implements PaymentStrategy {
private String emailId;
private String password;
public PaypalStrategy(String emailId, String password) {
this.emailId = emailId;
this.password = password;
}
public void pay(double amount) {
System.out.println(amount + " paid using Paypal.");
}
}接下来,我们定义一个
ShoppingCart
类,该类将会使用策略模式来执行支付操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
List<Item> items;
public ShoppingCart() {
items = new ArrayList<Item>();
}
public void addItem(Item item) {
items.add(item);
}
public void removeItem(Item item) {
items.remove(item);
}
public double calculateTotal() {
double sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}
public void pay(PaymentStrategy paymentMethod) {
double amount = calculateTotal();
paymentMethod.pay(amount);
}
}最后,我们创建一个
Main
类来测试策略模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 public class Main {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
Item item1 = new Item("1234",10);
Item item2 = new Item("5678",40);
cart.addItem(item1);
cart.addItem(item2);
//pay by credit card
cart.pay(new CreditCardStrategy("John Doe", "1234567890123456", "786", "12/15"));
//pay by Paypal
cart.pay(new PaypalStrategy("myemail@example.com", "mypwd"));
}
}在上面的示例中,我们首先创建了一个购物车
ShoppingCart
,然后向其中添加两个商品。接着,我们通过调用ShoppingCart
的pay()
方法来执行支付操作,该方法将接收一个具体的支付策略对象作为参数。最后,我们分别使用了
CreditCardStrategy
和PaypalStrategy
来执行支付操作。这就是一个简单的使用Java实现的策略模式示例。
5.建造者模式
将创建复杂对象的过程与其表示相分离,意味着可以使用相同的创建过程来创建不同类型的对象。
通过使用建造者模式,您可以避免在对象的构造函数中使用大量参数,从而使代码更加清晰和可读。
以下是一个使用Java实现的建造者模式的示例:
首先,我们需要定义需要构建的对象的类,这里我以一个简单的汽车为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public class Car {
private String make;
private String model;
private int year;
private int numberOfDoors;
public Car(String make, String model, int year, int numberOfDoors) {
this.make = make;
this.model = model;
this.year = year;
this.numberOfDoors = numberOfDoors;
}
public String getMake() {
return make;
}
public String getModel() {
return model;
}
public int getYear() {
return year;
}
public int getNumberOfDoors() {
return numberOfDoors;
}
}接下来,我们需要创建一个建造者类,用于构建Car对象。建造者类中包含与Car类相同的属性,并提供一些方法用于设置这些属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 public class CarBuilder {
private String make;
private String model;
private int year;
private int numberOfDoors;
public CarBuilder setMake(String make) {
this.make = make;
return this;
}
public CarBuilder setModel(String model) {
this.model = model;
return this;
}
public CarBuilder setYear(int year) {
this.year = year;
return this;
}
public CarBuilder setNumberOfDoors(int numberOfDoors) {
this.numberOfDoors = numberOfDoors;
return this;
}
public Car build() {
return new Car(make, model, year, numberOfDoors);
}
}在这个建造者类中,我们定义了一些设置属性的方法,并提供了一个build()方法,用于构建Car对象。注意,每个方法都返回建造者对象本身,以支持链式调用。
最后,我们可以使用建造者模式来构建一个Car对象:
1
2
3
4
5
6 Car car = new CarBuilder()
.setMake("Honda")
.setModel("Accord")
.setYear(2022)
.setNumberOfDoors(4)
.build();在这个例子中,我们创建了一个CarBuilder对象,并使用链式调用设置了汽车的各项属性,最终通过build()方法构建了一个完整的Car对象。
6.适配器模式
可以将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而无法一起工作的类可以协同工作。适配器模式通常用于旧代码重用、类库迁移等场景中。
下面是一个简单的使用适配器模式的Java代码示例:
假设我们有一个接口 MediaPlayer,可以播放 mp3 格式的音频文件,但是现在我们需要扩展该接口,让其能够播放其他格式的音频文件(如 mp4、vlc 等)。由于原始接口只支持 mp3 格式,因此需要使用适配器模式。
当在Java中实现适配器模式时,可以按照以下步骤进行:
定义目标接口(Target Interface):这是客户端代码期望的接口。
1
2
3 public interface Target {
void request();
}
1
2
3
4
5
6
7
8 1. 创建原始类(Adaptee):这是需要适配的原始类,它拥有一组不兼容于目标接口的方法。
```java
public class Adaptee {
public void specificRequest() {
System.out.println("Adaptee's specificRequest() called");
}
}1. 创建适配器类(Adapter):适配器类实现了目标接口,并持有对原始类的引用。在适配器类的方法中,将调用原始类的方法进行适配。
1
2
3
4
5
6
7
8
9
10
11
12 public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
}
- 在客户端代码中使用适配器:
1
2
3
4
5
6
7
8 public class Client {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
Target adapter = new Adapter(adaptee);
adapter.request(); // 调用适配器的方法,实际上会调用原始类的方法
}
}在上面的示例中,
Target
是目标接口,定义了客户端代码期望的方法。Adaptee
是原始类,具有不兼容的方法specificRequest()
。Adapter
是适配器类,它实现了目标接口,并在其方法中调用了原始类的方法。在客户端代码中,我们创建了一个
Adaptee
对象和一个适配器对象adapter
。当调用adapter.request()
时,实际上会调用适配器类中的request()
方法,该方法内部通过引用的adaptee
对象调用了原始类的方法specificRequest()
。这样,通过适配器模式,我们可以将原始类的方法适配为目标接口的方法,以满足客户端代码的需求。
7.代理模式
允许在一个对象的基础上创建一个代理对象,用于控制对原始对象的访问。代理对象可以拦截对原始对象的访问并进行额外的处理,例如缓存、安全性检查、懒加载等操作。实现对象间的解耦,同时也能帮助提高程序的性能和安全性。
假设我们有一个接口
Image
和一个实现该接口的类RealImage
。我们还有一个代理类ProxyImage
,它实现了Image
接口,并且具有一个属性realImage
,它是一个RealImage
对象。当我们调用
ProxyImage
的display()
方法时,它首先检查realImage
是否为null
。如果是,则创建一个新的RealImage
并将其分配给realImage
属性。然后,它调用realImage
的display()
方法来显示图像。下面是代码示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 // Image interface
public interface Image {
void display();
}
// RealImage class
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
// ProxyImage class
public class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
// Usage example
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
// The first time we call display() method, it will load the image from disk
image.display();
// The second time we call display() method, it will use the cached image
image.display();
}在上面的示例中,
ProxyImage
类充当了RealImage
对象的代理。当我们第一次调用display()
方法时,它会创建一个新的RealImage
对象并将其分配给realImage
属性。当我们再次调用display()
方法时,它不需要创建新的对象,而是直接使用缓存的对象来显示图像。这提高了应用程序的性能,因为我们避免了多余的对象创建和资源消耗。
动态代理和静态代理
动态代理
动态代理是在运行时动态生成代理类的过程。在Java中,可以通过反射机制来动态地创建代理对象。通常情况下,动态代理需要实现一个接口或者继承一个父类,然后在运行时生成相应的代理类。由于动态代理生成的代理类是在运行时生成的,所以也称作“运行时代理”。
Spring AOP就是基于动态代理技术实现的,它可以在运行时动态地将切面代码织入到目标对象中,并生成代理对象。这样,就可以在不修改原始代码的情况下实现对目标对象的拦截和修改。
静态代理
静态代理是在编译时就已经存在了代理类的过程。在静态代理中,代理类和委托类(即被代理对象)的关系在程序编译期间就确定了,因此也称为“编译时代理”。
在静态代理中,代理类需要实现与被代理类相同的接口或者继承相同的父类,在代理类中调用被代理类的方法,并在必要时添加额外的操作。然后在使用时,需要将代理类的实例传递给其他对象来完成调用。
静态代理的缺点是每个委托类都需要对应一个代理类,因此当委托类比较多时,会导致代理类数量过多,不易维护。同时,由于代理类和委托类之间的关系在编译期就已经确定了,因此静态代理也不太灵活,无法动态地改变被代理对象或者代理对象的行为。
总之,动态代理和静态代理都是代理模式的实现方式,它们的主要区别在于代理类的生成方式和时机不同。动态代理在运行时动态生成代理类,可以灵活地拦截和修改目标对象的行为;而静态代理在编译时就已经存在代理类,不太灵活,也不易于维护。
代理模式和适配器模式
代理模式和适配器模式是两种不同的设计模式,它们的主要区别在于解决的问题不同。
1.代理模式
代理模式是一种结构型设计模式,它主要用于控制对对象的访问,为其他对象提供一种代理以控制这个对象的访问。代理模式可以增强目标对象的功能,如增加数据校验、权限管理等。代理模式通常由三部分组成:抽象角色、代理角色、真实角色。
代理模式主要解决的是对目标对象的访问控制问题,即通过代理对象来控制目标对象的访问权限,并可以在不改变目标对象原有逻辑的情况下增强其功能。
2.适配器模式
适配器模式是一种结构型设计模式,它主要用于将一个接口转换成另一个客户端所期望的接口形式,从而使原本不兼容的接口能够一起工作。适配器模式通常由两个角色组成:适配者(Adaptee)和适配器(Adapter)。
适配器模式主要解决的是不同接口之间的兼容性问题,即通过适配器将一个接口转换成另一个接口,使得原本不兼容的接口能够协同工作。
总之,代理模式和适配器模式都是结构型设计模式,但它们的解决问题不同。代理模式主要用于控制对目标对象的访问,并增强其功能;而适配器模式主要用于将一个接口转换成另一个接口以解决兼容性问题。