目录
懒汉式单例和双重检查锁(Double-Checked Locking)
工厂方法模式(Factory Method Pattern)
设计模式七大原则
设计模式的七大原则是指软件设计中常用的七条准则,它们是:
-
单一职责原则(Single Responsibility Principle,SRP): 一个类或模块只负责完成单一的功能或职责。这样可以提高代码的可读性、可维护性和可扩展性。
-
开放封闭原则(Open-Closed Principle,OCP): 软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。通过抽象、接口、继承等方式,使得系统在扩展时根据需求变化而变化,而不需要修改已有的代码。
-
里氏替换原则(Liskov Substitution Principle,LSP): 所有引用基类的地方必须能够透明地使用其子类对象,子类对象必须能够替换基类对象而不影响程序的正确性。这保证了继承关系的正确性和稳定性。
-
依赖倒置原则(Dependency Inversion Principle,DIP): 高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。通过依赖注入和接口的使用,降低模块之间的耦合性。
-
接口隔离原则(Interface Segregation Principle,ISP): 客户端不应该强迫依赖它不需要的接口。类之间的依赖关系应该建立在最小的接口上,避免“胖接口”和“接口污染”。
-
迪米特法则(Law of Demeter,LoD): 一个对象应该对其他对象有尽可能少的了解。模块之间的通信应该通过最少的接口进行,尽量降低模块之间的依赖关系。
-
合成复用原则(Composite/Aggregate Reuse Principle,CARP): 尽量使用对象组合和聚合,而不是继承来达到复用的目的。通过组合关系,可以将已有的对象组合成新的对象,更灵活地增加新的行为。
这些设计原则提供了指导和规范,帮助开发人员设计出良好的软件架构和可维护的代码。它们能够提升软件系统的稳定性、灵活性、可扩展性和可理解性,从而降低维护成本和改进效率。在实际开发中,结合具体的场景和需求,合理运用这些原则可以获得更好的设计效果。
设计模式的分类
在《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)一书中,提出了 23 种设计模式,通常称为 GoF(Gang of Four)设计模式。这些设计模式被分为以下三种类型:
-
创建型模式(Creational Patterns):
-
工厂方法模式(Factory Method Pattern)
-
抽象工厂模式(Abstract Factory Pattern)
-
单例模式(Singleton Pattern)
-
建造者模式(Builder Pattern)
-
原型模式(Prototype Pattern)
-
-
结构型模式(Structural Patterns):
-
适配器模式(Adapter Pattern)
-
桥接模式(Bridge Pattern)
-
组合模式(Composite Pattern)
-
装饰者模式(Decorator Pattern)
-
外观模式(Facade Pattern)
-
享元模式(Flyweight Pattern)
-
代理模式(Proxy Pattern)
-
-
行为型模式(Behavioral Patterns):
-
责任链模式(Chain of Responsibility Pattern)
-
命令模式(Command Pattern)
-
解释器模式(Interpreter Pattern)
-
迭代器模式(Iterator Pattern)
-
中介者模式(Mediator Pattern)
-
备忘录模式(Memento Pattern)
-
观察者模式(Observer Pattern)
-
状态模式(State Pattern)
-
策略模式(Strategy Pattern)
-
模板方法模式(Template Method Pattern)
-
访问者模式(Visitor Pattern)
-
这三种类型的设计模式分别涵盖了对象的创建、结构和行为方面的需求,每种类型的设计模式在特定的场景下都能提供有效的解决方案,从而帮助开发人员构建更加可复用、灵活和可维护的面向对象软件系统。
常见的设计模式
创建型模式
懒汉式单例和双重检查锁(Double-Checked Locking)
以下是一个使用懒汉式和双重检查锁(Double-Checked Locking)的单例模式代码示例:
public class Singleton { private static volatile Singleton instance; // 使用 volatile 关键字保证可见性和禁止指令重排序 private Singleton() { // 私有构造方法,防止外部实例化 } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { // 对实例化代码块加锁 if (instance == null) { instance = new Singleton(); } } } return instance; } }
在这个示例中,Singleton 类使用了懒汉式的实现方式,即只有在需要获取单例对象时才会进行实例化,并且通过双重检查锁机制来确保线程安全性。
在 getInstance()
方法中,首先会检查 instance 是否已经被实例化,如果没有,则进入同步代码块。在同步代码块中再次检查 instance 是否为 null,这是为了避免多个线程同时通过了第一个 null 检查,从而产生多个实例。如果 instance 为 null,则实例化一个新的 Singleton 对象。
关键的一点是使用了 volatile
关键字来声明 instance 变量,它可以保证可见性和禁止指令重排序。这样可以确保在多线程环境下,对 instance 的修改对其他线程立即可见,并且避免指令重排序带来的潜在问题。
双重检查锁的方式减少了同步的开销,只有第一次需要实例化时才会进行同步操作,后续获取实例时直接返回已经实例化的对象,提高了性能。
需要注意的是,上述示例中的 Singleton 类并没有处理序列化和反序列化的情况,如果需要支持序列化和反序列化,还需要添加 readResolve()
方法来确保反序列化时返回同一个实例。
工厂模式
工厂模式是一种创建型设计模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们使用工厂方法来创建对象,而不是在代码中直接使用 new 关键字实例化对象。这有助于实现代码的松耦合性,同时也提高了代码的可维护性和可扩展性。
在 Java 中,工厂模式通常包括一个工厂接口和多个具体工厂类,每个具体工厂类负责创建一种特定类型的对象。下面是一个简单的示例,演示了如何使用工厂模式创建不同类型的形状对象:
```java
// 创建一个接口 Shape,定义了一个绘制方法
interface Shape {
void draw();
}
// 创建具体的形状类 Circle
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw a circle.");
}
}
// 创建具体的形状类 Square
class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw a square.");
}
}
// 创建工厂接口 ShapeFactory
interface ShapeFactory {
Shape createShape();
}
// 创建具体的圆形工厂类 CircleFactory
class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle();
}
}
// 创建具体的正方形工厂类 SquareFactory
class SquareFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Square();
}
}
// 在客户端代码中使用工厂模式创建对象
public class FactoryPatternExample {
public static void main(String[] args) {
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw();
ShapeFactory squareFactory = new SquareFactory();
Shape square = squareFactory.createShape();
square.draw();
}
}
```
在上面的示例中,通过引入 Shape 接口和 ShapeFactory 接口,以及具体的形状类和对应的工厂类,实现了工厂模式。通过继承 ShapeFactory 接口的具体工厂类,我们可以方便地创建不同类型的形状对象,从而实现了对象的解耦和灵活性。
抽象工厂模式
抽象工厂模式是一种创建型设计模式,它提供了一种创建一系列相关或依赖对象的最佳方式,而无需指定它们具体的类。抽象工厂模式与工厂模式的区别在于,抽象工厂模式用于创建一组相关的对象,而工厂模式用于创建单个对象。抽象工厂模式通过引入抽象工厂接口和多个具体工厂类来实现,每个具体工厂类负责创建一组相关的对象。
下面是一个简单的示例,演示了如何使用抽象工厂模式创建不同类型的形状和颜色对象:
```java
// 创建一个抽象形状接口 Shape
interface Shape {
void draw();
}
// 创建具体形状类 Circle
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Draw a circle.");
}
}
// 创建具体形状类 Square
class Square implements Shape {
@Override
public void draw() {
System.out.println("Draw a square.");
}
}
// 创建一个抽象颜色接口 Color
interface Color {
void fill();
}
// 创建具体颜色类 Red
class Red implements Color {
@Override
public void fill() {
System.out.println("Fill with red color.");
}
}
// 创建具体颜色类 Blue
class Blue implements Color {
@Override
public void fill() {
System.out.println("Fill with blue color.");
}
}
// 创建抽象工厂接口 AbstractFactory
interface AbstractFactory {
Shape createShape();
Color createColor();
}
// 创建具体形状工厂类 ShapeFactory,用于创建形状对象
class ShapeFactory implements AbstractFactory {
@Override
public Shape createShape() {
return new Circle();
}
@Override
public Color createColor() {
return new Red();
}
}
// 创建具体颜色工厂类 ColorFactory,用于创建颜色对象
class ColorFactory implements AbstractFactory {
@Override
public Shape createShape() {
return new Square();
}
@Override
public Color createColor() {
return new Blue();
}
}
// 在客户端代码中使用抽象工厂模式创建对象
public class AbstractFactoryPatternExample {
public static void main(String[] args) {
AbstractFactory shapeFactory = new ShapeFactory();
Shape circle = shapeFactory.createShape();
Color red = shapeFactory.createColor();
circle.draw();
red.fill();
AbstractFactory colorFactory = new ColorFactory();
Shape square = colorFactory.createShape();
Color blue = colorFactory.createColor();
square.draw();
blue.fill();
}
}
```
在上面的示例中,通过引入 Shape、Color、AbstractFactory 接口以及具体的形状类、颜色类和对应的工厂类,实现了抽象工厂模式。通过使用不同的具体工厂类实例化抽象工厂接口,我们可以方便地创建一组相关的对象,从而实现了对象间的解耦和灵活性。
建造者模式
建造者模式(Builder Pattern)是一种创建型设计模式,它的主要目的是将一个复杂对象的构建过程与其表示相分离,以便同样的构建过程可以创建不同的表示。
在软件开发中,有一些复杂的对象需要通过多个步骤和组件来构建,如果直接在一个类中完成对象的构建,会导致该类变得庞大且难以维护。此时,可以使用建造者模式对构建过程进行解耦,使得构建过程更加灵活、可配置,同时也提高了代码的可读性和可维护性。
下面是建造者模式的核心组成部分:
-
产品(Product):
-
表示要构建的复杂对象。通常,产品包含多个部分组成,例如具体的属性、方法或组件。
-
-
抽象建造者(Abstract Builder):
-
定义构建产品的方法和步骤。它通常包含一个用于创建和返回最终产品的方法。
-
-
具体建造者(Concrete Builder):
-
实现抽象建造者定义的方法和步骤,完成产品的具体构建。具体建造者通常包含一个对应于产品的内部表示的成员变量。
-
-
指挥者/导演(Director):
-
负责使用具体建造者来构建产品,它定义了构建的顺序和执行方式。
-
使用建造者模式时,通常的流程如下:
-
定义产品类,确定产品的属性和组成部分。
-
创建抽象建造者接口,定义了构建产品的方法和步骤。
-
创建具体建造者类,实现抽象建造者接口,并实现构建产品的具体步骤。
-
创建指挥者类,负责调用建造者的方法来构建产品。-
-
在客户端代码中,通过实例化具体建造者和指挥者,调用指导者的构建方法,即可完成产品的构建。
建造者模式的优点包括:
-
将构建过程与产品的表示解耦,使得构建过程更加灵活。
-
可以生成不同表示的产品。
-
可以隐藏构建细节,简化客户端的使用。
然而,建造者模式也有一些限制,例如增加了代码的复杂性,需要额外的建造者类和指导者类。因此,在设计时需要权衡使用建造者模式的利弊,根据具体情况来决定是否使用该设计模式。
当涉及到使用建造者模式时,下面是一个简单的示例代码, 假设我们有一个产品类 Pizza
,它有许多个部分,如面团、酱料、奶酪等,并且我们使用建造者模式来构建这些部分。
首先,我们定义产品类 Pizza
:
public class Pizza { private String dough; private String sauce; private String cheese; public void setDough(String dough) { this.dough = dough; } public void setSauce(String sauce) { this.sauce = sauce; } public void setCheese(String cheese) { this.cheese = cheese; } // 其他属性的设置方法... @Override public String toString() { return "Pizza{" + "dough='" + dough + '\'' + ", sauce='" + sauce + '\'' + ", cheese='" + cheese + '\'' + // 其他属性... '}'; } }
接下来,我们定义抽象建造者接口 PizzaBuilder
:
public interface PizzaBuilder { void buildDough(); void buildSauce(); void buildCheese(); // 其他属性的构建方法... Pizza getPizza(); }
然后,我们创建具体的建造者类 MargheritaPizzaBuilder
:
public class MargheritaPizzaBuilder implements PizzaBuilder { private Pizza pizza; public MargheritaPizzaBuilder() { this.pizza = new Pizza(); } @Override public void buildDough() { pizza.setDough("Thin crust"); } @Override public void buildSauce() { pizza.setSauce("Tomato sauce"); } @Override public void buildCheese() { pizza.setCheese("Mozzarella"); } public Pizza getPizza() { return this.pizza; } }
最后,我们可以创建指挥者类 Waiter
,它负责使用具体建造者来构建产品:
public class Waiter { private PizzaBuilder pizzaBuilder; public void setPizzaBuilder(PizzaBuilder pizzaBuilder) { this.pizzaBuilder = pizzaBuilder; } public Pizza getPizza() { return pizzaBuilder.getPizza(); } public void constructPizza() { pizzaBuilder.buildDough(); pizzaBuilder.buildSauce(); pizzaBuilder.buildCheese(); // 其他属性的构建 } }
客户端代码:
public class Main { public static void main(String[] args) { Waiter waiter = new Waiter(); PizzaBuilder margheritaPizzaBuilder = new MargheritaPizzaBuilder(); waiter.setPizzaBuilder(margheritaPizzaBuilder); waiter.constructPizza(); Pizza pizza = waiter.getPizza(); System.out.println("Pizza is ready: " + pizza); } }
在这个例子中,我们使用了建造者模式来构建 Pizza 对象,根据需要组装不同种类的 Pizza,同时将产品的构建过程与表示分离,使得代码更加灵活和可维护。
结构型设计模式
适配器模式
适配器模式(Adapter Pattern)是一种结构型设计模式,它允许接口不兼容的类之间进行合作。适配器模式可以将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类能够协同工作。
适配器模式通常涉及三个核心角色:
-
目标接口(Target Interface): 这是客户端希望使用的接口,但是由于某些原因,现有的实现无法直接满足这一接口。
-
适配器(Adapter): 适配器是一个实现了目标接口的类,它包装了一个需要适配的类(被适配者),并将客户端的请求转换为对被适配者的调用。
-
被适配者(Adaptee): 被适配者是客户端已经存在的类,但其接口与目标接口不兼容,无法直接被客户端使用。
适配器模式可分为两种类型:
-
类适配器模式(Class Adapter Pattern): 在类适配器模式中,适配器继承了被适配者,并同时实现了目标接口。通过继承被适配者,适配器能够重定义被适配者的一部分行为,同时通过实现目标接口,使得客户端能够使用适配器作为目标接口的实现。
-
对象适配器模式(Object Adapter Pattern): 在对象适配器模式中,适配器持有被适配者的实例,并同时实现了目标接口。适配器将客户端的请求转发给被适配者的实例,从而使得客户端能够通过适配器使用被适配者的功能。
适配器模式常见于当需要使用已有的类,但其接口与需求不匹配时,可以创建一个适配器来解决这一问题。适配器模式能够提供一种灵活、可复用和可扩展的解决方案,帮助不兼容的类之间进行协同工作。
总之,适配器模式是一种常用的设计模式,它允许不兼容的接口进行合作,同时通过适配器的方式实现接口转换和适配,使得不兼容的类能够一起工作。
一个简单的适配器模式的例子是将美元转换成欧元。
假设有两个接口,DollarCurrency
表示美元接口和 EuroCurrency
表示欧元接口,它们有各自的方法 getDollarAmount()
和 getEuroAmount()
。现在我们要将 DollarCurrency
接口适配成 EuroCurrency
接口,以便能够在已有的程序中使用欧元接口。
首先,我们定义目标接口 EuroCurrency
:
public interface EuroCurrency { double getEuroAmount(); }
然后,创建被适配的美元接口 DollarCurrency
:
public interface DollarCurrency { double getDollarAmount(); }
接下来,我们创建适配器类 DollarToEuroAdapter
实现 EuroCurrency
接口。在适配器中,我们将 DollarCurrency
的 getDollarAmount()
方法转换为欧元金额并返回:
public class DollarToEuroAdapter implements EuroCurrency { private DollarCurrency dollarCurrency; public DollarToEuroAdapter(DollarCurrency dollarCurrency) { this.dollarCurrency = dollarCurrency; } @Override public double getEuroAmount() { double dollarAmount = dollarCurrency.getDollarAmount(); // 进行美元到欧元的转换:1美元 = 0.92欧元 double euroAmount = dollarAmount * 0.92; return euroAmount; } }
现在,我们可以使用适配器将美元转换为欧元,例如:
public class Client { public static void main(String[] args) { DollarCurrency dollarCurrency = new DollarCurrencyImpl(100); // 假设有100美元 EuroCurrency euroCurrency = new DollarToEuroAdapter(dollarCurrency); System.out.println("Euro Amount: " + euroCurrency.getEuroAmount()); } }
在上述示例中,美元接口 DollarCurrency
是已有的接口,欧元接口 EuroCurrency
是目标接口,适配器类 DollarToEuroAdapter
实现了目标接口,并将美元接口转换为欧元接口。通过适配器模式,我们可以使用现有的美元接口,并将其适配成我们需要的欧元接口,从而在已有的程序中使用欧元接口。
这个例子展示了适配器模式通过将不兼容的接口适配成目标接口,使得不同的接口能够协同工作。
桥接模式
桥接模式是一种结构型设计模式,旨在将抽象部分与实现部分分离,使它们可以独立变化,而不会相互影响。桥接模式主要通过将继承关系改为对象组合的方式来实现这一目的,以提高系统的灵活性和可扩展性。
在桥接模式中,存在两个独立的层级结构,抽象部分和实现部分。抽象部分包括一个抽象类或接口,用于定义抽象部分的功能和行为;实现部分也包括一个抽象类或接口,并且实现了抽象部分定义的方法。
假设我们正在设计一个图形绘制系统,它支持绘制不同形状的图形(如圆形、矩形)以及不同的颜色(如红色、蓝色)。
首先,我们定义一个图形抽象类 Shape
:
public abstract class Shape { protected Color color; public Shape(Color color) { this.color = color; } public abstract void draw(); }
然后,我们定义一个颜色接口 Color
:
public interface Color { void applyColor(); }
接下来,我们创建不同形状的实现类,如 Circle
和 Rectangle
:
public class Circle extends Shape { public Circle(Color color) { super(color); } @Override public void draw() { System.out.print("绘制圆形,"); color.applyColor(); } } public class Rectangle extends Shape { public Rectangle(Color color) { super(color); } @Override public void draw() { System.out.print("绘制矩形,"); color.applyColor(); } }
最后,我们创建不同颜色的实现类,如 RedColor
和 BlueColor
:
public class RedColor implements Color { @Override public void applyColor() { System.out.println("使用红色"); } } public class BlueColor implements Color { @Override public void applyColor() { System.out.println("使用蓝色"); } }
在上述例子中,Shape
表示图形抽象类,Color
表示颜色接口。Circle
和 Rectangle
是具体形状类,它们都继承自 Shape
。RedColor
和 BlueColor
是具体颜色类,实现了 Color
接口。
通过桥接模式,我们将形状和颜色分离,形状类作为抽象部分,颜色接口作为实现部分,它们之间通过组合关系连接,实现了抽象与实现的解耦。这样,我们可以在设计时灵活地选择不同的形状和颜色来组合绘制图形,而且不同的形状和颜色可以相互独立变化,不会相互影响。
例如,我们可以创建一个红色的圆形:
Color redColor = new RedColor(); Shape circle = new Circle(redColor); circle.draw();
输出结果将是:绘制圆形,使用红色
通过桥接模式,我们可以实现更灵活的组合,而不需要为每个形状和颜色的组合创建一个子类,同时也符合设计模式中的开闭原则。
装饰者模式
装饰者模式是一种结构型设计模式,它允许你通过将对象放入包含行为的特殊包装对象中来为原对象添加新的行为。装饰者模式能够以透明的方式动态地将责任附加到对象上。这种方式比生成子类(继承)方便,因为装饰对象的配置可以在运行时(而不是在编译时)进行。
在装饰者模式中,有一个抽象组件(Component),定义了一个对象接口,可以给这些对象动态地添加职责。然后有具体组件(ConcreteComponent),实现了抽象组件的接口,是被装饰的原始对象。还有装饰者(Decorator),它也实现了抽象组件的接口,并持有一个抽象组件的实例,对抽象组件进行装饰,可以动态地给具体组件添加新的行为。
假设我们有一个抽象的形状接口 Shape
,其中包含了一个方法 draw()
。我们有一个具体的形状类 Circle
实现了这个接口。现在我们想要给这个圆形添加一些装饰,比如颜色和边框。
首先,定义形状接口 Shape
:
public interface Shape { void draw(); }
然后,创建具体的形状类 Circle
:
public class Circle implements Shape { @Override public void draw() { System.out.println("Drawing Circle"); } }
接下来,创建装饰者抽象类 ShapeDecorator
:
public abstract class ShapeDecorator implements Shape { protected Shape decoratedShape; public ShapeDecorator(Shape decoratedShape) { this.decoratedShape = decoratedShape; } public void draw() { decoratedShape.draw(); } }
然后,创建具体的装饰者类 ColorDecorator
:
public class ColorDecorator extends ShapeDecorator { private String color; public ColorDecorator(Shape decoratedShape, String color) { super(decoratedShape); this.color = color; } public void draw() { decoratedShape.draw(); System.out.println("Color: " + color); } }
再创建另一个具体的装饰者类 BorderDecorator
:
public class BorderDecorator extends ShapeDecorator { private int borderWidth; public BorderDecorator(Shape decoratedShape, int borderWidth) { super(decoratedShape); this.borderWidth = borderWidth; } public void draw() { decoratedShape.draw(); System.out.println("Border Width: " + borderWidth); } }
最后,在客户端进行调用:
public class DecoratorPatternExample { public static void main(String[] args) { Shape circle = new Circle(); Shape colorDecoratedCircle = new ColorDecorator(circle, "Red"); Shape borderAndColorDecoratedCircle = new BorderDecorator(colorDecoratedCircle, 2); borderAndColorDecoratedCircle.draw(); } }
这个例子中,Circle
是原始对象,ColorDecorator
和 BorderDecorator
是装饰者。通过装饰者模式,我们可以动态地给圆形添加颜色和边框,而且这种装饰过程是可以灵活组合的。
装饰者模式和代理模式
在某种程度上具有相似性,
它们都属于结构型设计模式,都是通过包装和代理来修改对象的行为。然而,它们之间有一些关键的区别:
-
目的和使用方式:
-
装饰者模式的主要目的是在不改变原始对象接口的情况下扩展其功能。它提供了一种动态地添加或修改对象行为的方式。装饰者模式的使用方式是通过包装具体组件来实现功能的叠加,可以任意组合多个装饰者。
-
代理模式的主要目的是控制对对象的访问。代理模式用于创建一个代理对象,代理对象可以控制客户端对目标对象的访问,并且可以在目标对象的基础上提供额外的功能。代理模式的使用方式是通过创建一个代理类,代理类持有目标对象的引用,并且将对目标对象的调用委派给目标对象。
-
-
关注点:
-
装饰者模式关注的是对现有对象行为的扩展。装饰者模式通过包装具体组件,并在运行时动态添加额外的行为或修改原有行为。
-
代理模式关注的是对对象的控制。代理模式控制对目标对象的访问,并提供额外的功能,比如对目标对象的访问权限、远程访问等。
-
-
对象类型:
-
装饰者模式中,被装饰者和装饰者都实现同一个抽象接口或继承同一个父类,以保持一致的类型,并且可以透明地替换被装饰者。
-
代理模式中,代理对象和目标对象实现相同的接口,并且可以通过持有目标对象的引用,对目标对象的方法进行代理。
-
总结起来,装饰者模式主要用于在运行时扩展对象的功能,而代理模式主要用于控制对象的访问。装饰者模式通过包装具体组件来实现功能的叠加,而代理模式通过创建一个代理类来控制对目标对象的访问。
代理模式
代理模式是一种结构型设计模式,它允许通过代理对象控制对其他对象的访问。代理模式主要用于控制对真实对象的访问,以便在访问真实对象之前或之后执行一些额外的操作。这有助于实现对象的保护、远程访问、延迟加载等功能。
下面是一个简单的示例,演示了如何使用代理模式控制对图片加载的访问:
```java
// 创建一个接口 Image,定义了显示图片的方法
interface Image {
void display();
}
// 创建具体的图片类 RealImage
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image from disk: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
// 创建代理类 ProxyImage
class ProxyImage implements Image {
private RealImage realImage;
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 在客户端代码中使用代理模式控制图片加载
public class ProxyPatternExample {
public static void main(String[] args) {
Image image = new ProxyImage("image.jpg");
// 图片第一次加载
image.display();
System.out.println("---");
// 图片第二次加载,此时直接使用代理对象,无需再次加载图片
image.display();
}
}
```
在上面的示例中,通过引入 Image 接口和具体的 RealImage 类以及代理类 ProxyImage,实现了代理模式。在代理类中控制对真实图片对象的访问,确保在第一次访问图片时加载图片,而在之后的访问中直接使用加载过的图片对象,从而提高系统的性能并实现对图片加载的控制。代理模式也可以用于实现缓存、安全控制、日志记录等功能。
动态代理和静态代理
动态代理和静态代理是代理模式的两种实现方式,它们之间有一些关键的区别:
1. **静态代理**:
- 在编译时就已经确定代理类、目标类和接口的关系。
- 静态代理需要为每个需要代理的类创建一个代理类,编码量较大。
- 静态代理的代理类在编译时就已经确定,无法在运行时改变。
2. **动态代理**:
- 在运行时动态生成代理类,无需提前为每个类编写代理类。
- 动态代理使用 Java 提供的 Proxy 类和 InvocationHandler 接口或第三方库生成代理类。
- 动态代理在运行时才确定代理类、目标类和接口的关系,可以更加灵活地进行代理操作。
总的来说,动态代理相比静态代理具有以下优点:
- 减少编码量:动态代理无需为每个类编写代理类,减少了重复代码量。
- 灵活性更强:可以在运行时动态地生成代理类,可以灵活地添加额外的操作或修改代理逻辑。
- 可扩展性更好:可以动态地为任何对象生成代理,而不需要为每个类都创建特定的代理类。
在实际应用中,动态代理通常更加灵活和方便,特别适用于需要在运行时动态代理的场景。而静态代理则更适合简单的代理场景,或者在无法使用动态代理的情况下使用静态代理来实现。
享元模式
享元模式是一种结构型设计模式,旨在减少应用程序内存使用量和提高性能。该模式通过共享对象来最小化内存使用,特别适用于需要大量相似对象的情况。
在享元模式中,有两种类型的状态:内部状态和外部状态。内部状态是可以共享的,它存储在享元对象内部并且不随环境变化而改变;而外部状态取决于环境,因此不可共享,在需要时需要通过参数传递给享元对象。(主要强调的是内部对象的共享和重用)
下面是一个简单的 Java 代码示例,展示了如何使用享元模式。
首先,我们定义享元接口 Shape
,表示形状对象:
public interface Shape { void draw(String color); }
然后,创建具体的享元类 Circle
,实现了 Shape
接口:
public class Circle implements Shape { private String label; public Circle(String label) { this.label = label; } @Override public void draw(String color) { System.out.println("Drawing a " + color + " " + label + " circle"); } }
接下来,创建享元工厂类,用于创建和管理享元对象:
import java.util.HashMap; import java.util.Map; public class ShapeFactory { private static final Map<String, Shape> circleMap = new HashMap<>(); public static Shape getCircle(String label) { Shape circle = circleMap.get(label); if (circle == null) { circle = new Circle(label); circleMap.put(label, circle); System.out.println("Creating a new circle with label: " + label); } else { System.out.println("Reusing existing circle with label: " + label); } return circle; } }
最后,在客户端代码中使用享元模式:
public class FlyweightPatternExample { public static void main(String[] args) { String[] labels = {"Red", "Green", "Blue", "Red", "Yellow"}; for (String label : labels) { Shape circle = ShapeFactory.getCircle(label); circle.draw(label); } } }
在这个示例中,ShapeFactory
充当享元工厂,负责创建和管理享元对象 Circle
。当需要创建新的圆形时,工厂首先检查是否已经存在具有相同标签的圆形,如果是,则直接返回已存在的圆形;如果没有,则创建一个新的圆形对象。在客户端代码中,我们可以看到相同标签的圆形对象实际上是被共享使用的。
通过享元模式,我们可以在大量相似对象的场景中,大幅度减少内存使用,并提高性能。
行为型设计模式
责任链模式
责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,它通过将请求的发送者和接收者解耦,从而使多个对象都有机会处理请求。
在责任链模式中,请求沿着一个链式结构传递,每个处理者都有机会处理请求。当一个处理者无法处理请求时,它将请求传递给下一个处理者,直到请求被处理或者所有处理者都无法处理为止。
责任链模式的核心组成部分包括:
-
抽象处理者(Handler):定义了处理请求的接口方法,并持有下一个处理者的引用。该类可以是抽象类或接口。
-
具体处理者(ConcreteHandler):实现了处理请求的接口方法,并在必要时调用下一个处理者来处理请求。如果自身能够处理请求,就处理请求;否则,将请求传递给下一个处理者。
责任链模式的工作原理如下:
-
客户端发起请求,将请求传递给责任链的第一个处理者。
-
每个处理者判断自己是否能够处理该请求,如果能够处理,则处理请求;如果不能处理,则将请求传递给下一个处理者。
-
这个过程持续进行,直到请求被处理或者整个责任链上的所有处理者都无法处理为止。
责任链模式的优点包括:
-
解耦请求发送者和接收者,使得系统的对象具有更好的灵活性。
-
可以动态的增加或修改处理者,而无需修改客户端代码。
-
提高代码的可维护性和可扩展性。
然而,责任链模式也存在一些注意事项:
-
每个请求都必须有一个处理者,否则请求无法被处理。
-
为了避免出现循环调用的情况,需要正确设置好处理者之间的关联关系。
-
处理者之间的顺序很重要,需要明确各个处理者的优先级。
总结起来,责任链模式通过将请求的发送者和接收者解耦,使得多个处理者都有机会处理请求,从而提高系统的灵活性和可扩展性。
假设我们有一个客户服务系统,需要根据用户的问题类型将问题转发给不同的处理人员,处理人员根据自己的职责来处理问题。我们可以将处理人员构建成一个责任链,每个处理人员根据自己的能力来处理问题,如果无法处理,则将问题传递给下一个处理人员。
// 抽象处理人员 public abstract class ServiceHandler { protected ServiceHandler nextHandler; public abstract void handleRequest(ServiceRequest request); public void setNextHandler(ServiceHandler handler) { this.nextHandler = handler; } } // 具体处理人员 public class TechnicalSupportHandler extends ServiceHandler { public void handleRequest(ServiceRequest request) { if (request.getProblemType().equals("Technical")) { // 处理技术问题 System.out.println("Technical support handler is handling the request."); } else if (nextHandler != null) { // 如果无法处理,则传递给下一个处理人员 nextHandler.handleRequest(request); } } } public class SalesSupportHandler extends ServiceHandler { public void handleRequest(ServiceRequest request) { if (request.getProblemType().equals("Sales")) { // 处理销售问题 System.out.println("Sales support handler is handling the request."); } else if (nextHandler != null) { // 如果无法处理,则传递给下一个处理人员 nextHandler.handleRequest(request); } } } // 服务请求 public class ServiceRequest { private String problemType; public ServiceRequest(String problemType) { this.problemType = problemType; } public String getProblemType() { return problemType; } } // 使用责任链模式处理客户服务请求 public class Client { public static void main(String[] args) { // 构建责任链:技术支持 -> 销售支持 TechnicalSupportHandler technicalHandler = new TechnicalSupportHandler(); SalesSupportHandler salesHandler = new SalesSupportHandler(); technicalHandler.setNextHandler(salesHandler); // 创建服务请求 ServiceRequest technicalRequest = new ServiceRequest("Technical"); ServiceRequest salesRequest = new ServiceRequest("Sales"); // 处理请求 technicalHandler.handleRequest(technicalRequest); technicalHandler.handleRequest(salesRequest); } }
在上述例子中,每个具体的处理人员(TechnicalSupportHandler 和 SalesSupportHandler)都可以处理特定类型的问题,如果不是自己能处理的问题,则将问题传递给链中的下一个处理人员。通过构建责任链,请求可以沿着链中的处理人员依次传递,直到有处理人员能够处理请求或者到达链的末尾。
责任链模式的优点在于将请求发送者与接收者解耦,请求发送者不需要知道请求最终由谁处理,只需要将请求发送给链中的第一个处理人员即可。同时,责任链模式也提供了灵活性,能够根据需要动态地调整或扩展责任链。
观察者模式
观察者模式是一种行为型设计模式,它允许一个对象(主题或可观察者)将其状态的改变通知给其他依赖于这个对象的对象(观察者),从而使这些观察者能够自动更新。观察者模式实现了对象之间的一对多依赖关系,当一个对象改变状态时,所有依赖它的对象都会得到通知并自动更新。
在观察者模式中,有两个主要角色:可观察者(主题)和观察者。可观察者维护一个观察者列表,并提供注册、注销、通知观察者状态变化的方法;观察者定义了在可观察者状态变化时所需执行的更新操作。
以下是一个简单的 Java 代码示例,展示了如何使用观察者模式。
首先,定义观察者接口 Observer
:
public interface Observer { void update(String message); }
然后,创建具体的观察者类 ConcreteObserver
,实现了 Observer
接口:
public 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); } }
接下来,定义可观察者接口 Subject
:
public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(String message); }
创建具体的可观察者类 ConcreteSubject
,实现了 Subject
接口:
import java.util.ArrayList; import java.util.List; public 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); } } }
最后,在客户端代码中使用观察者模式:
public class ObserverPatternExample { public static void main(String[] args) { ConcreteSubject subject = new ConcreteSubject(); Observer observer1 = new ConcreteObserver("Observer 1"); Observer observer2 = new ConcreteObserver("Observer 2"); subject.registerObserver(observer1); subject.registerObserver(observer2); subject.notifyObservers("New message for observers"); } }
在这个示例中,ConcreteSubject
充当可观察者,负责维护观察者列表并通知观察者;ConcreteObserver
充当观察者,定义了观察者收到通知后的行为。在客户端代码中,我们创建了一个具体的可观察者对象,并注册了两个具体的观察者对象,然后通过可观察者通知所有观察者,观察者收到通知并执行相应的更新操作。
通过观察者模式,可观察者对象和观察者对象之间实现了解耦,当可观察者状态变化时,所有的观察者都能够及时得到通知并进行相关操作,从而实现了松耦合的协作关系。
策略模式
策略模式是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并使它们可相互替换。这使得算法的变化独立于使用算法的客户端。简而言之,策略模式允许在运行时选择算法,而不是在编译时固定使用某种算法。
策略模式主要包含以下角色:
-
策略接口(Strategy Interface):它定义了所有支持的算法的通用接口,通过这个接口,所有的具体策略类都可以被调用。
-
具体策略类(Concrete Strategies):它实现了策略接口,提供了具体的算法实现。
-
环境类(Context):它持有一个策略接口的引用,用于执行具体的策略。
下面是一个简单的示例:假设我们有一个电商网站,针对不同的促销活动,我们想实现不同的折扣策略。
首先,定义一个抽象的折扣策略接口 DiscountStrategy
:
public interface DiscountStrategy { double applyDiscount(double originalPrice); }
然后,实现具体的折扣策略类:
针对新用户的折扣策略:
public class NewUserDiscountStrategy implements DiscountStrategy { @Override public double applyDiscount(double originalPrice) { return originalPrice * 0.9; // 新用户9折 } }
针对老用户的折扣策略:
public class OldUserDiscountStrategy implements DiscountStrategy { @Override public double applyDiscount(double originalPrice) { return originalPrice * 0.8; // 老用户8折 } }
最后,创建一个环境类 PriceCalculator
,用于执行具体的折扣策略:
public class PriceCalculator { private DiscountStrategy discountStrategy; public PriceCalculator(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public double calculateDiscountedPrice(double originalPrice) { return discountStrategy.applyDiscount(originalPrice); } }
在实际使用中,我们可以根据用户的情况动态选择折扣策略。比如:
DiscountStrategy strategy = new NewUserDiscountStrategy(); // 或者 OldUserDiscountStrategy PriceCalculator calculator = new PriceCalculator(strategy); double discountedPrice = calculator.calculateDiscountedPrice(100.0);
在这个示例中,折扣策略接口 DiscountStrategy
扮演了策略模式中的策略接口,NewUserDiscountStrategy
和 OldUserDiscountStrategy
扮演了具体的策略类,而 PriceCalculator
则扮演了环境类的角色。这个设计使得可以根据需要灵活选择具体的折扣策略,而不会影响到客户端的代码。
通过策略模式,我们可以动态地切换算法或者策略,而不需要修改原有的代码,极大地提高了代码的扩展性和维护性。
委派模式
委派模式(Delegate Pattern)是一种设计模式,它允许对象将任务委派给其他对象来执行,这些对象负责具体的子任务或者业务逻辑。委派模式实际上是一种组合模式,其中一个对象(称为委托者)将任务委托给另一个对象(称为委托对象),而委托对象则负责执行实际的任务。
举个例子来说明委派模式的含义:
假设有一个管理系统,负责安排员工的工作,其中经理负责指派具体的工作任务给员工,并告诉他们应该找谁去完成任务。这里的经理就是委托者,在没有固定员工的前提下,经理不会自己执行具体的任务,而是将任务委派给实际的员工。
// 定义委托者 public class Manager { private WorkerDelegate delegate; public Manager(WorkerDelegate delegate) { this.delegate = delegate; } public void assignTask(String task) { delegate.doTask(task); } } // 定义委托对象接口 public interface WorkerDelegate { void doTask(String task); } // 具体的委托对象,实际执行任务 public class WorkerA implements WorkerDelegate { @Override public void doTask(String task) { System.out.println("Worker A is working on task: " + task); } } // 具体的委托对象,另一个执行任务的对象 public class WorkerB implements WorkerDelegate { @Override public void doTask(String task) { System.out.println("Worker B is working on task: " + task); } }
在这个例子中,Manager 充当了委派者的角色,它并不直接执行具体的任务,而是将任务委派给 WorkerA 或 WorkerB 这样的委派对象来执行。通过这种方式,Manager 可以根据需要动态地委派任务,而无需关心具体任务的执行细节。
这种方式可以很好地降低委派者与具体任务执行者之间的耦合程度,并且使得系统具有更好的扩展性。委派模式通过将任务委派给其他对象来实现任务的分发和执行,从而使系统更加灵活和可维护。
迭代器模式
迭代器模式是一种行为设计模式,它允许以统一的方式访问一个聚合对象中各个元素,而不必暴露该对象的内部表示。迭代器模式提供了一种方法来顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部结构。
迭代器模式通常包括以下角色:
-
迭代器(Iterator):定义访问和遍历元素的接口。
-
具体迭代器(Concrete Iterator):实现迭代器接口,负责对具体聚合对象进行遍历。
-
聚合(Aggregate):定义创建相应迭代器对象的接口。
-
具体聚合(Concrete Aggregate):实现聚合接口,返回一个合适的具体迭代器。
举个例子,假设我们有一个自定义的集合类 CustomList,我们可以使用迭代器模式来实现对该集合类的遍历操作。下面是一个简单的 Java 代码示例:
// 聚合接口:定义创建迭代器对象的接口 interface Aggregate { Iterator createIterator(); } // 迭代器接口:定义访问和遍历元素的接口 interface Iterator { boolean hasNext(); Object next(); } // 具体迭代器:实现迭代器接口,负责对集合对象进行遍历 class ConcreteIterator implements Iterator { private CustomList list; private int index; public ConcreteIterator(CustomList list) { this.list = list; this.index = 0; } public boolean hasNext() { return index < list.size(); } public Object next() { Object obj = list.get(index); index++; return obj; } } // 具体聚合:实现聚合接口,返回一个合适的具体迭代器 class CustomList implements Aggregate { private Object[] elements; private int size; public CustomList() { elements = new Object[10]; // 假设初始容量为 10 size = 0; } public void add(Object element) { elements[size] = element; size++; } public Object get(int index) { return elements[index]; } public int size() { return size; } public Iterator createIterator() { return new ConcreteIterator(this); } } public class Main { public static void main(String[] args) { CustomList list = new CustomList(); list.add("A"); list.add("B"); list.add("C"); Iterator iterator = list.createIterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
在这个例子中,CustomList 充当了具体的聚合,ConcreteIterator 充当了具体的迭代器。通过迭代器模式,我们可以统一访问 CustomList 中的各个元素,而不需要暴霏内部的存储结构。
执行器模式
执行器模式(Command Pattern)是一种行为设计模式,它允许将请求封装为一个对象,从而使您能够参数化客户端与具体执行操作的对象之间的关系。在该模式中,请求以命令的形式存在,并且可以被传递、排队、记录或撤销。
执行器模式的核心组成部分包括:
1. **命令(Command)**:定义了执行操作的接口。通常包含一个执行方法,该方法在调用时会触发相应的操作。
2. **具体命令(Concrete Command)**:实现了命令接口,并包含了执行操作的具体逻辑。它通常会持有一个接收者对象,用于执行实际的操作。
3. **接收者(Receiver)**:实际执行操作的对象。具体命令对象会委托接收者来执行实际的操作。
4. **调用者(Invoker)**:持有命令对象并触发命令对象执行相应的操作。调用者不需要了解命令的具体实现细节,只需要知道如何触发命令执行。
下面是一个简单的 Java 代码示例,演示了执行器模式的实现:```java
// 定义命令接口
interface Command {
void execute();
}
// 具体命令:开灯命令
class TurnOnLightCommand implements Command {
private Light light;
public TurnOnLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
// 具体命令:关灯命令
class TurnOffLightCommand implements Command {
private Light light;
public TurnOffLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
}
// 接收者:灯
class Light {
public void turnOn() {
System.out.println("灯已打开");
}
public void turnOff() {
System.out.println("灯已关闭");
}
}
// 调用者:遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 客户端代码
public class Main {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
// 创建具体命令对象并指定接收者
Command turnOnCommand = new TurnOnLightCommand(light);
Command turnOffCommand = new TurnOffLightCommand(light);
// 创建调用者并设置命令
RemoteControl remoteControl = new RemoteControl();
remoteControl.setCommand(turnOnCommand);
// 调用者触发命令执行
remoteControl.pressButton(); // 输出:"灯已打开"
// 修改命令为关闭命令
remoteControl.setCommand(turnOffCommand);
remoteControl.pressButton(); // 输出:"灯已关闭"
}
}
在这个例子中,我们通过执行器模式实现了一个简单的遥控器控制灯的功能。命令对象将开灯和关灯操作封装起来,遥控器作为调用者持有命令对象并触发相应的操作,而灯作为接收者实际执行操作。这样,遥控器和灯之间解耦,遥控器只需要知道如何触发命令,而不需要了解具体的操作实现细节。
Spring 框架用到的设计模式
创建型模式(Creational Patterns):
工厂方法模式(Factory Method Pattern)
另一个在 Spring 框架中应用的设计模式思想是工厂模式,具体体现在 Spring 的 Bean 工厂中。Spring 使用工厂模式来创建和管理 Bean 实例,其中最常见的是使用 ApplicationContext 或 BeanFactory 这样的工厂来创建和配置 Bean 对象。这种工厂模式隐藏了对象的创建细节,使得应用程序的代码不需要直接依赖具体类的实例化过程,从而实现松耦合和更好的可维护性。
通过 ApplicationContext 或 BeanFactory,Spring 的应用程序可以通过配置文件、注解或 Java 代码来描述 Bean 对象的创建和依赖关系,而不需要直接使用 new 关键字来实例化对象。这使得系统更加灵活,能够更方便地进行扩展和修改,符合开闭原则。
-
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
Spring 框架中,单例模式被广泛应用于管理 Bean 实例,以确保在整个应用程序范围内只存在一个实例。这样做有助于节省资源,提高性能,并且在某些情况下也能确保状态的一致性。
Spring 使用了一种叫做“容器单例”的方式来管理 Bean 的实例。容器单例是指在 Spring 容器中管理的单例对象,在整个容器生命周期中只存在一个实例。这一思想借鉴了单例模式的设计理念,但并不是严格意义上的传统单例模式。在传统单例模式中,类的构造方法是私有的,通过静态方法获取实例,而 Spring 中的容器单例则是统一由 Spring 容器进行管理和维护的。
在 Spring 中,容器单例确保了应用程序中对同一个 Bean 的多次请求都返回同一个实例,这有利于避免重复创建对象,节省资源。此外,容器单例也符合 Spring 框架对 Bean 生命周期的管理,例如在初始化和销毁阶段发挥了作用。
总的来说,Spring 框架中的单例模式思想体现在对 Bean 实例的管理上,通过容器单例的方式确保了在应用程序中某个 Bean 只存在一个实例,从而提高了系统的性能和资源利用率。
建造者模式(Builder Pattern)
是的,建造者模式在 Spring 框架中也有应用。虽然建造者模式不像适配器模式、代理模式等那么显而易见,但在某些场景下,Spring 框架中的一些组件和功能确实体现了建造者模式的思想。
一个经典的例子是 Spring 中的 BeanDefinitionBuilder 类。在 Spring 中,我们可以通过 BeanDefinitionBuilder 来构建并定义 Bean 的属性、依赖关系等配置信息,然后通过 BeanDefinitionReader 将其注册到 Spring 容器中。BeanDefinitionBuilder 的设计与建造者模式相似,它通过使用链式调用的方式来设置 Bean 的属性,最终创建一个符合需求的 BeanDefinition 对象,实现了更加灵活且易于理解的 Bean 定义方式。
另外,Spring 中的 JdbcTemplate 也可以看作是建造者模式的应用。通过 JdbcTemplate,开发者可以通过链式调用的方式来构建 SQL 查询语句、设置参数等,最终执行数据库操作。这种使用方式类似于建造者模式,让开发者可以按照自己的需求来构建和执行数据库操作,提高了代码的可读性和可维护性。
总的来说,虽然建造者模式在 Spring 框架中可能没有明显的固定实现,但在一些组件和功能的设计中,我们可以看到建造者模式的思想被巧妙地应用,从而提高了代码的灵活性和可维护性。建造者模式的使用有助于简化对象的创建过程,将复杂对象的构建与表示分离,使得代码更加清晰和易于扩展。
原型模式(Prototype Pattern)
-
在 Spring 框架中,虽然没有直接使用原型模式(Prototype Pattern),但是可以从一些功能和特性中识别出与原型模式相关的设计思想。下面是一些 Spring 中应用了与原型模式类似的设计思想:
-
原型Bean(Prototype Bean): Spring 容器中的 Bean 可以被声明为原型作用域(prototype scope)。当一个 Bean 被声明为原型作用域时,每次注入或者获取该 Bean 时,容器都会创建一个新的实例。这类似于原型模式中创建新对象实例的概念。
-
Bean 的克隆: 在 Spring 中,Bean 可以通过特定的方式来克隆,例如使用 Object 类的 clone() 方法,或者在配置文件中通过配置原型 Bean 来获取 Bean 的一个新实例。这种方式类似于原型模式中通过克隆来创建新对象的过程。
-
原型注册表(Prototype Registry): 在 Spring 中,可以通过原型注册表的方式来管理原型 Bean 的创建和获取。原型注册表类似于原型模式中的原型管理器,负责管理原型对象的创建和复制。
尽管 Spring 框架没有直接使用原型模式来实现其功能,但是通过原型作用域的 Bean、Bean 的克隆和原型注册表等特性,可以看出 Spring 框架中采用了与原型模式类似的设计思想。这种设计思想使得 Spring 容器能够有效地管理原型 Bean 的创建和使用,同时为开发者提供了更灵活的对象创建和管理方式。
-
####
结构型模式(Structural Patterns):
适配器模式(Adapter Pattern)
在 Spring 框架中,适配器模式(Adapter Pattern)被广泛应用于各个模块和功能。以下是一些 Spring 中使用适配器模式的常见示例:
-
MVC 框架中的适配器: 在 Spring 的 MVC(Model-View-Controller)框架中,适配器模式用于连接控制器(Controller)和处理器(Handler)。Spring 提供了适配器类(如
HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
)来处理不同类型的控制器(如实现HttpRequestHandler
接口的处理器、继承AbstractController
的处理器等),并将请求适配到对应的处理器上。 -
AOP 框架中的适配器: 在 Spring 的 AOP(面向切面编程)框架中,适配器模式用于连接切面(Aspect)和切点(Pointcut)。Spring 提供了适配器类(如
MethodBeforeAdviceAdapter
、AfterReturningAdviceAdapter
)来将切面适配到特定类型的切点上,从而在切点的前后执行相应的通知。 -
事务管理中的适配器: Spring 的事务管理模块借助适配器模式来实现不同事务管理器之间的适配。Spring 提供了多个事务管理器适配器类(如
DataSourceTransactionManager
、JtaTransactionManager
),将不同的事务管理器(如 JDBC 事务、JTA 事务)适配成 Spring 框架能够识别和使用的统一方式。 -
消息处理中的适配器: 在 Spring 的消息处理模块中,适配器模式用于连接消息发送者和接收者。Spring 提供了适配器类(如
MessageListenerAdapter
、JmsListenerEndpointAdapter
),将不同类型的消息发送者适配到统一的消息接收器上,使得消息的发送和接收可以进行适配和协同工作。
总之,适配器模式在 Spring 框架中被广泛使用,用于连接和适配不同组件、接口和机制。通过适配器模式,Spring 实现了多个模块之间的解耦,提供了统一、灵活和可扩展的框架结构。
桥接模式(Bridge Pattern)
在 Spring 框架中,桥接模式经常被用于实现依赖注入(Dependency Injection,DI)和面向接口编程。
Spring 框架通过桥接模式将组件的实现与其依赖关系解耦,提供了一种灵活的方式来管理组件之间的关系。以下是桥接模式在 Spring 中的一些应用场景:
-
依赖注入(Dependency Injection,DI):Spring 使用桥接模式通过依赖注入的方式来解耦组件之间的依赖关系。通过将依赖关系定义在组件的接口或抽象类中,并将其与具体的实现进行桥接,Spring 可以在运行时动态地将适当的实现注入到组件中。
-
JDBC 桥接:Spring 的 JDBC 模块提供了一个 JDBC 桥接,它允许开发人员使用 Spring 的 JdbcTemplate 来操作数据库,而不必关心底层的 JDBC API。这个桥接模式隐藏了底层的 JDBC 细节,并提供了更简洁、更易用的 API。
-
AOP(面向切面编程)桥接:Spring 的 AOP 模块使用了桥接模式来提供面向切面编程的功能。通过定义切面(Aspect)和通知(Advice),并将它们桥接到目标对象上,Spring 能够实现横切关注点的模块化和重用。
-
模板方法模式桥接:Spring 的 JdbcTemplate 和 HibernateTemplate 等模板类使用了桥接模式。这些模板类将数据库操作的细节封装起来,并提供统一的模板方法供开发人员使用。通过桥接的设计,模板类能够适配不同的数据访问技术(如 JDBC、Hibernate)。
总的来说,桥接模式在 Spring 框架中广泛应用于解耦组件之间的关系,提供了强大的灵活性和可扩展性。它帮助开发人员实现面向接口编程,并通过依赖注入、AOP、模板方法等技术来管理组件之间的依赖关系,提高了系统的可测试性、可维护性和可扩展性。
组合模式(Composite Pattern)
装饰者模式(Decorator Pattern)
在 Spring 框架中,装饰器模式的应用并不是特别明显,因为 Spring 更多地使用了代理模式来实现 AOP(面向切面编程)和事务管理等功能。然而,Spring 框架中也存在一些类似装饰器模式的特性,用于给对象动态地添加功能或修改行为。
-
BeanPostProcessor: Spring 框架中的 BeanPostProcessor 接口就类似于装饰者模式,在对象初始化的时候可以对对象进行增强处理。具体来说,BeanPostProcessor 接口中有两个方法
postProcessBeforeInitialization
和postProcessAfterInitialization
,这两个方法允许开发者在初始化 bean 之前和之后对其进行一些自定义操作,比如包装成代理对象,修改属性等。 -
AOP装饰器: 在 Spring 框架中,AOP(面向切面编程)可以使用代理模式来实现,但在某种程度上也展现了装饰器模式的特征。在 AOP 中,可以通过切面(Aspect)给原始的对象动态地添加功能,比如日志记录、事务管理等。虽然是代理模式在工作,但从功能扩展的角度来看,也具有一定的装饰者模式的特点。
虽然 Spring 框架中使用的装饰者模式并不明显,但可以看到一些类似装饰者模式的特性被应用在 Spring 的不同组件中,从而提供了灵活地对对象进行增强和修改行为的能力。
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
-
享元模式在对象池中的应用:Spring 中还提供了对象池(例如数据库连接池、线程池等)来管理对象的创建和重用。对象池可以看作享元模式的一种扩展,有效地管理对象的创建和销毁,并实现对象的共享,从而提高性能和降低资源消耗。
代理模式(Proxy Pattern)
在 Spring 框架中还有一个经典的设计模式是代理模式。代理模式在 Spring 中被广泛应用于 AOP(面向切面编程)和事务管理等方面。
在 AOP 中,Spring 使用了动态代理来实现横切关注点(如日志记录、性能监控、事务管理等)的功能。通过代理模式,Spring 创建了代理对象来包装原始对象,并在代理对象中插入横切逻辑,使得这些逻辑能够与原始对象的业务逻辑分离,从而实现了更好的模块化和可维护性。
在 Spring 的事务管理中,代理模式同样扮演了重要的角色。Spring 的事务管理通过 AOP 实现,允许开发者通过事务代理对象来管理事务的开始、提交、回滚等操作。这种方式下,代理模式帮助开发者将事务管理逻辑与业务逻辑分离,减少了业务代码中和事务处理相关的重复性代码,提高了代码的可读性和可维护性。
此外,在 Spring 的 IoC(控制反转)容器中,Bean 对象的实例化和生命周期管理也经常通过代理模式来完成。Spring 通过代理模式在 Bean 的实例化、初始化、销毁等过程中插入相应的逻辑,实现了对 Bean 生命周期的管理和控制。
总的来说,代理模式在 Spring 框架中发挥着重要的作用,帮助实现了 AOP、事务管理和 IoC 容器等功能。代理模式的运用使得 Spring 框架在面向对象设计中更加灵活、可扩展和易于维护。
行为型模式(Behavioral Patterns)
模板模式
还有一个在 Spring 框架中常见的设计模式是模板方法模式。这种模式在 Spring 的事务管理中得到了广泛的应用。
在 Spring 中,事务管理是通过 AOP 的方式实现的。Spring 提供了一种名为 TransactionTemplate 的模板类,该类封装了事务的创建、提交、回滚等操作,并提供了一个回调方法的机制,用户可以在回调方法中定义自己的业务逻辑。
使用 TransactionTemplate,开发者可以通过扩展并重写其中的回调方法,实现自己的业务逻辑,并在事务的开始和结束时自动执行相应的操作。这种方式下,事务处理的开始和结束的细节由框架控制,而业务逻辑的实现由开发者来定义,实现了框架与业务逻辑的解耦。
TransactionTemplate 的设计符合模板方法模式的思想,在模板方法模式中,定义一个抽象的父类,其中包含一个模板方法和若干个具体的子类实现。模板方法封装了通用的算法结构,而具体的实现由子类来完成,从而实现了代码的复用和扩展。
总结一下,在 Spring 框架中使用模板方法模式的案例是事务管理。通过 TransactionTemplate,开发者可以在事务的模板方法中定义自己的业务逻辑,而框架负责管理事务的创建和提交等细节,实现了代码的解耦和提高了开发效率。
观察者模式
在 Spring 框架中,观察者模式的概念被广泛应用于事件驱动的编程模型。Spring 提供了丰富的事件支持机制,可以在应用程序中实现可观察者和观察者之间的解耦合,从而实现事件的发布和订阅。
Spring 中观察者模式的应用主要体现在以下几个方面:
-
应用事件:Spring 框架中有一个事件体系,通过
ApplicationEvent
及其子类表示事件对象。开发者可以在自己的应用程序中定义各种事件,比如用户注册事件、订单支付事件等。这些事件就充当着可观察者,可以被其他组件订阅。 -
事件发布者:在 Spring 中,通过
ApplicationEventPublisher
接口或者使用ApplicationEventPublisherAware
接口实现类,可以向应用程序中发布事件,充当着事件的发布者。 -
事件监听器:通过实现
ApplicationListener
接口或者使用@EventListener
注解,可以创建事件监听器,充当观察者。监听器可以订阅感兴趣的事件,并在事件发生时执行相应的逻辑。
下面是一个简单的示例,展示了 Spring 中观察者模式的应用:
首先,定义一个自定义事件类 MyCustomEvent
:
import org.springframework.context.ApplicationEvent; public class MyCustomEvent extends ApplicationEvent { private String message; public MyCustomEvent(Object source, String message) { super(source); this.message = message; } public String getMessage() { return message; } }
然后,创建一个事件发布者 MyEventPublisher
:
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; public class MyEventPublisher implements ApplicationEventPublisherAware { private ApplicationEventPublisher eventPublisher; @Override public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) { this.eventPublisher = eventPublisher; } public void publishCustomEvent(String message) { eventPublisher.publishEvent(new MyCustomEvent(this, message)); } }
接下来,定义一个事件监听器 MyEventListener
:
import org.springframework.context.ApplicationListener; public class MyEventListener implements ApplicationListener<MyCustomEvent> { @Override public void onApplicationEvent(MyCustomEvent event) { System.out.println("Received custom event - " + event.getMessage()); } }
最后,在 Spring 配置文件中进行相关配置:
<bean id="myEventPublisher" class="com.example.MyEventPublisher" /> <bean id="myEventListener" class="com.example.MyEventListener" />
在这个示例中,MyEventPublisher
充当事件的发布者,调用 publishCustomEvent
方法发布自定义事件;MyEventListener
充当事件监听器,通过实现 ApplicationListener
接口来订阅自定义事件,并在事件发生时执行相应的逻辑。通过这种方式,实现了可观察者和观察者之间的解耦合,符合观察者模式的设计思想。
总之,Spring 框架中的事件机制提供了一种高效的观察者模式的实现,可以让应用程序中的各个组件之间实现解耦合,更好地处理事件驱动的编程模型。
责任链模式
在 Spring 框架中,责任链模式通常用于实现拦截器和过滤器链,以及事件处理器链,充分发挥了责任链模式的灵活性和可扩展性。以下是 Spring 框架中责任链模式的一些应用场景:
-
拦截器链:Spring MVC 中的拦截器链就是典型的责任链模式。拦截器按照配置的顺序依次执行,每个拦截器都有机会在请求处理前或处理后执行相应的逻辑。如果某个拦截器决定拦截了请求,后续的拦截器将不再执行,类似于责任链模式中的处理者链。
-
过滤器链:在 Spring Security 中,过滤器链用于处理安全相关的操作。每个过滤器都有机会对请求进行安全处理,如果某个过滤器处理了请求,后续的过滤器就不再执行。
-
事件处理器链:Spring 框架中的事件驱动模型也可以使用责任链模式。通过
ApplicationEvent
和ApplicationListener
,开发者可以定义多个事件处理器,每个事件处理器可以根据自己的逻辑来处理事件,如果其中某个事件处理器处理了事件,后续的处理器将不再执行。
在这些场景中,Spring 框架充分利用责任链模式的特点,实现了灵活的请求处理和事件处理机制。责任链模式使得每个处理器能够独立地处理请求或事件,并根据自己的逻辑来决定是否传递给下一个处理器,从而实现了更好的模块化和扩展性。通过配置管理和依赖注入,Spring 框架提供了便利的方式来构建和管理责任链,使其成为实际项目中常用的设计模式之一。
策略模式
在 Spring 框架中,策略模式通常用于实现不同的业务逻辑之间的选择和切换。Spring 提供了多种方式来应用策略模式,以下是一些典型的应用场景:
-
Bean 的选择策略: 在 Spring IoC 容器中,可以使用策略模式来选择需要实例化的 bean。通过实现接口或者注解的方式,定义多个备选的具体 bean,并使用策略模式来选择合适的实例。例如,使用
@Conditional
注解可以根据条件选择不同的 bean 实例。 -
AOP 中的切面选择策略: Spring AOP 提供了一种方便的方式来通过切面来处理横切关注点。在实际应用中,可以使用策略模式来选择不同的切面实现,根据具体的业务需求动态地切换切面的行为。
-
处理器选择策略: 在 Spring MVC 中,可以使用策略模式来选择不同的处理器(Controller)来处理请求。通过配置和路由规则,根据请求的特征选择不同的处理器来处理请求,实现灵活的请求处理策略。
-
缓存策略选择: 在 Spring 中,可以使用策略模式来选择不同的缓存策略。通过实现
CacheManager
接口和自定义的缓存策略类,可以灵活地选择使用哪种缓存策略,以提高系统性能。
在这些场景中,Spring 框架应用了策略模式来实现不同算法或者策略的选择和切换。通过使用灵活的配置和扩展机制,Spring 框架提供了便利的方式来切换不同的策略实现,以满足不同的业务需求。策略模式帮助提高系统的可扩展性和灵活性,同时降低了各个模块之间的耦合度。
Mybaits中常用的设计模式
MyBatis 是一个流行的持久层框架,它使用了多种设计模式来实现其核心功能。下面是 MyBatis 框架中一些常用的设计模式及其用途的详细说明:
-
工厂模式(Factory Pattern):MyBatis 使用工厂模式来创建和管理 SqlSession 对象。SqlSessionFactory 是 SqlSession 的工厂类,它封装了创建 SqlSession 实例的逻辑,并隐藏了具体的实现细节。通过工厂模式,MyBatis 可以根据配置信息来动态地创建不同类型的 SqlSession 对象。
-
建造者模式(Builder Pattern):MyBatis 采用建造者模式来构建配置对象 Configuration。ConfigurationBuilder 是一个建造者类,它负责根据配置文件中的信息以及用户的自定义配置来构建 Configuration 对象。通过建造者模式,MyBatis 可以解耦配置信息的解析和对象的创建过程。
-
代理模式(Proxy Pattern):MyBatis 使用代理模式来实现面向接口的动态代理。SqlSession 接口的实现类是由 MyBatis 在运行时动态生成的代理类。代理模式使得 MyBatis 可以在执行数据库操作之前和之后进行一些额外的操作,如日志记录、事务管理等。
-
模板模式(Template Pattern):MyBatis 使用模板模式来定义 SQL 执行的基本流程。SqlSession 接口中的方法定义了操作数据库的步骤,包括打开连接、提交事务、执行 SQL 语句等。具体的实现类将实现这些方法,并提供自定义的逻辑。
-
装饰器模式(Decorator Pattern):MyBatis 使用装饰器模式来扩展和定制 SqlSession 的功能。例如,MyBatis 提供了一个缓存功能,它通过装饰器模式将缓存逻辑添加到 SqlSession 执行过程中。
-
策略模式(Strategy Pattern):MyBatis 使用策略模式来选择不同的执行策略。在执行 SQL 语句时,根据配置信息和用户的选择,MyBatis 可以使用不同的执行策略,如 JDBC 执行策略、存储过程执行策略、批量执行策略等。
-
观察者模式(Observer Pattern):
-
一对一关系:MyBatis 中的
org.apache.ibatis.transaction.Transaction
接口及其实现类(如org.apache.ibatis.transaction.managed.ManagedTransaction
)使用了观察者模式。事务管理器(Transaction Manager)充当了 Subject(被观察者)的角色,而事务对象(Transaction)则是 Observer(观察者)的角色。当触发事务状态发生变化时,事务管理器会通知事务对象进行相应的操作。 -
一对多关系:MyBatis 中的
org.apache.ibatis.parsing.GenericTokenParser
类使用了观察者模式。该类用于解析 SQL 语句中的占位符,并在解析过程中触发 TokenHandler(观察者)的处理方法。
-
-
迭代器模式(Iterator Pattern):
-
MyBatis 中的
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
类中使用了迭代器模式来遍历数据库结果集。该类使用到了java.sql.ResultSet
的迭代器接口java.sql.ResultSet.next()
方法来获取数据库中的每一行数据。 -
此外,MyBatis 中的
org.apache.ibatis.cursor.Cursor
接口也是迭代器模式的体现。Cursor
接口提供了对数据库结果集的游标操作,可以逐个获取结果集的每一行数据,而不需要一次性加载到内存中
-
这些设计模式的使用使得 MyBatis 框架具有灵活、可扩展和易于维护的特性。通过合理应用这些设计模式,MyBatis 可以实现对数据库的高效访问、简化开发过程,并提供丰富的拓展能力。
常见的面试题
适配器模式和和装饰器模式以及代理模式的区别?
适配器模式、装饰器模式和代理模式是设计模式中常见的结构型模式,它们各自有不同的作用和应用场景。下面详细介绍它们之间的区别:
1. 适配器模式(Adapter Pattern):
- 作用:适配器模式旨在使原本因接口不兼容而无法一起工作的类能够协同工作,它将一个类的接口转换成客户希望的另一个接口。
- 实现:适配器模式通过一个适配器类来对原有的类进行包装,使原有的类具备新的接口。
- 关键角色:适配器模式包括目标接口(Target)、适配器(Adapter)、适配者(Adaptee)。
- 例子:比如在面向对象编程中,对不同接口的类进行适配,使它们能够协同工作。
2. 装饰器模式(Decorator Pattern)**:
- 作用:装饰器模式通过动态地将责任附加到对象上,来扩展对象的功能,同时又不改变其接口。
- 实现:装饰器模式通过递归地组合对象来实现功能的增强,可以动态地为对象添加额外的行为。
- 关键角色:装饰器模式包括抽象组件(Component)、具体组件(ConcreteComponent)、装饰器(Decorator)、具体装饰器(ConcreteDecorator)。
- 例子:在 Java 中的 I/O 类中,通过装饰器模式可以动态地为文件读写操作添加缓冲、加密等功能。
3. 代理模式(Proxy Pattern):
- 作用:代理模式为其他对象提供一种代理以控制对这个对象的访问,可以在访问对象时进行一些附加的操作。
- 实现:代理模式通过代理类来包装实际的对象,控制对其的访问,实现了与实际对象对接口的一致性。
- 关键角色:代理模式包括真实主题(Real Subject)、抽象主题(Subject)、代理(Proxy)。
- 例子:如网络代理、虚拟代理、保护代理等,都是代理模式的应用。
在总体上来说,这三种模式的区别在于应用场景和目的:
- 适配器模式主要用于解决接口不兼容的问题,让不同接口的类能够协同工作。
- 装饰器模式主要用于动态地扩展对象的功能,实现功能的增强而不改变接口。
- 代理模式主要用于控制对对象的访问,可以对访问对象进行一些附加操作。
选择合适的模式取决于你的需求和设计目标,这些模式都有各自独特的优势和适用场景。
代理模式及动态代理的实现
代理模式属于结构型模式,为其他对象提供⼀种代理以控制对这个对象的访问。优点是可以增强⽬标对
象的功能,降低代码耦合度,扩展性好。缺点是在客户端和⽬标对象之间增加代理对象会导致请求处理
速度变慢,增加系统复杂度。
Spring 利⽤动态代理实现 AOP,如果 Bean 实现了接⼝就使⽤ JDK 代理,否则使⽤ CGLib 代理。
静态代理:代理对象持有被代理对象的引⽤,调⽤代理对象⽅法时也会调⽤被代理对象的⽅法,但是会
在被代理对象⽅法的前后增加其他逻辑。需要⼿动完成,在程序运⾏前就已经存在代理类的字节码⽂
件,代理类和被代理类的关系在运⾏前就已经确定了。 缺点是⼀个代理类只能为⼀个⽬标服务,如果要
服务多种类型会增加⼯作量。
动态代理:动态代理在程序运⾏时通过反射创建具体的代理类,代理类和被代理类的关系在运⾏前是不
确定的。动态代理的适⽤性更强,主要分为 JDK 动态代理和 CGLib 动态代理。
- JDK动态代理:通过 Proxy 类的 newInstance ⽅法获取⼀个动态代理对象,需要传⼊三个参
数,被代理对象的类加载器、被代理对象实现的接⼝,以及⼀个 InvocationHandler 调⽤处理
器来指明具体的逻辑,相⽐静态代理的优势是接⼝中声明的所有⽅法都被转移到
InvocationHandler 的 invoke ⽅法集中处理。
- CGLib动态代理:JDK 动态代理要求实现被代理对象的接⼝,⽽ CGLib 要求继承被代理对象,如
果⼀个类是 fifinal 类则不能使⽤ CGLib 代理。两种代理都在运⾏期⽣成字节码,JDK 动态代理直接
写字节码,⽽ CGLib 动态代理使⽤ ASM 框架写字节码,ASM 的⽬的是⽣成、转换和分析以字节
数组表示的已编译 Java 类。 JDK 动态代理调⽤代理⽅法通过反射机制实现,⽽ GCLib 动态代理通
过 FastClass 机制直接调⽤⽅法,它为代理类和被代理类各⽣成⼀个类,该类为代理类和被代理类
的⽅法分配⼀个 int 参数,调⽤⽅法时可以直接定位,因此调⽤效率更⾼。