22种设计模式详解
设计模式通常分为三类:创建型(Creational),结构型(Structural)和行为型(Behavioral)。
设计模式可以看作是程序设计的经验总结,它们可以提高代码的可维护性、可扩展性、和复用性。在实际的软件开发中,设计模式经常会被结合起来使用,来解决复杂的设计问题。
创建型模式
这类模式专注于如何创建对象或类的实例。
- 单例模式(Singleton):确保类只有一个实例,并提供对该实例的全局访问点。
- 工厂方法模式(Factory Method):定义一个接口并让子类决定实例化哪个类。
- 抽象工厂模式(Abstract Factory):创建相关或依赖对象的家族,而不需要明确指定具体类。
- 建造者模式(Builder):将一个复杂对象的构建与它的表示分离,这样同样的构建过程可以创建出不同的表示。
- 原型模式(Prototype):通过复制现有的实例来创建新的实例。
单例模式
单例模式的目的是确保一个类只有一个实例,并提供一个全局访问点。下面是一个线程安全的单例模式实现,采用"双重检查锁定"(Double-Checked Locking)方案。这种方案在多线程环境下可以保持高性能。
java
public class Singleton {
// volatile关键字确保多线程环境下的可见性和有序性
private static volatile Singleton instance;
// 私有构造方法,防止外部通过new创建多个实例
private Singleton() {
}
// 提供一个静态方法供外部调用获取单例
public static Singleton getInstance() {
// 第一重检查:减少不必要的同步,提高效率
if (instance == null) {
// 锁定类对象进行同步
synchronized (Singleton.class) {
// 第二重检查:确保只创建一次实例
if (instance == null) {
instance = new Singleton(); // 创建实例
}
}
}
return instance;
}
// 示例方法,表明可以通过单例调用普通方法
public void doSomething() {
System.out.println("Doing something...");
}
}
工厂方法模式
以下是一个使用Java编写的工厂方法模式的简单示例,其中包含了线程安全的考虑。我们将创建一个工厂接口和几个实现这个接口的具体产品类。为了保证线程安全,我们将工厂方法设置为同步方法。
java
// Product接口定义了产品的方法
interface Product {
void use();
}
// ConcreteProductA 是 Product 接口的一个实现类
class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductA");
}
}
// ConcreteProductB 是 Product 接口的一个实现类
class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("Using ConcreteProductB");
}
}
// Creator定义了工厂方法的接口
abstract class Creator {
// 工厂方法是同步的,以确保线程安全
public synchronized Product factoryMethod(String type) {
switch (type) {
case "A":
return new ConcreteProductA();
case "B":
return new ConcreteProductB();
}
return null;
}
}
// ConcreteCreator 是工厂方法的具体实现者
class ConcreteCreator extends Creator {
// 实现工厂方法,返回一个 Product 类型的对象
@Override
public synchronized Product factoryMethod(String type) {
return super.factoryMethod(type);
}
}
public class FactoryMethodExample {
public static void main(String[] args) {
// 创建工厂对象
Creator creator = new ConcreteCreator();
// 通过工厂方法创建产品对象
Product productA = creator.factoryMethod("A");
if (productA != null) {
productA.use(); // 使用产品A
}
Product productB = creator.factoryMethod("B");
if (productB != null) {
productB.use(); // 使用产品B
}
}
}
在这个示例中,我们定义了一个名为Product
的接口,它有一个use
方法。我们还创建了两个Product
的实现类ConcreteProductA
和ConcreteProductB
。Creator
抽象类定义了工厂方法factoryMethod
的接口,而ConcreteCreator
类继承自Creator
并实现了这个方法。
已经使用synchronized
关键字标记了factoryMethod
方法,这可以确保在多线程环境中访问该工厂方法时的线程安全性。不过,在高并发场景下,使用同步方法可能会降低性能。
在main
方法中,我们创建了一个ConcreteCreator
实例,并调用了它的factoryMethod
来创建产品。我们检查了返回的产品对象是否不为null
,然后调用了它的use
方法。
这是一个简单的例子,展示了工厂方法模式和线程安全的一种实现方式。在实际的应用程序中,工厂方法模式可能需要根据具体的需求进行更细致的处理。
抽象工厂模式
下面是一个使用Java实现的线程安全的抽象工厂模式的例子。在这个例子中,我们将创建一个表示两种类型的数据库连接的抽象工厂(比如:MySQL和Oracle)。
首先,我们定义两个数据库连接的接口,并为每种类型的数据库连接创建具体的实现类。
java
// 数据库连接的接口
interface DatabaseConnection {
void connect();
}
// MySQL数据库连接的实现
class MySqlConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to MySQL database...");
}
}
// Oracle数据库连接的实现
class OracleConnection implements DatabaseConnection {
@Override
public void connect() {
System.out.println("Connecting to Oracle database...");
}
}
接下来,我们定义抽象工厂的接口,并为每种类型的数据库创建具体的工厂。
java
// 数据库连接工厂的抽象接口
interface DatabaseConnectionFactory {
DatabaseConnection createConnection();
}
// MySQL数据库连接工厂的具体实现
class MySqlConnectionFactory implements DatabaseConnectionFactory {
@Override
public DatabaseConnection createConnection() {
return new MySqlConnection();
}
}
// Oracle数据库连接工厂的具体实现
class OracleConnectionFactory implements DatabaseConnectionFactory {
@Override
public DatabaseConnection createConnection() {
return new OracleConnection();
}
}
现在,我们需要一个工厂生产者来生成具体的工厂实例。为了确保线程安全,我们将使用ReentrantLock
。
java
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 工厂生产者
class FactoryProducer {
// 确保线程安全使用锁
private static final Lock lock = new ReentrantLock();
private static FactoryProducer instance;
// 私有构造器,防止外部直接实例化
private FactoryProducer() {
}
// 获取实例的静态方法
public static FactoryProducer getInstance() {
if (instance == null) {
lock.lock();
try {
if (instance == null) {
instance = new FactoryProducer();
}
} finally {
lock.unlock();
}
}
return instance;
}
// 根据类型创建对应的数据库连接工厂
public DatabaseConnectionFactory getFactory(String databaseType) {
if ("MySQL".equalsIgnoreCase(databaseType)) {
return new MySqlConnectionFactory();
} else if ("Oracle".equalsIgnoreCase(databaseType)) {
return new OracleConnectionFactory();
}
throw new IllegalArgumentException("Unknown database type: " + databaseType);
}
}
在上述代码中,我们使用了一个称为"双重检查锁定"的方法来确保FactoryProducer
是线程安全的单例。这种方法在getInstance
方法中先检查是否已经有实例存在,如果不存在,就加锁并再次检查。这样做可以防止多个线程同时进入这个代码块,并确保在任何时刻只创建一个FactoryProducer
实例。
现在我们可以使用FactoryProducer
来获取数据库连接:
java
public class AbstractFactoryDemo {
public static void main(String[] args) {
// 获取工厂生产者单例
FactoryProducer producer = FactoryProducer.getInstance();
// 获取MySQL数据库连接工厂,并创建连接
DatabaseConnectionFactory mySqlFactory = producer.getFactory("MySQL");
DatabaseConnection mySqlConnection = mySqlFactory.createConnection();
mySqlConnection.connect();
// 获取Oracle数据库连接工厂,并创建连接
DatabaseConnectionFactory oracleFactory = producer.getFactory("Oracle");
DatabaseConnection oracleConnection = oracleFactory.createConnection();
oracleConnection.connect();
}
}
这个例子演示了如何使用锁机制(ReentrantLock
)在Java中创建一个线程安全的抽象工厂模式。注意,为了保持示例的简洁性,没有涉及异常处理和资源管理的具体细节。在实际应用中,可能还需要考虑处理数据库连接的释放、异常处理以及其他并发问题。
工厂模式和抽象工厂模式有什么区别和相似点呢?
工厂方法模式和抽象工厂模式是两种常用的创建型设计模式,它们都用于创建对象,但是在应用场景、复杂性以及它们解决的问题上存在差异。
工厂方法模式 是一种创建型设计模式,它定义了一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化延迟到子类中进行。换言之,工厂方法模式中,一个类通过其子类来指定创建哪个对象。
抽象工厂模式是一种创建型设计模式,它提供了一个接口,用于创建相关或依赖对象的家族,而不需要指定具体类。抽象工厂允许客户端使用抽象的接口来创建一组相关的产品,而不需要知道实际生产出的具体产品是什么。
区别:
- 意图不同:工厂方法模式的意图是允许一个类在不指定具体类的情况下创建对象,是针对单一产品的创建。抽象工厂模式旨在创建一系列相关或依赖对象,而不需要指定它们的具体类。
- 复杂度:抽象工厂模式比工厂方法模式更为复杂,因为它涉及到多个产品对象的创建。
- 适用范围:工厂方法模式适用于一个类不知道它所必须创建对象的类的情况;抽象工厂模式适用于处理多个相关或相互依赖的对象族,而不是单一对象。
- 层次结构:工厂方法模式通常由一个抽象工厂和多个具体工厂组成,每个具体工厂负责一个具体产品的创建。抽象工厂模式则有多个抽象产品和一个抽象工厂,具体工厂将创建具有多个产品类别的产品族。
- 产品关系:工厂方法模式每次仅创建一个产品,而抽象工厂模式创建的是产品族,产品族中的多个产品通常是相关联的。
总结:工厂方法模式解决了子类选择实例化对象的问题,而抽象工厂模式解决了产品族的创建问题。选择使用哪一种模式通常取决于软件设计问题的复杂度以及你要解决的问题。在实际应用中,两种模式有时也会结合使用。
建造者模式
建造者模式(Builder Pattern)是一种创建对象的设计模式,它用来解决复杂对象的构建问题。这种模式下,用户只需要指定复杂对象的类型和内容,建造者负责将对象的各个部分按照一定的顺序组装起来,最终构建成复杂对象。
建造者模式通常涉及以下几个角色:
Product(产品角色)
:这是我们要构建的复杂对象,通常包含多个组成部分。Builder(抽象建造者)
:定义建造步骤的接口。ConcreteBuilder(具体建造者)
:实现Builder接口,具体化对象的创建。Director(指挥者)
:负责安排已有模块的顺序,然后告诉Builder开始建造。Client(客户端)
:请求一个产品对象,指定产品类型。
下面是一个简单的建造者模式的Java实现:
java
// Product(产品角色):一个具体的产品
class Product {
private String partA;
private String partB;
private String partC;
public void setPartA(String partA) {
this.partA = partA;
}
public void setPartB(String partB) {
this.partB = partB;
}
public void setPartC(String partC) {
this.partC = partC;
}
public void show() {
// 展示产品的特性
System.out.println("部件A:" + partA);
System.out.println("部件B:" + partB);
System.out.println("部件C:" + partC);
}
}
// Builder(抽象建造者):定义创建产品各个部件的接口
interface Builder {
void buildPartA();
void buildPartB();
void buildPartC();
Product getResult();
}
// ConcreteBuilder(具体建造者):实现Builder接口,完成具体产品的组建
class ConcreteBuilder implements Builder {
private Product product = new Product();
@Override
public void buildPartA() {
product.setPartA("建造 PartA");
}
@Override
public void buildPartB() {
product.setPartB("建造 PartB");
}
@Override
public void buildPartC() {
product.setPartC("建造 PartC");
}
@Override
public Product getResult() {
// 返回最终组建好的产品
return product;
}
}
// Director(指挥者):调用建造者对象来创建复杂对象的各个部分
class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
// 构建一个产品
public Product construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
// Client(客户端):请求一个产品对象
public class Client {
public static void main(String[] args) {
Builder builder = new ConcreteBuilder(); // 创建一个建造者对象
Director director = new Director(builder); // 创建指挥者,注入建造者
Product product = director.construct(); // 指挥者负责最终产品的组装过程
product.show(); // 展示最终产品
}
}
在上述代码中,Product
类代表最终要构建的复杂对象,Builder
接口定义了创建该产品各个部分的方法,ConcreteBuilder
类提供了接口的具体实现,并存储了构建过程中产生的产品实例。Director
类负责控制构建过程,它知道构建的步骤,但不知道具体实现。最后,客户端Client
类的作用是发起构建请求,并展示结果。
建造者模式的优点包括:
- 将构建代码与表示代码分离,使得构建过程可以复用。
- 提高了构建复杂对象的安全性和稳定性。
- 构建过程可以控制产品的内部构造。
原型模式
原型模式(Prototype Pattern)是指创建一个可定制的对象的类型,然后通过拷贝这个原型来创建新的可定制的对象,而无需了解任何创建的细节。原型模式属于创建型设计模式的一种。它的主要目的是减少创建对象的成本,特别是当创建一个新对象所需的成本比拷贝一个现有对象更高时。在Java中,原型模式通常是通过实现Cloneable
接口并覆盖clone()
方法来实现的。
原型模式通常用于以下场景:
- 当新对象的创建比较复杂时,可以通过复制已有对象的方式进行创建。
- 当创建对象的成本较高,需要优化性能时。
- 当一个对象需要提供给其他对象访问,而不希望这些对象修改其值时,可以提供一个拷贝。
下面是使用Java实现原型模式的一个示例代码:
java
// Cloneable接口是一个标记接口,它表示类的实例是可以被克隆的
public class PrototypePatternDemo {
// 创建一个实现了Cloneable接口的抽象类
public static abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public String getType(){
return type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
// 实现克隆方法,这里捕获异常是为了简化代码,实际使用时可以更具体地处理异常
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
// 创建具体的形状类
public static class Rectangle extends Shape {
public Rectangle(){
type = "Rectangle";
}
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
public static class Square extends Shape {
public Square(){
type = "Square";
}
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
public static class Circle extends Shape {
public Circle(){
type = "Circle";
}
@Override
void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
// 创建一个类,从数据库获取实体类,并把它们存储在一个Hashtable中
public static class ShapeCache {
private static Hashtable<String, Shape> shapeMap = new Hashtable<String, Shape>();
public static Shape getShape(String shapeId) {
Shape cachedShape = shapeMap.get(shapeId);
//这里使用clone方法来获取新的实例
return (Shape) cachedShape.clone();
}
// 对每种形状都运行数据库查询,并创建该形状
// shapeMap.put(shapeKey, shape);
// 例如,我们要添加三种形状
public static void loadCache() {
Circle circle = new Circle();
circle.setId("1");
shapeMap.put(circle.getId(),circle);
Square square = new Square();
square.setId("2");
shapeMap.put(square.getId(),square);
Rectangle rectangle = new Rectangle();
rectangle.setId("3");
shapeMap.put(rectangle.getId(),rectangle);
}
}
public static void main(String[] args) {
ShapeCache.loadCache();
Shape clonedShape = ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());
Shape clonedShape2 = ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType());
Shape clonedShape3 = ShapeCache.getShape("3");
System.out.println("Shape : " + clonedShape3.getType());
}
}
在这个示例中,我们有一个Shape
抽象类,这个类实现了Cloneable
接口。我们有几个具体的形状类(Rectangle
、Square
、Circle
),它们都继承自Shape
。我们还有一个ShapeCache
类,它允许我们存储形状对象的实例,并在需要时克隆它们。main
方法中模拟了客户端代码,它从ShapeCache
中获取克隆的对象。
注意,在实现clone()
方法时,如果对象中包含了对其他对象的引用,需要提供深拷贝的实现,否则克隆的对象可能会共享内部的复杂结构,这可能不是我们希望的结果。上述代码中,因为Shape
中没有引用类型的字段,所以浅拷贝方式已经足够。
结构型模式
这类模式处理类和对象的组合。
- 适配器模式(Adapter):允许将一个类的接口转换成客户期望的另一个接口。
- 桥接模式(Bridge):将抽象部分与实现部分分离,使它们可以独立地变化。
- 组合模式(Composite):将对象组合成树形结构以表示"部分-整体"的层次结构。
- 装饰器模式(Decorator):动态地给一个对象添加一些额外的职责。
- 外观模式(Facade):为子系统中的一组接口提供一个统一的高层接口,以使子系统更容易使用。
- 享元模式(Flyweight):运用共享技术有效地支持大量细粒度的对象。
- 代理模式(Proxy):为另一个对象提供一个替身或者占位符以控制对这个对象的访问。
适配器模式
适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户期望的另一个接口。这样做可以使原本由于接口不兼容而不能一起工作的类可以一起工作。适配器模式通常用于系统升级或集成一些外部系统时,后者的接口与现有的系统接口不一致时。
适配器模式主要有以下角色:
- 目标接口(Target):定义客户所期望的接口,可以是一个抽象类或接口。
- 需要适配的类(Adaptee):定义了一个已存在的接口,这个接口需要被适配。
- 适配器(Adapter):对Adaptee的接口与Target接口进行适配;适配器实现了Target接口,并在内部封装了一个Adaptee对象,它将Target接口的调用转换为对Adaptee接口的调用。
下面是用Java实现的适配器模式的一个例子,其中包括了这三个角色:
java
// 目标接口
public interface LightningPhone { // 目标接口定义了插入Lightning接口的操作
void recharge();
void useLightning();
}
// 需要适配的类
public class AndroidPhone { // Adaptee是一个已经存在的类,具备了Micro USB接口
public void recharge() {
System.out.println("Recharging Android phone...");
}
public void useMicroUSB() {
System.out.println("Using Micro USB...");
}
}
// 适配器
public class AndroidToLightningAdapter implements LightningPhone { // 适配器实现了目标接口
private AndroidPhone androidPhone;
public AndroidToLightningAdapter(AndroidPhone androidPhone) {
this.androidPhone = androidPhone;
}
@Override
public void recharge() {
androidPhone.recharge(); // 适配器调用Adaptee类的方法
}
@Override
public void useLightning() {
System.out.println("Adapting from Micro USB to Lightning...");
androidPhone.useMicroUSB(); // 适配器转换接口调用
}
}
// 客户端代码
public class AdapterPatternDemo {
public static void main(String[] args) {
// 客户期望使用Lightning接口的设备
LightningPhone iphone = new LightningPhone() {
@Override
public void recharge() {
System.out.println("Recharging iPhone...");
}
@Override
public void useLightning() {
System.out.println("Using Lightning...");
}
};
iphone.useLightning();
iphone.recharge();
// 客户有一个安卓手机,但是想要使用Lightning接口
AndroidPhone androidPhone = new AndroidPhone();
LightningPhone adaptedPhone = new AndroidToLightningAdapter(androidPhone); // 创建适配器
// 通过适配器,客户可以使用Lightning接口来充电
adaptedPhone.useLightning();
adaptedPhone.recharge();
}
}
这个例子中,AndroidPhone
是需要适配的类(Adaptee),它具有Micro USB接口。LightningPhone
是目标接口,定义了雷电接口的操作。AndroidToLightningAdapter
则是一个适配器,它实现了Lightning接口并在内部持有一个AndroidPhone
的实例,从而使原本仅支持Micro USB充电的Android手机也能通过Lightning接口进行充电。在客户端代码中,通过使用适配器,客户可以无缝使用Lightning接口对Android手机进行充电,即使它本身不支持这种接口。适配器 AndroidToLightningAdapter
无缝地将 LightningPhone
接口的调用转换为 AndroidPhone
接口的调用。
桥接模式
桥接模式(Bridge Pattern)是一种设计模式,它将抽象部分与其实现部分分离,使它们可以独立变化。在桥接模式中,抽象部分通常定义高层的控制逻辑,它将实际工作委托给实现部分的接口。实现部分通常是由具体实现的类来定义,它们真正完成底层的详细工作。使用桥接模式可以提高代码的可扩展性和可维护性,尤其是在需要支持不断变化的实现或配置时。
桥接模式主要包含以下角色:
- 抽象(Abstraction):定义抽象类的接口,它持有一个实现(Implementor)接口的引用,并可以包含定义抽象业务逻辑的方法。
- 改进的抽象(RefinedAbstraction):扩展抽象类,实现或者覆盖抽象类定义的方法。
- 实现者(Implementor):定义实现类的接口,这个接口不一定要与抽象类的接口一致,实际上二者可以完全不同。一般而言,实现者接口仅提供基本操作,而抽象类则基于这些基本操作定义较高层次的操作。
- 具体实现者(ConcreteImplementor):实际实现Implementor接口的类,具体化了实现接口的方法。
下面是一个使用Java实现的桥接模式示例:
java
// 实现者接口
interface Color {
String applyColor();
}
// 具体实现者A
class RedColor implements Color {
@Override
public String applyColor() {
return "Red";
}
}
// 具体实现者B
class BlueColor implements Color {
@Override
public String applyColor() {
return "Blue";
}
}
// 抽象
abstract class Shape {
protected Color color; // 持有实现部分的引用
public Shape(Color color) {
this.color = color;
}
abstract public String draw(); // 抽象方法
}
// 改进的抽象
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
@Override
public String draw() {
return "Circle drawn in " + color.applyColor() + " color.";
}
}
// 改进的抽象
class Square extends Shape {
public Square(Color color) {
super(color);
}
@Override
public String draw() {
return "Square drawn in " + color.applyColor() + " color.";
}
}
public class BridgePatternDemo {
public static void main(String[] args) {
Shape redCircle = new Circle(new RedColor());
Shape blueSquare = new Square(new BlueColor());
System.out.println(redCircle.draw());
System.out.println(blueSquare.draw());
}
}
如上所示,我们定义了两个具体实现者RedColor
和BlueColor
实现了Color
接口,两个改进的抽象Circle
和Square
继承了Shape
抽象类。当我们在客户端创建具体的Shape
类实例时,通过构造函数传入特定的Color
实现,以此来"桥接"抽象和实现。这样,如果我们需要添加新的颜色或者形状,我们只需要扩展Color
接口或Shape
抽象类即可,一种颜色或形状的改变,不会影响到另一种。这就是桥接模式的强大之处。
组合模式
组合模式是一种结构型设计模式,用于以树形结构表示部分以及整体层次的构成。它允许客户端以一致的方式处理个别对象以及对象组合。组合模式使得客户端能头使用单个对象和组合对象的相同方法。
组合模式的主要角色如下:
- 组件(Component):定义一个接口或抽象类,用于访问和管理Component的子部件。
- 叶子(Leaf):在组合中表示叶节点对象,叶节点没有子节点。
- 组合(Composite):定义有子部件的那些部件的行为,存储子部件,并实现在Component接口中与子部件有关的操作。
接下来是使用Java实现的一个简单的组合模式示例,以文件系统为例,其中包含文件(Leaf)和文件夹(Composite):
java
// Component
abstract class FileSystemNode {
protected String name;
public FileSystemNode(String name) {
this.name = name;
}
public abstract void print(String prefix);
}
// Leaf
class File extends FileSystemNode {
public File(String name) {
super(name);
}
@Override
public void print(String prefix) {
System.out.println(prefix + "File: " + name);
}
}
// Composite
class Directory extends FileSystemNode {
private List<FileSystemNode> children = new ArrayList<>();
public Directory(String name) {
super(name);
}
public void addChildren(FileSystemNode node) {
children.add(node);
}
@Override
public void print(String prefix) {
System.out.println(prefix + "Directory: " + name);
// Iterate over all children and invoke print
for (FileSystemNode node : children) {
// Recursive call to print the substructure
node.print(prefix + " ");
}
}
}
public class CompositeDemo {
public static void main(String[] args) {
Directory root = new Directory("root");
root.addChildren(new File("file1.txt"));
root.addChildren(new File("file2.docx"));
Directory subDir = new Directory("subdir");
subDir.addChildren(new File("file3.pdf"));
root.addChildren(subDir);
// Prints the entire structure
root.print("");
}
}
代码运行后,会打印如下文件结构:
Directory: root
File: file1.txt
File: file2.docx
Directory: subdir
File: file3.pdf
在这个例子中,FileSystemNode
是抽象组件,它定义了print
方法,这个方法用于打印文件系统的某个节点。File
类是叶子节点,它代表文件系统中的一个文件,只需要实现print
方法打印自己。Directory
类是组合类,它代表文件系统中的一个目录,可以包含其他文件或目录,也需要实现print
方法,打印目录下的所有节点。
这个组合模式允许客户端使用统一的方式(print
方法)来处理单个文件和整个目录,从而简化了对文件系统的操作。
装饰器模式
装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许用户在不改变对象的接口的前提下,为对象添加新的功能。这是通过创建一个包装对象来实现的,这个包装对象持有原对象的引用,并且提供一个与原对象相同的接口。同时,它还可以在调用原对象的方法之前或之后添加额外的行为。装饰器模式主要用于扩展一个对象的功能,同时又不想通过继承增加子类的方式,避免了类的爆炸式增长。
装饰器模式包括以下角色:
- 抽象组件(Component):定义了一个对象接口,可以给这些对象动态地添加职责。
- 具体组件(ConcreteComponent):定义了具体的对象,实现了抽象组件的接口。
- 抽象装饰器(Decorator):持有一个组件(Component)对象的引用,并定义了符合组件接口的抽象方法。
- 具体装饰器(ConcreteDecorator):实现抽象装饰器定义的接口,并添加额外的功能。
以下是使用Java实现的简单装饰器模式示例代码:
java
// Component接口
public interface Component {
void operation();
}
// ConcreteComponent实现了Component接口
public class ConcreteComponent implements Component {
@Override
public void operation() {
// 原始功能
System.out.println("ConcreteComponent operation executed.");
}
}
// Decorator抽象类,也实现了Component接口
public abstract class Decorator implements Component {
// 持有一个Component类型的对象引用
protected Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
// 委托给被装饰者执行
component.operation();
}
}
// ConcreteDecoratorA具体实现了Decorator
public class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
// 首先执行Decorator的operation方法
super.operation();
// 然后执行本类独有的功能,相当于对原operation进行了装饰
addedBehavior();
}
// ConcreteDecoratorA独有的方法
private void addedBehavior() {
System.out.println("ConcreteDecoratorA added behavior.");
}
}
// ConcreteDecoratorB具体实现了Decorator
public class ConcreteDecoratorB extends Decorator {
public ConcreteDecoratorB(Component component) {
super(component);
}
@Override
public void operation() {
// 首先执行Decorator的operation方法
super.operation();
// 然后执行本类独有的功能,相当于对原operation进行了装饰
addedState = "New State";
System.out.println("ConcreteDecoratorB added state: " + addedState);
}
// ConcreteDecoratorB持有的独有状态
private String addedState;
}
// 客户端使用代码
public class DecoratorClient {
public static void main(String[] args) {
// 创建具体组件
Component component = new ConcreteComponent();
// 装饰组件
Decorator decoratorA = new ConcreteDecoratorA(component);
Decorator decoratorB = new ConcreteDecoratorB(decoratorA);
// 执行操作,可以看到装饰后的行为
decoratorB.operation();
}
}
在这个例子中,ConcreteComponent
是基本的实现类,它实现了 Component
接口。Decorator
是一个抽象的装饰类,包含了一个 Component
类型的对象,作为其成员变量。ConcreteDecoratorA
和 ConcreteDecoratorB
是两个具体的装饰类,它们分别添加了一些特定的行为。
在客户端 DecoratorClient
中,首先创建了一个 ConcreteComponent
对象,然后分别用 ConcreteDecoratorA
和 ConcreteDecoratorB
来装饰这个对象。当调用 decoratorB.operation()
时,可以看到被装饰对象的原始行为以及额外添加的行为。这样做的好处是,我们不必修改 ConcreteComponent
类的代码,就可以对其功能进行扩展。
代理模式
代理模式是一个非常广泛使用的结构型设计模式,其基本原理是为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,并且可以在不改变目标对象的前提下增加额外的功能。
代理模式通常涉及以下角色:
- Subject(抽象主题角色):定义了真实对象和代理对象的共同接口,这样一来在任何使用真实对象的地方都可以使用代理对象。
- RealSubject(真实主题角色):定义了代理角色所代表的真实对象。
- Proxy(代理角色):保存一个引用使得代理可以访问实体,并且可以提供一个与Subject的接口相同的接口,这样代理就可以用来替代实体。
- Client(客户端):通过Subject接口操作实体和代理对象。
下面是一个简单的Java代码实例,展示了代理模式的实现。我们将以一个简单的场景为例:一个打印消息的接口和它的实现类,以及一个代理类来控制对实现类的访问。
java
// Subject接口
interface Printer {
void printMessage();
}
// RealSubject类
class RealPrinter implements Printer {
@Override
public void printMessage() {
System.out.println("Hello, this is the RealPrinter printing a message.");
}
}
// Proxy类
class PrinterProxy implements Printer {
private RealPrinter realPrinter; // 代理类内部包含一个对真实对象的引用
// 构造方法
public PrinterProxy(RealPrinter realPrinter) {
this.realPrinter = realPrinter;
}
// 代理方法,可以在这里加入控制逻辑
@Override
public void printMessage() {
// 代理可以在调用真实对象之前或之后执行一些操作
System.out.println("PrinterProxy: Before the real printer prints the message.");
realPrinter.printMessage(); // 调用真实对象的方法
System.out.println("PrinterProxy: After the real printer has printed the message.");
}
}
// Client类
public class Client {
public static void main(String[] args) {
RealPrinter realPrinter = new RealPrinter(); // 创建真实对象
PrinterProxy printerProxy = new PrinterProxy(realPrinter); // 创建代理对象,并将真实对象传递给它
printerProxy.printMessage(); // 通过代理对象访问真实对象的方法
}
}
在上面的代码中,Printer
接口定义了printMessage
方法,这是客户端可以调用的方法。RealPrinter
是实现了Printer
接口的真实类,其包含了具体的实现逻辑。PrinterProxy
是代理类,它也实现了Printer
接口,并在内部持有一个RealPrinter
的引用。当PrinterProxy
的printMessage
方法被调用时,它可以在调用RealPrinter
的printMessage
方法前后执行一些附加操作,比如日志记录、权限控制、延迟加载等。
最后,Client
类中的main
方法创建了真实对象和代理对象,并通过代理对象来访问真实对象的服务。通过这种方式,客户端是与RealPrinter
解耦的,这样我们就可以修改代理类而不影响到客户端代码。
代理模式与装饰者模式区别
- 设计目的 :装饰者模式主要用于增加对象的功能,而代理模式主要用于控制对对象的访问。
- 功能扩展:装饰者模式支持动态地堆叠多个装饰器以增加多个职责,代理模式一般只有一个代理层,重点在于代理的过程中可以增加额外的逻辑。
- 接口实现:虽然两者都实现了相同的接口,但装饰者模式强调的是增加行为,代理模式强调的是控制访问。
外观模式
外观模式(Facade Pattern)是一种设计模式,用于为复杂的系统提供一个简化的界面。它通过创建一个外观类来隐藏系统的复杂性,并提供一个访问系统的简单接口。这样客户代码就可以通过这个简单接口与复杂系统进行交互,而不需要了解系统内部的复杂逻辑。
外观模式通常包含以下几个角色:
- Facade(外观):一个为子系统中的一组接口提供一个统一的接口的类。Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- Subsystems(子系统):在外观模式中,子系统是指那些实际执行功能的类。它们实现了系统的功能,但直接使用它们通常比较复杂。
以下是一个使用Java实现外观模式的简单例子,包括相关代码注释:
java
// 子系统1:银行账户服务
class BankAccountService {
public void createAccount(String customerId) {
System.out.println("Creating bank account for customer " + customerId);
// 实现创建账户的逻辑
}
// ... 其他账户相关操作
}
// 子系统2:贷款服务
class LoanService {
public void issueLoan(String accouuntId, double amount) {
System.out.println("Issuing a loan of " + amount + " to account " + accouuntId);
// 实现借贷服务的逻辑
}
// ... 其他贷款相关操作
}
// 子系统3:信用评分服务
class CreditService {
public boolean checkCreditRating(String customerId) {
System.out.println("Checking credit rating for customer " + customerId);
// 实现信用评分的逻辑
// 在实际应用中,我们可能会调用一个外部服务或者算法来计算信用评分
return true; // 假设客户信用良好
}
// ... 其他信用评分相关操作
}
// 外观类:银行服务外观
class BankServiceFacade {
private BankAccountService accountService = new BankAccountService();
private LoanService loanService = new LoanService();
private CreditService creditService = new CreditService();
// 创建账户,并根据信用评分决定是否发放贷款
public void createAccountAndIssueLoan(String customerId, double loanAmount) {
accountService.createAccount(customerId); // 创建账户
boolean isEligible = creditService.checkCreditRating(customerId); // 检查信用
if (isEligible) {
loanService.issueLoan(customerId, loanAmount); // 发放贷款
}
}
// ... 可以添加其他外观方法来简化子系统的操作
}
public class FacadePatternDemo {
public static void main(String[] args) {
// 客户端代码仅与外观类交互
BankServiceFacade bankFacade = new BankServiceFacade();
bankFacade.createAccountAndIssueLoan("CUST123", 1000.0);
// 客户端不需要直接跟复杂的子系统交互
}
}
上面的代码中,BankAccountService、LoanService、CreditService是子系统类,它们实现了银行服务的不同功能。BankServiceFacade是外观类,它提供了一个createAccountAndIssueLoan方法,这个方法封装了子系统的操作。客户端只需要与BankServiceFacade交互,而不需要直接与子系统类打交道,从而简化了客户端的使用,并且将子系统的复杂性隐藏起来。
享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,其主要目的是减少创建对象的数量,以减少内存占用和提高性能。这种模式主要适用于大量对象的场景,其中很多对象的状态可以被共享。
享元模式原理:
享元模式的基本原理是将一个对象的状态分为内部状态和外部状态。
- 内部状态(Intrinsic State):对象共享出来的信息,储存在享元信息内部,不会随环境的改变而有所不同。
- 外部状态(Extrinsic State):对象得以依赖的一个标记,是随环境改变而改变的、不可以共享的状态。
为了使对象可以共享,享元模式中通常会有一个工厂类,用来创建和管理享元对象。
享元模式角色:
- Flyweight(抽象享元类):这是一个接口或抽象类,声明具体享元类公共的方法,这些方法可以向外部状态传入外部状态。
- ConcreteFlyweight(具体享元类):继承或实现抽象享元类,实例被共享的部分。
- UnsharedConcreteFlyweight(非共享具体享元类):并非所有的享元对象都需要被共享,非共享的享元对象通常是对共享享元对象的组合对象。
- FlyweightFactory(享元工厂类):负责创建和管理享元对象。
- Client(客户端):使用享元对象的类。
示例代码:
下面以一个简单的圆形对象为例,演示享元模式。我们将创建一个圆形类作为具体享元类,颜色作为内部状态,位置信息作为外部状态。
java
import java.util.HashMap;
import java.util.Map;
// 抽象享元类
interface Circle {
void draw(int x, int y, int radius);
}
// 具体享元类
class ConcreteCircle implements Circle {
private String color; // 内部状态
public ConcreteCircle(String color) {
this.color = color;
}
@Override
public void draw(int x, int y, int radius) {
System.out.println("Drawing Circle [Color: " + color + ", x: " + x + ", y: " + y + ", radius: " + radius + "]");
}
}
// 享元工厂类
class CircleFactory {
private static final Map<String, Circle> circleMap = new HashMap<>();
// 获取圆的方法
public static Circle getCircle(String color) {
Circle circle = circleMap.get(color);
if (circle == null) {
circle = new ConcreteCircle(color);
circleMap.put(color, circle);
System.out.println("Creating circle of color : " + color);
}
return circle;
}
}
// 客户端类
public class FlyweightPatternDemo {
public static void main(String[] args) {
Circle circle1 = CircleFactory.getCircle("Red");
circle1.draw(100, 100, 50);
Circle circle2 = CircleFactory.getCircle("Green");
circle2.draw(200, 100, 50);
Circle circle3 = CircleFactory.getCircle("Red");
circle3.draw(100, 200, 50);
Circle circle4 = CircleFactory.getCircle("Green");
circle4.draw(200, 200, 50);
Circle circle5 = CircleFactory.getCircle("Red");
circle5.draw(100, 300, 50);
// 测试输出,只创建了两个实例
System.out.println("Total created circle instances: " + CircleFactory.circleMap.size());
}
}
在这个例子中,ConcreteCircle
是 Circle
接口(抽象享元类)的具体实现,代表具体的享元类。CircleFactory
是享元工厂类,它根据颜色创建圆的实例并存储在一个 Map 中,以便重用。客户端(FlyweightPatternDemo
)通过调用工厂类的静态方法 getCircle
来获取圆实例,并传入外部状态(即圆的位置信息和半径)给享元对象。如果在工厂的 Map 中已经有相应颜色的圆形,则会重用该对象而不是重新创建新的实例,这样就实现了实例的共享,减少了对象的创建。
注意:在上面的代码中,圆的颜色作为内部状态,是可以共享的;而圆的位置和半径作为外部状态,在每次调用 draw
方法时作为参数传入。
行为型模式
这类模式特别关注对象之间的通信。
- 模板方法模式(Template Method):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中去实现。
- 命令模式(Command):将请求封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。
- 迭代器模式(Iterator):提供一种方法,顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
- 观察者模式(Observer):定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并被自动更新。
- 中介者模式(Mediator):封装一系列对象相互作用的方式,使得这些对象不需要显式地相互引用,并且可以独立地改变它们之间的交互。
- 备忘录模式(Memento):在不破坏封装的前提下,捕获并保存一个对象的内部状态,这样以后就可将该对象恢复到保存的状态。
- 解释器模式(Interpreter):给定一个语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
- 状态模式(State):允许一个对象在其内部状态改变时改变它的行为。
- 策略模式(Strategy):定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换。
- 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作。它可以在不改变各元素类的前提下定义作用于这些元素的新操作。
模板方法模式
模板方法模式是一种行为设计模式,它定义了一个操作中的算法的骨架,将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法的结构的情况下,重新定义算法的某些步骤。
原理
在模板方法模式中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。这样,我们在抽象类中定义了一个顶级算法的结构,而将一些步骤的具体实现留给了子类。
角色
- 抽象类(AbstractClass): 定义模板方法的骨架,以及抽象操作(由子类实现)和具体操作。
- 具体子类(ConcreteClass): 实现抽象类中定义的一个或多个抽象步骤,从而完成特定的算法。
示例代码
以下是Java中使用模板方法模式的一个简单示例。我们将创建一个抽象类Game
,它定义了执行游戏的模板方法playGame
。这个方法包含了一系列将在子类中实现的步骤。
java
// 抽象类
abstract class Game {
// 模板方法,定义游戏的步骤骨架
final void playGame() {
initialize();
startPlay();
endPlay();
}
// 需要由子类实现的方法
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
}
// 具体子类
class Football extends Game {
@Override
void initialize() {
System.out.println("Football Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Football Game Started. Enjoy the game!");
}
@Override
void endPlay() {
System.out.println("Football Game Finished!");
}
}
// 具体子类
class Basketball extends Game {
@Override
void initialize() {
System.out.println("Basketball Game Initialized! Start playing.");
}
@Override
void startPlay() {
System.out.println("Basketball Game Started. Enjoy the game!");
}
@Override
void endPlay() {
System.out.println("Basketball Game Finished!");
}
}
// 演示类
public class TemplateMethodPatternDemo {
public static void main(String[] args) {
// 使用模板方法
Game game = new Football();
game.playGame(); // 使用Football类的实现
System.out.println();
game = new Basketball();
game.playGame(); // 使用Basketball类的实现
}
}
在这个示例中,Game
类定义了三个步骤:initialize
、startPlay
和 endPlay
。这些步骤在 playGame
方法(模板方法)中按顺序调用。Football
和 Basketball
类分别继承了 Game
类,并实现了这些步骤。当调用 playGame
方法时,将执行具体子类中定义的步骤。
这样,我们有一个顶层的算法结构(模板方法),它由多个子类中实现的步骤构成,从而使得子类能够在不改变算法结构的情况下,改变算法的某些特定步骤。
命令模式
命令模式(Command Pattern)是一种行为设计模式,允许将一个请求封装为一个对象,从而使您可以使用不同的请求、队列或日志请求,并支持可撤销操作。它主要用于把行为请求者和行为实现者解耦。
命令模式中的角色通常有以下几种:
- Command(命令接口):定义了命令的接口,声明了执行命令的 execute() 方法。
- ConcreteCommand(具体命令):Command 接口的实现对象,它关联了 Receiver,实现了 execute() 方法。
- Receiver(接收者):负责具体的行为逻辑。
- Invoker(调用者):负责调用命令对象执行请求。
- Client(客户端):创建具体的命令对象,并且设置命令对象的接收者。
下面是实现命令模式的一个简单 Java 例子:
java
// Step 1: 创建命令接口。
public interface Command {
void execute();
}
// Step 2: 创建接收者类。
public class Receiver {
public void performAction() {
System.out.println("Action is performed.");
}
}
// Step 3: 创建实现了 Command 接口的具体命令类。
public class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.performAction(); // 调用接收者的方法来执行命令。
}
}
// Step 4: 创建调用者类。
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
public void call() {
command.execute(); // 调用命令执行请求。
}
}
// Step 5: 使用 Client 类使用命令模式。
public class CommandPatternDemo {
public static void main(String[] args) {
// 客户端创建一个接收者对象。
Receiver receiver = new Receiver();
// 客户端创建一个命令对象并设置其接收者。
Command command = new ConcreteCommand(receiver);
// 客户端创建调用者,将命令对象设置进去。
Invoker invoker = new Invoker(command);
// 调用者执行命令。
invoker.call();
}
}
以上代码中:
Command
接口定义了执行操作的方法execute()
。ConcreteCommand
是实现了Command
接口的具体命令类,它持有一个对Receiver
对象的引用,并实现了execute()
方法,该方法调用Receiver
的动作。Receiver
类定义了命令的具体行为。Invoker
类持有命令对象,并在某个时间点调用命令对象的execute()
方法来执行请求。Client
是客户端类,负责创建一个具体命令并且设置其接收者。
这个模式特别有用在需要对命令进行记录、处理或者撤销的场景中,比如事务处理、GUI 按钮和菜单操作等多种应用场景。
迭代器模式
迭代器模式(Iterator Pattern)属于行为型模式之一,用于提供一种方法顺序访问一个集合对象中的各个元素,而又不暴露该对象的内部表示。迭代器模式可以使遍历各种集合的操作使用统一的接口,简化了集合的使用,并且它将遍历和集合的数据结构分离,支持不同的遍历方式。
迭代器模式通常包含以下角色:
- 抽象迭代器(Iterator):定义访问和遍历元素的接口,通常包含 hasNext()、next() 等方法。
- 具体迭代器(ConcreteIterator):实现迭代器接口,并保持迭代过程中的游标位置。
- 抽象集合(Aggregate):定义创建相应迭代器对象的接口。
- 具体集合(ConcreteAggregate):实现创建相应迭代器的具体方法,该类含有集合中的对象。
以下是使用 Java 实现迭代器模式的一个简单例子:
java
// 抽象迭代器
interface Iterator<T> {
boolean hasNext(); // 是否还有下一个元素
T next(); // 获取下一个元素
}
// 具体迭代器
class ConcreteIterator<T> implements Iterator<T> {
private List<T> collection; // 被迭代的集合
private int pos = 0; // 当前遍历的位置
public ConcreteIterator(List<T> collection) {
this.collection = collection;
}
@Override
public boolean hasNext() {
return pos < collection.size();
}
@Override
public T next() {
if (hasNext()) {
return collection.get(pos++);
}
return null;
}
}
// 抽象集合
interface Aggregate<T> {
Iterator<T> createIterator(); // 创建对应的迭代器
}
// 具体集合
class ConcreteAggregate<T> implements Aggregate<T> {
private List<T> items; // 集合元素
public ConcreteAggregate() {
items = new ArrayList<>();
}
public void add(T item) {
items.add(item);
}
public T get(int index) {
return items.size() > index ? items.get(index) : null;
}
@Override
public Iterator<T> createIterator() {
return new ConcreteIterator<>(items);
}
}
public class IteratorPatternDemo {
public static void main(String[] args) {
// 创建具体集合并添加元素
ConcreteAggregate<String> aggregate = new ConcreteAggregate<>();
aggregate.add("Element A");
aggregate.add("Element B");
aggregate.add("Element C");
// 创建迭代器
Iterator<String> iterator = aggregate.createIterator();
// 使用迭代器遍历集合
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
上面的代码中,我们定义了迭代器接口 Iterator
和具体迭代器 ConcreteIterator
,它们用于抽象和具体的遍历操作。然后我们定义了集合接口 Aggregate
以及具体的集合 ConcreteAggregate
,用于存储和管理元素。ConcreteAggregate
通过 createIterator()
方法提供一个迭代器,客户端可以使用这个迭代器来遍历集合中的元素而不需要知道集合的内部实现。
在 IteratorPatternDemo
主类中,我们创建了一个 ConcreteAggregate
实例,并向它添加了一些字符串元素。然后,我们使用 createIterator()
方法创建了一个迭代器实例,并使用它来遍历整个集合。
迭代器模式的优点是它支持以不同的方式遍历一个聚合对象,而且迭代器简化了聚合类。它还支持多种遍历同时进行,因为每个迭代器对象包含它自己的遍历状态。缺点是对于比较简单的聚合遍历,使用迭代器模式可能会有些过度。
观察者模式
观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。观察者模式通常用于实现分布式事件处理系统、新闻订阅、在对象之间建立一种触发机制等场景。
观察者模式主要有以下角色:
- 抽象主题(Subject):提供一个接口,用于增加和删除观察者对象。
- 具体主题(ConcreteSubject):实现抽象主题的接口,当其内部状态发生变化时,通知所有登记过的观察者。
- 抽象观察者(Observer):定义一个更新接口,用于在得到主题更改通知时更新自己。
- 具体观察者(ConcreteObserver):实现抽象观察者角色的更新接口,以便在获取到变更通知时更新自身状态。
下面是一个使用Java实现的观察者模式的示例:
java
import java.util.ArrayList;
import java.util.List;
// 抽象主题(Subject)
interface Subject {
void registerObserver(Observer observer); // 注册观察者
void removeObserver(Observer observer); // 移除观察者
void notifyObservers(); // 通知所有观察者
}
// 具体主题(ConcreteSubject)
class NewsPublisher implements Subject {
private List<Observer> observers;
private String latestNews;
public NewsPublisher() {
this.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() {
for (Observer observer : observers) {
observer.update(latestNews);
}
}
public void publishNews(String news) {
this.latestNews = news;
notifyObservers();
}
}
// 抽象观察者(Observer)
interface Observer {
void update(String news);
}
// 具体观察者(ConcreteObserver)
class Subscriber implements Observer {
private String name;
public Subscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " received news: " + news);
}
}
// 运行观察者模式示例
public class ObserverPatternDemo {
public static void main(String[] args) {
NewsPublisher newsPublisher = new NewsPublisher();
Observer observer1 = new Subscriber("Alice");
Observer observer2 = new Subscriber("Bob");
newsPublisher.registerObserver(observer1);
newsPublisher.registerObserver(observer2);
newsPublisher.publishNews("Breaking News: Observer pattern in Java!");
newsPublisher.removeObserver(observer1);
newsPublisher.publishNews("Update: Observer pattern still rocking!");
}
}
在这个例子中:
NewsPublisher
类是一个具体主题,它有一个列表来管理观察者,并提供注册和移除观察者的功能,当有新闻发布时,它就会通知所有的观察者。Subscriber
类是一个具体观察者,它实现了Observer
接口,并在更新时打印出新闻信息。ObserverPatternDemo
类中创建了一个主题和两个观察者,并展示了观察者模式的工作流程,其中观察者Alice在第一次通知后被移除,所以在第二次通知时她将不会接收到新闻更新。
中介者模式
中介者模式(Mediator Pattern)是一种行为设计模式,它通过一个中介对象来封装一系列对象交互的方式。中介者模式使得各对象不需要显示地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
中介者模式通常用于一组对象已定义良好但是复杂的方式进行通信的场合,特别是在设计的时候不能预见这些对象的交互方式。
中介者模式主要包含以下几种角色:
- 中介者(Mediator):定义各个同事对象通信的接口。
- 具体中介者(ConcreteMediator):实现中介者的接口,并协调各个同事类之间的交互关系。
- 同事类(Colleague):定义了一个接口,其中包含了中介者对象。一个同事类通常有一或多个同事对象,当一个同事类的状态发生变化时,它可以调用中介者对象与其他同事类进行通信。
- 具体同事类(Concrete Colleague):实现同事类的接口,并通知中介者其状态的改变。
接下来,我将用Java代码实现一个简单的中介者模式例子。假设我们有一个聊天室应用,聊天室充当中介者,用户(User)充当同事类。
java
// 中介者接口
interface Mediator {
void sendMessage(String message, User user);
void addUser(User user);
}
// 具体中介者类 - 聊天室
class ChatRoom implements Mediator {
private List<User> users;
public ChatRoom() {
this.users = new ArrayList<>();
}
@Override
public void sendMessage(String message, User user) {
for (User u : this.users) {
// 不要将消息发送给自己
if (u != user) {
u.receive(message);
}
}
}
@Override
public void addUser(User user) {
this.users.add(user);
}
}
// 同事类 - 用户
abstract class User {
protected Mediator mediator;
protected String name;
public User(Mediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}
public abstract void send(String message);
public abstract void receive(String message);
}
// 具体同事类 - 聊天室用户
class ChatUser extends User {
public ChatUser(Mediator mediator, String name) {
super(mediator, name);
}
@Override
public void send(String message) {
System.out.println(this.name + " 发送消息: " + message);
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println(this.name + " 收到消息: " + message);
}
}
// 客户端代码
public class MediatorPatternDemo {
public static void main(String[] args) {
Mediator mediator = new ChatRoom();
User user1 = new ChatUser(mediator, "John");
User user2 = new ChatUser(mediator, "Jane");
User user3 = new ChatUser(mediator, "Bob");
mediator.addUser(user1);
mediator.addUser(user2);
mediator.addUser(user3);
user1.send("Hi everyone!");
user2.send("Hello John!");
}
}
在这个例子中,ChatRoom
是一个具体的中介者,它协调 User
对象之间的通信。每个 User
都知道它的 Mediator
对象。当 User
想要发送消息时,它会调用 Mediator
的 sendMessage
方法,而不是直接给其他 User
发送消息,这样就解耦了用户之间的通信。
客户端代码中创建了一个聊天室和三个用户,然后用户发送消息。我们可以看到当用户发送消息时,这个消息是通过中介者聊天室转发给其他用户的,而不是直接由用户交互。
中介者模式的关键优势是减少了类之间的直接通讯,从而降低了系统的耦合度。缺点包括可能会创建一个非常复杂的中介者类,因为它集中了所有的通信逻辑。
备忘录模式
备忘录模式是一种行为设计模式,它允许对象在不违反封装的情况下捕获并外部化其内部状态,以便于在以后将对象恢复到此状态。备忘录模式通常用于实现撤销和重做操作。
在备忘录模式中,有三个角色:
- 发起人(Originator): 负责创建一个备忘录,用以记录当前时刻它的内部状态,并可使用备忘录恢复内部状态。
- 备忘录(Memento): 负责存储发起人的内部状态,并可防止除发起人以外的对象访问备忘录。
- 看护者(Caretaker): 负责保存备忘录,但是不会对备忘录的内容进行操作或检查。
下面是使用Java实现备忘录模式的简单例子,以一个简单的数字存储器为例:
java
// Memento(备忘录)类
class Memento {
private int state;
public Memento(int state) {
this.state = state;
}
public int getState() {
return state;
}
}
// Originator(发起人)类
class Originator {
private int state;
public void setState(int state) {
this.state = state;
System.out.println("State set to: " + state);
}
public int getState() {
return state;
}
// 保存状态到备忘录
public Memento saveToMemento() {
System.out.println("Saving state to Memento.");
return new Memento(state);
}
// 从备忘录恢复状态
public void restoreFromMemento(Memento memento) {
state = memento.getState();
System.out.println("State restored from Memento: " + state);
}
}
// Caretaker(看护者)类
class Caretaker {
private List<Memento> mementoList = new ArrayList<>();
public void addMemento(Memento memento) {
mementoList.add(memento);
}
public Memento getMemento(int index) {
return mementoList.get(index);
}
}
// 客户端代码
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState(1);
caretaker.addMemento(originator.saveToMemento());
originator.setState(2);
caretaker.addMemento(originator.saveToMemento());
// 恢复到第一次保存的状态
originator.restoreFromMemento(caretaker.getMemento(0));
// 现在 Originator 的状态变为 1
}
}
代码解释:
Memento
类:这个类的对象用于存储Originator的状态。它有一个方法getState()
以获取存储的状态。Originator
类:这个类的对象是我们希望保存其状态的对象。它包含方法saveToMemento()
来保存当前状态到一个新的Memento对象,并包含一个方法restoreFromMemento()
来从Memento恢复状态。Caretaker
类:它负责存储Memento对象,但是它不能修改Memento对象或直接访问其内部信息。
客户端代码创建了Originator
和Caretaker
对象。然后它改变发起人的状态,并要求发起人保存状态。Caretaker
保存了所有的Memento
。当需要的时候,可以通过Caretaker
来获取某个Memento
对象,并要求发起人从这个Memento
恢复状态。
解释器模式
解释器模式(Interpreter Pattern)是一种行为型设计模式,它给定一个语言,定义它的文法的一种表示,并且定义一个解释器,使用这个解释器来解释语言中的句子。这种模式通常用于语言的编译器和解释器的开发中。
解释器模式的关键角色如下:
- 抽象表达式(Abstract Expression):定义解释器的接口,约定解释器的解释操作。
- 具体表达式(Terminal Expression):实现与文法中的终结符相关联的解释操作。
- 非终结表达式(Nonterminal Expression):为文法中非终结符实现解释操作。非终结表达式通常是一个递归结构,可以包含其他非终结表达式或终结表达式。
- 上下文(Context):包含解释器之外的一些全局信息。
- 客户端(Client): 客户端构建(或被提供)该文法中各种具体表达式的实例,并调用解释操作。
以下是使用Java编写的简单解释器模式示例,包括代码注释:
java
// 抽象表达式(Abstract Expression)
interface Expression {
boolean interpret(String context);
}
// 终结符表达式(Terminal Expression)
class TerminalExpression implements Expression {
private String data;
public TerminalExpression(String data){
this.data = data;
}
@Override
public boolean interpret(String context){
return context.contains(data);
}
}
// 非终结符表达式(Nonterminal Expression)
class OrExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public OrExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
class AndExpression implements Expression {
private Expression expr1 = null;
private Expression expr2 = null;
public AndExpression(Expression expr1, Expression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public boolean interpret(String context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}
// 客户端(Client)
public class InterpreterPatternDemo {
// 规则:Robert 和 John 是男性
public static Expression getMaleExpression(){
Expression robert = new TerminalExpression("Robert");
Expression john = new TerminalExpression("John");
return new OrExpression(robert, john);
}
// 规则:Julie 是一个已婚的女性
public static Expression getMarriedWomanExpression(){
Expression julie = new TerminalExpression("Julie");
Expression married = new TerminalExpression("Married");
return new AndExpression(julie, married);
}
public static void main(String[] args) {
Expression isMale = getMaleExpression();
Expression isMarriedWoman = getMarriedWomanExpression();
System.out.println("John is male? " + isMale.interpret("John"));
System.out.println("Julie is a married woman? " + isMarriedWoman.interpret("Married Julie"));
}
}
在上面的代码中,TerminalExpression
类表示终结符表达式,即不再需要进一步分解的表达式。OrExpression
和 AndExpression
类代表非终结符表达式,它们分别代表逻辑或和逻辑与操作。InterpreterPatternDemo
类中的 main
方法是客户端,它创建了表达式,并使用解释器对特定的字符串进行解释。
这个简单的示例中,我们创建了一些规则以模拟用解释器模式解释句子的过程。当函数 interpret
被调用时,相应的表达式会判断给定的上下文(字符串)是否符合规则。
解释器模式在实际应用中通常用于解析复杂的文法,比如编程语言的解释器或者SQL解析器。不过,由于解释器模式可能会导致复杂的设计和难以维护的代码,如果可用现有的解析工具或者其他设计模式可以更简单地解决问题时,它不是特别推荐的选择。
状态模式
状态模式(State Pattern)是一种行为设计模式,它允许一个对象在其内部状态改变时改变它的行为。这个模式中的关键点是把那些会随着状态改变而改变的行为抽取出来,然后把这些行为封装在不同的状态对象中。
状态模式主要包含以下角色:
-
Context(环境类):它定义了客户感兴趣的接口。它维护一个表示当前状态的实例,并将与状态相关的工作委托给它。
-
State(抽象状态类):这是一个抽象类或接口,它定义了每个状态对象需要实现的方法。
-
ConcreteState(具体状态类):这个类实现了State接口,提供了具体状态相关的行为。
下面是一个简单的状态模式的Java实现样例,模拟一个简单的电灯开关的例子,电灯有开和关两种状态,每次切换时状态变化。
java
// State.java
// 抽象状态类
public interface State {
void handle(Context context);
}
// OnState.java
// 具体状态类,开灯状态
public class OnState implements State {
@Override
public void handle(Context context) {
System.out.println("Light is now on.");
// 切换状态到OffState
context.setState(new OffState());
}
}
// OffState.java
// 具体状态类,关灯状态
public class OffState implements State {
@Override
public void handle(Context context) {
System.out.println("Light is now off.");
// 切换状态到OnState
context.setState(new OnState());
}
}
// Context.java
// 环境类,维护一个State类型的对象实例
public class Context {
private State state;
// 构造器中可以设置初始状态
public Context(State state) {
this.state = state;
}
// 设置新的状态
public void setState(State state) {
this.state = state;
}
// 请求State处理
public void request() {
state.handle(this);
}
}
// Main.java
// 客户端代码
public class Main {
public static void main(String[] args) {
// 初始状态为关灯
Context context = new Context(new OffState());
// 进行开关操作
context.request(); // 应输出 "Light is now off." 并切换到OnState
context.request(); // 应输出 "Light is now on." 并切换到OffState
context.request(); // 再次切换状态
context.request(); // 再次切换状态
}
}
在上述代码中:
State
接口定义了一个handle
方法,该方法接收一个Context
对象作为参数。OnState
和OffState
类实现了State
接口和handle
方法,它们各自代表了电灯的开启状态和关闭状态,并在handle
方法中实现了对状态的切换。Context
类提供了一个设置状态的接口setState
,它还有一个request
方法,用于委托当前状态对象处理请求。- 在客户端
Main
类中,首先创建了一个初始状态为OffState
的Context
对象,然后通过调用request
方法模拟开关电灯的操作,每次调用都会切换电灯的状态。
这样的设计将状态的变更和行为封装在状态对象中,这让状态的变更更加清晰,并且使得Context类的职责更单一,易于维护和扩展。状态模式非常适用于对象的状态多且是相互独立的情况。
策略模式
策略模式(Strategy Pattern)是一种行为设计模式,它定义了算法族,分别封装起来,让它们之间可以互相替换。策略模式让算法的变化独立于使用算法的客户。
策略模式主要涉及三个角色:
- 上下文(Context):维护一个对策略对象的引用。它可以定义一个接口来让策略实现。
- 策略(Strategy):定义所有支持的算法的公共接口。上下文使用这个接口来调用某个策略定义的算法。
- 具体策略(ConcreteStrategy):实现策略接口的具体算法。
接下来,我们使用Java编写一个策略模式的例子。假设我们有一个支付服务,它可以使用不同的支付策略(例如:信用卡支付、PayPal支付等)。
首先,定义策略接口(Strategy):
java
// PaymentStrategy.java
public interface PaymentStrategy {
void pay(int amount); // 支付方法接受一个支付金额
}
然后,实现具体策略(ConcreteStrategy):
java
// CreditCardStrategy.java
public class CreditCardStrategy implements PaymentStrategy {
private String name;
private String cardNumber;
public CreditCardStrategy(String name, String cardNumber) {
this.name = name;
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit/debit card");
}
}
// PaypalStrategy.java
public class PaypalStrategy implements PaymentStrategy {
private String emailId;
private String password;
public PaypalStrategy(String email, String pwd) {
this.emailId = email;
this.password = pwd;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal.");
}
}
最后,创建上下文类(Context):
java
// ShoppingCart.java
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
// 商品列表
List<Item> items;
public ShoppingCart() {
this.items = new ArrayList<>();
}
public void addItem(Item item) {
this.items.add(item);
}
public void removeItem(Item item) {
this.items.remove(item);
}
// 计算总金额
public int calculateTotal() {
int sum = 0;
for (Item item : items) {
sum += item.getPrice();
}
return sum;
}
// 客户端通过该方法选择支付策略
public void pay(PaymentStrategy paymentMethod) {
int amount = calculateTotal();
paymentMethod.pay(amount);
}
}
// Item.java (辅助类,表示购物车中的商品)
public class Item {
private String name;
private int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
}
现在,我们可以在客户端代码中使用这些类来选择具体的支付策略:
java
// Client.java
public class Client {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// 添加商品
cart.addItem(new Item("987654", 100));
cart.addItem(new Item("123456", 200));
// 选择并进行支付,使用信用卡
cart.pay(new CreditCardStrategy("John Doe", "1234567890123456"));
// 选择并进行支付,使用PayPal
cart.pay(new PaypalStrategy("john@example.com", "password"));
}
}
在上述例子中,ShoppingCart
类不必知道客户将使用哪种支付方式,它只关心支付策略接口,这使得我们可以新增或修改支付策略,而不必修改ShoppingCart
类。策略模式提供了一种用于封装算法族的机制,使得它们可以互换,这增加了算法间的可重用性和灵活性。
访问者模式
访问者模式(Visitor Pattern)是一种将算法与对象结构分离的设计模式。这种模式主要用于操作一个由许多不同类型的对象构成的复杂对象结构,并且希望在不修改这些对象的类的前提下,增加新的操作。
访问者模式的核心原理在于在一个对象结构(比如一个对象组合)中添加新的操作而不修改结构。在这个模式中,可以定义一个访问者类,它可以改变对象结构中具体元素的执行算法。
访问者模式涉及以下角色:
-
访问者(Visitor)接口:声明了一组访问方法,用于对每个具体元素(ConcreteElement)进行操作。这些方法有相同的名字但是参数类型分别对应一个具体元素。
-
具体访问者(ConcreteVisitor):实现访问者接口中的操作,定义了对每个元素的具体访问行为。
-
元素(Element)接口:声明了一个接受操作,接受一个访问者对象作为参数。
-
具体元素(ConcreteElement):实现了元素接口,通常会有一个
accept(Visitor visitor)
方法,用于接收访问者,并将自身作为参数传递给访问者的访问方法。 -
对象结构(ObjectStructure):这是一个包含元素对象的组合结构,它能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素。
以下是使用Java实现访问者模式的示例,其中包含注释以说明各个部分:
java
// 访问者接口
interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
// 具体访问者A
class ConcreteVisitorA implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("ConcreteVisitorA visiting " + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("ConcreteVisitorA visiting " + element.operationB());
}
}
// 具体访问者B
class ConcreteVisitorB implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("ConcreteVisitorB visiting " + element.operationA());
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("ConcreteVisitorB visiting " + element.operationB());
}
}
// 元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体元素A
class ConcreteElementA implements Element {
public String operationA() {
return "ConcreteElementA";
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 具体元素B
class ConcreteElementB implements Element {
public String operationB() {
return "ConcreteElementB";
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
// 对象结构
class ObjectStructure {
private List<Element> elements = new ArrayList<>();
public void attach(Element element) {
elements.add(element);
}
public void detach(Element element) {
elements.remove(element);
}
public void accept(Visitor visitor) {
for (Element element : elements) {
element.accept(visitor);
}
}
}
// 示例使用
public class VisitorPatternExample {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new ConcreteElementA());
objectStructure.attach(new ConcreteElementB());
ConcreteVisitorA visitorA = new ConcreteVisitorA();
ConcreteVisitorB visitorB = new ConcreteVisitorB();
objectStructure.accept(visitorA);
objectStructure.accept(visitorB);
}
}
在这个例子中,我们创建了两个具体访问者(ConcreteVisitorA
和 ConcreteVisitorB
),它们知道如何处理两种具体元素(ConcreteElementA
和 ConcreteElementB
)。对象结构(ObjectStructure
)被用来存储元素,它提供了一种让访问者访问其元素的方式。当我们在主函数(main
)中创建这些对象并调用 accept
方法时,访问者通过对象结构中的元素,根据其类型调用合适的访问方法。
使用访问者模式可以使得新增操作变得简单,因为它不需要修改现有的元素类。然而,如果需要在系统中增加新的元素类,则需要更改每个访问者以适应新类,这可能会导致访问者与元素之间的耦合增加。