Java中23种设计模式
提示:文中有理解不到位的地方,欢迎各位大佬指点批评
前言
提示:这里可以添加本文要记录的大概内容:
Java的23种设计模式是用来解决常见编程问题的最佳实践。这些模式可以分为三类:创建型、结构型和行为型。
提示:以下是本篇文章正文内容,下面案例可供参考
创建型模式(关注对象的创建过程):
工厂方法模式(Factory Method)
就像一个工厂生产不同类型的物品,这个模式定义了一个创建对象的接口,但让子类决定实例化哪个类。
工厂方法模式是一种创建型设计模式,它提供了一个接口用于创建对象,但让子类决定实例化哪个类。这种模式将对象的创建过程延迟到子类中进行,使得代码更具灵活性和可扩展性。
以下是一个简单的工厂方法模式的Java代码示例:
首先有一个抽象的产品接口或类:
public interface Product {
void use();
}
然后有实现了这个产品接口的具体产品类:
public class ConcreteProductA implements Product {
@Override
public void use() {
System.out.println("Using product A");
}
}
public class ConcreteProductB implements Product {
@Override
public void use() {
System.out.println("Using product B");
}
}
接下来定义一个抽象工厂类,它声明了一个创建产品的工厂方法:
public abstract class AbstractFactory {
public abstract Product createProduct();
}
现在创建具体的工厂类,它们实现了抽象工厂中的工厂方法,并返回具体的产品:
public class ConcreteFactoryA extends AbstractFactory {
@Override
public Product createProduct() {
return new ConcreteProductA();
}
}
public class ConcreteFactoryB extends AbstractFactory {
@Override
public Product createProduct() {
return new ConcreteProductB();
}
}
最后在客户端代码中使用工厂来创建产品:
public class Client {
public static void main(String[] args) {
AbstractFactory factoryA = new ConcreteFactoryA();
Product productA = factoryA.createProduct();
productA.use(); // 输出 "Using product A"
AbstractFactory factoryB = new ConcreteFactoryB();
Product productB = factoryB.createProduct();
productB.use(); // 输出 "Using product B"
}
}
在这个例子中,当我们需要创建不同类型的产品时,我们不需要修改客户端代码,只需要创建相应的具体工厂即可。
这就是工厂方法模式的主要优点:它将对象的创建过程封装在工厂中,使得代码更易于维护和扩展。
抽象工厂模式(Abstract Factory)
这是一个更高级的工厂,它不仅生产单个产品,而是生产一系列相关的产品。
抽象工厂模式是一种创建型设计模式,它提供了一种方法来创建相关或依赖对象的家族,而无需指定具体的产品类。这种模式将对象的创建过程封装在工厂中,使得代码更易于维护和扩展。
以下是一个简单的抽象工厂模式的Java代码示例:
首先有多个产品接口或抽象类:
public interface ProductA {
void use();
}
public interface ProductB {
void use();
}
然后有实现了这些产品接口的具体产品类:
public class ConcreteProductA1 implements ProductA {
@Override
public void use() {
System.out.println("Using product A1");
}
}
public class ConcreteProductA2 implements ProductA {
@Override
public void use() {
System.out.println("Using product A2");
}
}
public class ConcreteProductB1 implements ProductB {
@Override
public void use() {
System.out.println("Using product B1");
}
}
public class ConcreteProductB2 implements ProductB {
@Override
public void use() {
System.out.println("Using product B2");
}
}
接下来定义一个抽象工厂接口,它声明了创建多个相关产品的工厂方法:
public interface AbstractFactory {
ProductA createProductA();
ProductB createProductB();
}
现在创建具体的工厂类,它们实现了抽象工厂中的工厂方法,并返回具体的产品:
public class ConcreteFactory1 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA1();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB1();
}
}
public class ConcreteFactory2 implements AbstractFactory {
@Override
public ProductA createProductA() {
return new ConcreteProductA2();
}
@Override
public ProductB createProductB() {
return new ConcreteProductB2();
}
}
最后在客户端代码中使用工厂来创建相关的产品:
public class Client {
public static void main(String[] args) {
AbstractFactory factory1 = new ConcreteFactory1();
ProductA productA1 = factory1.createProductA();
productA1.use(); // 输出 "Using product A1"
ProductB productB1 = factory1.createProductB();
productB1.use(); // 输出 "Using product B1"
AbstractFactory factory2 = new ConcreteFactory2();
ProductA productA2 = factory2.createProductA();
productA2.use(); // 输出 "Using product A2"
ProductB productB2 = factory2.createProductB();
productB2.use(); // 输出 "Using product B2"
}
}
在这个例子中,当我们需要创建不同系列的相关产品时,我们不需要修改客户端代码,只需要创建相应的具体工厂即可。
这就是抽象工厂模式的主要优点:它不仅封装了对象的创建过程,还能够处理相关或依赖对象的家族,使得代码更易于管理和扩展。
建造者模式(Builder)
当你需要分步骤构建一个复杂对象,并希望解耦构建过程和表示时,使用这种模式。
建造者模式是一种创建型设计模式,它提供了一种灵活的方式来创建复杂对象。这种模式将一个复杂的构建过程分解为多个简单的步骤,并允许在不同的构建过程中改变各个部分的构造方式,而无需修改客户端代码。
首先有一个表示复杂对象的产品类:
public class Product {
private String partA;
private String partB;
private String partC;
public String getPartA() {
return partA;
}
public void setPartA(String partA) {
this.partA = partA;
}
public String getPartB() {
return partB;
}
public void setPartB(String partB) {
this.partB = partB;
}
public String getPartC() {
return partC;
}
public void setPartC(String partC) {
this.partC = partC;
}
}
然后定义一个抽象的建造者接口,它声明了用于构建产品的各个部分的方法:
public abstract class Builder {
protected Product product;
public Builder() {
this.product = new Product();
}
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
public Product getProduct() {
return product;
}
}
接下来创建具体的建造者类,它们实现了抽象建造者中的方法,并负责构建产品的各个部分:
public class ConcreteBuilder1 extends Builder {
@Override
public void buildPartA() {
product.setPartA("部件A for ConcreteBuilder1");
}
@Override
public void buildPartB() {
product.setPartB("部件B for ConcreteBuilder1");
}
@Override
public void buildPartC() {
product.setPartC("部件C for ConcreteBuilder1");
}
}
public class ConcreteBuilder2 extends Builder {
@Override
public void buildPartA() {
product.setPartA("部件A for ConcreteBuilder2");
}
@Override
public void buildPartB() {
product.setPartB("部件B for ConcreteBuilder2");
}
@Override
public void buildPartC() {
product.setPartC("部件C for ConcreteBuilder2");
}
}
最后定义一个导演类或指挥者类,它使用建造者来构建产品:
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
}
public Product getProduct() {
return builder.getProduct();
}
}
在客户端代码中,我们可以选择不同的建造者来构建产品:
public class Client {
public static void main(String[] args) {
Director director = new Director(new ConcreteBuilder1());
director.construct();
Product product1 = director.getProduct();
System.out.println(product1.getPartA()); // 输出 "部件A for ConcreteBuilder1"
System.out.println(product1.getPartB()); // 输出 "部件B for ConcreteBuilder1"
System.out.println(product1.getPartC()); // 输出 "部件C for ConcreteBuilder1"
director = new Director(new ConcreteBuilder2());
director.construct();
Product product2 = director.getProduct();
System.out.println(product2.getPartA()); // 输出 "部件A for ConcreteBuilder2"
System.out.println(product2.getPartB()); // 输出 "部件B for ConcreteBuilder2"
System.out.println(product2.getPartC()); // 输出 "部件C for ConcreteBuilder2"
}
}
在这个例子中,建造者模式将复杂的构建过程分解为多个简单的步骤,并通过导演类来控制这些步骤的执行顺序。客户端代码可以选择不同的建造者来构建具有不同部分的产品,而无需了解具体的构建过程。
这就是建造者模式的主要优点:它提供了更大的灵活性和可扩展性,同时保持了代码的清晰性和低耦合性。
原型模式(Prototype)
通过复制已有对象来创建新对象,而不是每次都从头创建。
原型模式是一种创建型设计模式,它通过复制已有对象来创建新对象,而不是每次都从头创建。这种模式适用于那些初始化成本高或者需要保持一致性状态的对象。
以下是一个简单的原型模式的Java代码示例:
首先有一个表示原型对象的接口或抽象类:
public interface Prototype {
Prototype clone();
}
然后有实现了这个原型接口的具体产品类:
public class ConcretePrototype implements Prototype {
private String attribute;
public ConcretePrototype(String attribute) {
this.attribute = attribute;
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("Cloning is not supported", e);
}
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
}
在这个例子中,ConcretePrototype
类实现了 Prototype
接口,并重写了 clone()
方法来创建一个新的对象副本。
在客户端代码中,我们可以使用原型对象来创建新对象:
public class Client {
public static void main(String[] args) {
ConcretePrototype original = new ConcretePrototype("Original");
System.out.println("原始对象的属性: " + original.getAttribute());
// 使用原型对象创建新对象
ConcretePrototype clone = original.clone();
clone.setAttribute("Cloned");
System.out.println("原始对象的属性在克隆后: " + original.getAttribute());
System.out.println("克隆对象的属性: " + clone.getAttribute());
}
}
在这个例子中,当我们调用 original.clone()
时,会创建一个新的 ConcretePrototype
对象,其属性与原始对象相同。然后可以修改克隆对象的属性,而不会影响原始对象。
这就是原型模式的主要优点:通过复制已有对象来创建新对象,可以提高对象创建的效率,并且可以保持对象的一致性状态。但是如果对象包含复杂的引用关系或非基本类型的成员变量,可能需要实现深拷贝以避免共享引用导致的问题。
在上述示例中,我们假设 ConcretePrototype
类的所有成员变量都是基本类型或不可变对象,因此可以直接使用 super.clone()
实现浅拷贝。在实际应用中,可能需要根据具体情况进行调整。
单例模式(Singleton)
确保一个类只有一个实例,并提供全局访问点。
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。这种模式通常用于那些需要频繁实例化但又希望控制资源使用的场景,例如数据库连接、线程池或缓存等。
以下是一个简单的单例模式的Java代码示例:
public class Singleton {
// 创建 Singleton 类的一个对象
private static Singleton instance;
// 让构造函数为 private,这样该类就不会被实例化
private Singleton() {}
// 获取唯一可用的对象
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
// 示例方法
public void someMethod() {
System.out.println("单例模式称为");
}
}
在这个例子中,通过以下方式实现单例模式:
- 将构造函数设为
private
,防止其他类直接实例化。 - 定义一个
static
成员变量instance
来存储 Singleton 类的唯一实例。 - 提供一个
public static
方法getInstance()
,用于获取 Singleton 类的唯一实例。
在客户端代码中,我们可以使用 getInstance()
方法来获取并操作 Singleton 对象:
public class Client {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
singleton1.someMethod(); // 输出 "单例模式称为"
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2); // 输出 "true"
}
}
在这个例子中,无论何时调用 getInstance()
方法,返回的都是同一个 Singleton 对象。因此,singleton1
和 singleton2
是同一个对象的引用,比较它们时结果为 true
。
这就是单例模式的主要优点:确保一个类只有一个实例,并提供全局访问点,从而控制资源的使用和管理。在实际应用中,根据具体需求和环境,可以选择不同的单例实现方式,如饿汉式、懒汉式、双重检查锁定等。上述示例采用的是懒汉式加同步锁的实现方式。
结构型模式(关注类和对象的组合):
适配器模式(Adapter)
将一个接口转换成另一个接口,使原本不兼容的类能够一起工作。
适配器模式是一种结构型设计模式,它的主要目的是将一个接口转换为另一个接口,使得原本由于接口不兼容而不能一起工作的类能够协同工作。这种模式通常在以下情况下使用:
- 当你想使用一个现有的类,但是它的接口不符合你的需求时。
- 当你想创建一个可以与多个无关或不直接兼容的类一起工作的类时。
- 当你想使用一些已经存在的类,但是又不想对这些类的源代码进行修改时。
适配器模式有三种常见的实现方式:
- 对象适配器:通过创建一个新的适配器类,该类包含一个被适配对象的实例,并提供目标接口的方法。适配器类在调用目标方法时,会将请求转发给被适配对象。
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adapteeSpecificRequest();
}
private void adapteeSpecificRequest() {
adaptee.specificRequest();
}
}
- 类适配器:通过继承被适配类并实现目标接口来创建适配器。这种方式在Java中可能受到单继承限制的影响。
public class ClassAdapter extends Adaptee implements Target {
@Override
public void request() {
super.specificRequest();
}
}
- 接口适配器(也称为“哑巴代理”):为一个接口创建一个默认实现,这样子类只需要重写他们需要的方法。这并不改变接口,而是提供了默认的行为。
public interface Source {
void method1();
void method2();
}
public abstract class Adapter implements Source {
@Override
public void method1() {
// 默认实现或者空实现
}
@Override
public void method2() {
// 默认实现或者空实现
}
}
在适配器模式中,客户端通常与目标接口交互,而不直接与适配器或被适配者交互。这样适配器就可以透明地将一个接口转换为另一个接口,使得原本不兼容的组件能够协同工作。
桥接模式(Bridge)
将抽象部分和实现部分分离,使它们可以独立变化。
桥接模式是一种结构型设计模式,它的主要目的是将抽象部分与实现部分分离,使得它们可以独立变化。这种模式通过引入一个抽象层,使得高级模块和低级模块都可以独立地进行修改和扩展,而不会相互影响。
在桥接模式中,通常包含以下角色:
-
Abstraction(抽象类):定义抽象接口,维护一个对实现类对象的引用,用来调用实现类中的方法。
-
Refined Abstraction(具体抽象类):是抽象类的子类,扩展了抽象类的部分功能,并通过构造函数指定了具体的实现类。
-
Implementor(实现类接口):定义实现类需要实现的接口,它与抽象类的接口不同,通常更底层、更具体。
-
Concrete Implementor(具体实现类):实现了实现类接口,提供了具体的实现。
以下是一个简单的Java代码示例来说明桥接模式:
// 实现类接口
public interface DrawingAPI {
void drawCircle(double x, double y, double radius);
}
// 具体实现类1
public class DrawingAPI1 implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("使用绘图技术1绘制圆形");
}
}
// 具体实现类2
public class DrawingAPI2 implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.println("使用绘图技术2绘制圆形");
}
}
// 抽象类
public abstract class Shape {
protected DrawingAPI drawingAPI;
public Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
public abstract void draw();
}
// 具体抽象类1
public class CircleShape extends Shape {
private double x, y, radius;
public CircleShape(DrawingAPI drawingAPI, double x, double y, double radius) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
public void draw() {
drawingAPI.drawCircle(x, y, radius);
}
}
// 客户端代码
public class BridgePatternDemo {
public static void main(String[] args) {
Shape circle1 = new CircleShape(new DrawingAPI1(), 10, 20, 5);
Shape circle2 = new CircleShape(new DrawingAPI2(), 10, 20, 5);
circle1.draw(); // 输出 "使用绘图技术1绘制圆形"
circle2.draw(); // 输出 "使用绘图技术2绘制圆形"
}
}
在这个例子中,Shape
是抽象类,CircleShape
是具体抽象类,DrawingAPI
是实现类接口,DrawingAPI1
和 DrawingAPI2
是具体实现类。客户端代码可以根据需要选择不同的实现类,而不需要修改抽象类或具体抽象类的代码,这就是桥接模式的主要优点。
装饰器模式(Decorator)
在不改变对象本身的情况下,动态地给对象添加新的职责或功能。
装饰器模式是一种结构型设计模式,它的主要目的是在不修改原有对象的基础上,动态地给对象添加新的职责或功能。这种模式通过创建一个装饰类,该装饰类包含了原始对象的引用,并提供了与原始对象相同的接口。当客户端调用装饰器的方法时,装饰器可以在执行实际操作之前或之后添加额外的行为。
以下是一个简单的装饰器模式的Java代码示例:
首先有一个基础的饮料接口和实现:
public interface Beverage {
String getDescription();
double cost();
}
public class Coffee implements Beverage {
private String description = "Coffee";
@Override
public String getDescription() {
return description;
}
@Override
public double cost() {
return 1.0;
}
}
然后定义一个装饰器接口,它继承自饮料接口:
public abstract class CondimentDecorator implements Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription();
}
}
接下来创建具体的装饰器类,如加糖装饰器:
public class SugarDecorator extends CondimentDecorator {
public SugarDecorator(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + ", Sugar";
}
@Override
public double cost() {
return super.cost() + 0.25;
}
}
同样可以创建其他的装饰器,如加奶装饰器:
public class MilkDecorator extends CondimentDecorator {
public MilkDecorator(Beverage beverage) {
super(beverage);
}
@Override
public String getDescription() {
return super.getDescription() + ", Milk";
}
@Override
public double cost() {
return super.cost() + 0.5;
}
}
在客户端代码中,我们可以根据需要组合不同的装饰器来创建具有不同特性的饮料:
public class Client {
public static void main(String[] args) {
Beverage coffee = new Coffee();
System.out.println(coffee.getDescription()); // 输出 "Coffee"
System.out.println(coffee.cost()); // 输出 1.0
Beverage coffeeWithSugar = new SugarDecorator(coffee);
System.out.println(coffeeWithSugar.getDescription()); // 输出 "Coffee, Sugar"
System.out.println(coffeeWithSugar.cost()); // 输出 1.25
Beverage coffeeWithMilkAndSugar = new MilkDecorator(coffeeWithSugar);
System.out.println(coffeeWithMilkAndSugar.getDescription()); // 输出 "Coffee, Sugar, Milk"
System.out.println(coffeeWithMilkAndSugar.cost()); // 输出 1.75
}
}
在这个例子中,Beverage
是饮料接口,Coffee
是基础饮料类,CondimentDecorator
是装饰器接口,SugarDecorator
和 MilkDecorator
是具体的装饰器类。客户端代码可以根据需要选择不同的装饰器进行组合,以动态地添加新的职责或功能到饮料对象上,这就是装饰器模式的主要优点。
组合模式(Composite)
将对象组织成树形结构,使得单个对象和组合对象都能以相同的方式处理。
组合模式是一种结构型设计模式,它允许你将对象组合成树形结构,并以统一的方式处理单个对象和对象组合。这种模式使得客户端代码可以一致地处理单个对象和对象集合,无需关心它们的具体类型。
在组合模式中,通常包含以下角色:
-
Component(组件接口):定义了所有元素(包括叶子节点和容器节点)的共有接口,声明了访问和管理子元素的方法。
-
Leaf(叶子节点):实现了 Component 接口,表示树结构中的叶子节点。叶子节点没有子元素。
-
Composite(容器节点):也实现了 Component 接口,表示树结构中的容器节点。容器节点包含一个或多个子元素,这些子元素也是 Component 类型的对象。
以下是一个简单的Java代码示例来说明组合模式:
// Component 接口
public interface Graphic {
void draw();
void add(Graphic graphic);
void remove(Graphic graphic);
Graphic getGraphic(int index);
}
// Leaf 类
public class Circle implements Graphic {
private String name;
public Circle(String name) {
this.name = name;
}
@Override
public void draw() {
System.out.println("Drawing " + name);
}
// 叶子节点没有子元素,以下方法可为空实现或者抛出异常
@Override
public void add(Graphic graphic) {
throw new UnsupportedOperationException("无法添加到叶节点");
}
@Override
public void remove(Graphic graphic) {
throw new UnsupportedOperationException("无法从叶节点中删除");
}
@Override
public Graphic getGraphic(int index) {
throw new UnsupportedOperationException("无法从叶节点获取子节点");
}
}
// Composite 类
public class GraphicsComposite implements Graphic {
private List<Graphic> graphics;
private String name;
public GraphicsComposite(String name) {
this.graphics = new ArrayList<>();
this.name = name;
}
@Override
public void draw() {
for (Graphic graphic : graphics) {
graphic.draw();
}
System.out.println("Drawing " + name);
}
@Override
public void add(Graphic graphic) {
graphics.add(graphic);
}
@Override
public void remove(Graphic graphic) {
graphics.remove(graphic);
}
@Override
public Graphic getGraphic(int index) {
return graphics.get(index);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Graphic circle1 = new Circle("Circle 1");
Graphic circle2 = new Circle("Circle 2");
Graphic composite = new GraphicsComposite("组合模式");
composite.add(circle1);
composite.add(circle2);
composite.draw(); // 输出 "Drawing Circle 1", "Drawing Circle 2", "Drawing Composite"
}
}
在这个例子中,Graphic
是组件接口,Circle
是叶子节点类,GraphicsComposite
是容器节点类。客户端代码可以一致地处理单个对象(如 circle1
和 circle2
)和对象组合(如 composite
),这就是组合模式的主要优点。
外观模式(Facade)
为复杂的子系统提供一个简单的接口。
外观模式是一种结构型设计模式,它的主要目的是为复杂的子系统提供一个统一的、更简单的接口,使得客户端与子系统的交互变得更加简单和容易。这种模式通过创建一个外观类,该类包含了对子系统中各个模块的引用,并提供了与子系统交互所需的必要方法。
以下是一个简单的外观模式的Java代码示例:
假设我们有一个音乐播放系统的子系统,包含多个模块,如CDPlayer、Radio 和 MP3Player等:
public class CDPlayer {
public void playCD() {
System.out.println("播放 CD...");
}
public void stopCD() {
System.out.println("暂停 CD...");
}
}
public class Radio {
public void turnOnRadio() {
System.out.println("打开 radio...");
}
public void turnOffRadio() {
System.out.println("关掉 radio...");
}
}
public class MP3Player {
public void playMP3() {
System.out.println("播放 MP3...");
}
public void stopMP3() {
System.out.println("暂停 MP3...");
}
}
为了简化客户端与这些模块的交互,我们可以创建一个外观类(MusicPlayerFacade):
public class MusicPlayerFacade {
private CDPlayer cdPlayer;
private Radio radio;
private MP3Player mp3Player;
public MusicPlayerFacade() {
this.cdPlayer = new CDPlayer();
this.radio = new Radio();
this.mp3Player = new MP3Player();
}
public void playMusic(String type) {
if (type.equals("cd")) {
cdPlayer.playCD();
} else if (type.equals("radio")) {
radio.turnOnRadio();
} else if (type.equals("mp3")) {
mp3Player.playMP3();
}
}
public void stopMusic(String type) {
if (type.equals("cd")) {
cdPlayer.stopCD();
} else if (type.equals("radio")) {
radio.turnOffRadio();
} else if (type.equals("mp3")) {
mp3Player.stopMP3();
}
}
}
在客户端代码中,我们可以使用外观类来与音乐播放系统的子系统进行交互:
public class Client {
public static void main(String[] args) {
MusicPlayerFacade musicPlayer = new MusicPlayerFacade();
musicPlayer.playMusic("cd"); // 输出 "播放 CD..."
musicPlayer.stopMusic("cd"); // 输出 "暂停 CD..."
musicPlayer.playMusic("radio"); // 输出 "打开 radio..."
musicPlayer.stopMusic("radio"); // 输出 "关掉 radio..."
musicPlayer.playMusic("mp3"); // 输出 "播放 MP3..."
musicPlayer.stopMusic("mp3"); // 输出 "暂停 MP3..."
}
}
在这个例子中,CDPlayer
、Radio
和 MP3Player
是子系统的模块,MusicPlayerFacade
是外观类。客户端代码通过外观类与子系统交互,无需直接调用各个模块的方法,这就是外观模式的主要优点。
外观模式可以简化复杂系统的使用,提高代码的可读性和可维护性。
享元模式(Flyweight)
通过共享技术有效支持大量细粒度的对象。
享元模式是一种结构型设计模式,它的主要目的是通过共享对象来有效地支持大量细粒度的对象,从而减少系统中对象的数量,提高系统的性能和内存利用率。
在享元模式中,通常包含以下角色:
-
Flyweight(享元接口):定义了所有具体享元类的公共接口,这些接口通常包括了内部状态(不可变部分)的访问方法。
-
Concrete Flyweight(具体享元类):实现了 Flyweight 接口,存储并提供了内部状态的实现。具体享元类是可共享的。
-
Unshared Concrete Flyweight(非共享具体享元类):对于不能被共享的状态,可以创建非共享的具体享元类来处理。
-
Flyweight Factory(享元工厂):负责创建和管理享元对象。它根据需要提供现有的享元对象或者创建新的享元对象。
以下是一个简单的Java代码示例来说明享元模式:
// Flyweight 接口
public interface Shape {
void draw(int x, int y);
}
// 具体享元类 - Circle
public class Circle implements Shape {
private Color color;
public Circle(Color color) {
this.color = color;
}
@Override
public void draw(int x, int y) {
System.out.println("Drawing a circle at (" + x + ", " + y + ") with color " + color);
}
}
// 享元工厂
public class ShapeFactory {
private static Map<Color, Shape> circleMap = new HashMap<>();
public static Shape getCircle(Color color) {
if (!circleMap.containsKey(color)) {
circleMap.put(color, new Circle(color));
}
return circleMap.get(color);
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Color red = new Color(255, 0, 0);
Color green = new Color(0, 255, 0);
Color blue = new Color(0, 0, 255);
Shape redCircle1 = ShapeFactory.getCircle(red);
Shape redCircle2 = ShapeFactory.getCircle(red);
Shape greenCircle = ShapeFactory.getCircle(green);
Shape blueCircle = ShapeFactory.getCircle(blue);
redCircle1.draw(10, 10);
redCircle2.draw(20, 20);
greenCircle.draw(30, 30);
blueCircle.draw(40, 40);
}
}
在这个例子中,Shape
是享元接口,Circle
是具体享元类,ShapeFactory
是享元工厂。客户端代码通过享元工厂获取享元对象,并使用它们进行绘制操作。
由于颜色相同的圆共享同一个对象实例,因此即使绘制多个相同颜色的圆,也不会创建多个对象,这就是享元模式的主要优点。
享元模式适用于那些对象中有大量重复并且可以共享的数据的情况,通过共享这些数据,可以降低系统中的对象数量,提高系统的性能和内存利用率。
代理模式(Proxy)
为其他对象提供一种代理以控制对这个对象的访问。
代理模式是一种结构型设计模式,它的主要目的是为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在执行实际对象的操作之前或之后添加额外的功能,如权限检查、日志记录、缓存、延迟加载等。
在代理模式中,通常包含以下角色:
-
Subject(主题接口):定义了代理和真实主题的公共接口,这样客户端就可以通过同一接口与真实主题或代理进行交互。
-
Real Subject(真实主题类):实现了主题接口,包含了实际的业务逻辑。
-
Proxy(代理类):也实现了主题接口,并持有真实主题的引用。代理类在接收到客户端请求时,可以决定是否直接将请求转发给真实主题,或者在转发请求之前/之后执行一些额外的操作。
以下是一个简单的Java代码示例来说明代理模式:
// Subject 接口
public interface Image {
void display();
}
// Real Subject 类
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("正在从磁盘加载图像: " + filename);
}
@Override
public void display() {
System.out.println("显示 image: " + filename);
}
}
// Proxy 类
public class ProxyImage implements Image {
private String filename;
private RealImage realImage;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename);
}
realImage.display();
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Image proxyImage = new ProxyImage("image.jpg");
proxyImage.display(); // 输出 "显示 image: image.jpg"
// 第二次调用时,由于图片已经加载,所以不会再次从磁盘加载
proxyImage.display(); // 输出 "显示 image: image.jpg"
}
}
在这个例子中,Image
是主题接口,RealImage
是真实主题类,它包含了从磁盘加载和显示图片的实际逻辑。ProxyImage
是代理类,它在接收到 display()
请求时,会先检查图片是否已经加载,如果没有加载,则先加载图片,然后转发请求给真实主题。
这就是代理模式的主要优点,它能够在不修改原有代码的基础上,为对象的操作添加额外的控制和功能。
代理模式适用于需要控制对某个对象的访问、增强对象功能或者在不同情况下使用不同实现的情况下。
行为型模式(关注对象之间的通信和职责分配):
责任链模式(Chain of Responsibility)
多个对象处理请求,每个对象都可以处理或传递请求给下一个对象。
责任链模式是一种行为设计模式,它允许你将一系列处理请求的处理器(或对象)链接在一起形成一个链。当一个请求沿着链传递时,每个处理器都有机会处理该请求。如果一个处理器能够处理请求,它就会执行相应的操作,并且通常会停止请求的进一步传递;如果当前处理器不能处理请求,则会将请求传递给链中的下一个处理器。
在责任链模式中,通常包含以下角色:
Handler(处理器接口或抽象类):定义了处理请求的接口,并提供了指向下一个处理器的引用。
Concrete Handler(具体处理器类):实现了处理器接口,包含了处理请求的实际逻辑。每个具体处理器都可以决定是否处理请求,或者将请求传递给链中的下一个处理器。
以下是一个简单的Java代码示例来说明责任链模式:
// Handler 接口
public interface Handler {
void setNext(Handler handler); // 设置下一个处理器
void handleRequest(int request); // 处理请求
}
// Concrete Handler 1
public class ConcreteHandler1 implements Handler {
private Handler next; // 下一个处理器引用
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handleRequest(int request) {
if (request >= 0 && request < 10) {
System.out.println("ConcreteHandler1 处理了请求: " + request);
} else if (next != null) {
next.handleRequest(request); // 将请求传递给下一个处理器
}
}
}
// Concrete Handler 2
public class ConcreteHandler2 implements Handler {
private Handler next; // 下一个处理器引用
@Override
public void setNext(Handler handler) {
this.next = handler;
}
@Override
public void handleRequest(int request) {
if (request >= 10 && request < 20) {
System.out.println("ConcreteHandler2 处理了请求: " + request);
} else if (next != null) {
next.handleRequest(request); // 将请求传递给下一个处理器
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1(); // 创建处理器1
Handler handler2 = new ConcreteHandler2(); // 创建处理器2
// 链接处理器
handler1.setNext(handler2);
// 向链中的第一个处理器发送请求
handler1.handleRequest(5); // 输出: "ConcreteHandler1 处理了请求: 5"
handler1.handleRequest(15); // 输出: "ConcreteHandler2 处理了请求: 15"
handler1.handleRequest(25); // 没有输出,因为没有处理器能处理这个请求
}
}
在这个例子中,Handler
是处理器接口,ConcreteHandler1
和 ConcreteHandler2
是具体处理器类,它们分别负责处理不同范围的请求。客户端代码创建了两个处理器对象并将其链接在一起,然后向链中的第一个处理器发送请求。这就是责任链模式的主要优点,它使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。同时,这种模式也提高了系统的灵活性和可扩展性。
命令模式(Command)
将请求封装为一个对象,使得可以用不同的请求、队列请求或者日志请求来参数化其他对象。
命令模式是一种行为设计模式,它的主要目的是将请求的发送者和接收者解耦。这种模式通过将操作封装在独立的对象(命令)中,使得请求可以被传递、存储、队列化、撤销或重试。
以下是一个简单的Java命令模式的组成部分和工作原理:
- Command接口(命令):
定义了一个执行操作的接口,通常包含一个execute()
方法。
public interface Command {
void execute();
}
- ConcreteCommand(具体命令):
实现了Command接口,这些类包含了接收者对象并知道如何调用其方法来执行特定的操作。
public class LightOnCommand implements Command {
private final Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
- Receiver(接收者):
知道如何实施和执行与一个请求相关的操作。在上面的例子中,Light类是接收者。
public class Light {
public void turnOn() {
System.out.println("The light is on.");
}
public void turnOff() {
System.out.println("The light is off.");
}
}
- Invoker(调用者):
负责调用命令对象的execute()方法。调用者通常不直接与接收者交互,而是通过命令对象进行。
public class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
在这个例子中,遥控器(RemoteControl)是调用者,它持有一个命令对象,并在其pressButton()
方法中调用命令的execute()
方法。当创建一个具体的命令(如LightOnCommand)并将它设置到遥控器中时,按下按钮就会执行相应的操作(打开灯)。
命令模式的优点包括:
- 解耦:发送者和接收者之间没有直接依赖关系。
- 可扩展性:添加新的命令很容易,只需要实现一个新的Command子类。
- 支持命令队列和宏命令:可以将多个命令组合在一起,或者将命令放入队列中按顺序执行。
- 支持撤销/重做操作:可以通过保存历史命令并在需要时重新执行它们来实现撤销和重做功能。
解释器模式(Interpreter)
定义语言的语法并解释该语言的句子。
以下是一个实现简单的算术表达式解析器:
首先定义一个抽象表达式(AbstractExpression)接口:
public interface Expression {
int interpret(Context context);
}
这个接口定义了一个interpret()
方法,所有具体的表达式类都需要实现这个方法。
接下来定义两个终结符表达式(TerminalExpression)类,分别对应数字和加法操作:
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret(Context context) {
return number;
}
}
public class AddExpression implements Expression {
private Expression left, right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
在上面的代码中,NumberExpression
类简单地返回其构造时传入的数字。而AddExpression
类包含两个子表达式,并在其interpret()
方法中调用这两个子表达式的interpret()
方法,然后将结果相加。
接下来定义一个上下文(Context)类,用于存储全局信息或环境状态。在这个简单的例子中,不需要上下文,但为了完整展示解释器模式,仍创建一个空的上下文类:
public class Context {
// 这个例子中不需要上下文,可以添加全局变量或环境状态
}
最后使用这些类来解析和执行一个简单的算术表达式:
public class Main {
public static void main(String[] args) {
Expression expression = new AddExpression(
new NumberExpression(5),
new NumberExpression(3)
);
System.out.println(expression.interpret(new Context())); // 输出:8
}
}
在这个例子中创建了一个加法表达式,它包含两个数字表达式(5和3)。然后,我们通过调用interpret()
方法来执行这个表达式,并打印出结果(8)。
解释器模式的优点包括:
- 它使得新的文法规则易于添加和修改。
- 它使得复杂的语句结构可以被清晰地表示和处理。
然而解释器模式也有一些潜在的缺点:
- 当语言复杂时,可能会导致大量的具体表达式类,增加系统的复杂性。
- 对于复杂的文法,解释器模式可能不如传统的编译器技术或者现成的解析库高效。
- 如果语言的变化频繁,那么需要不断修改解释器,这可能会增加维护难度。
迭代器模式(Iterator)
提供一种方法顺序访问聚合对象的元素,而无需暴露其内部表示。
迭代器模式是一种行为设计模式,它提供了一种方法来顺序访问聚合对象(如集合、列表)的元素,而无需暴露其底层表示。这种模式使得我们可以在不知道具体实现细节的情况下遍历不同的数据结构。
以下是一个简单的迭代器模式的Java代码示例:
首先定义一个迭代器接口(Iterator):
public interface Iterator<T> {
boolean hasNext();
T next();
}
这个接口定义了两个方法:hasNext()
用于检查是否还有更多的元素可以迭代,next()
用于返回下一个元素。
接下来定义一个聚合接口(Aggregate),它包含一个获取迭代器的方法:
public interface Aggregate<T> {
Iterator<T> createIterator();
}
然后创建一个具体的聚合类(ConcreteAggregate),并实现该接口:
import java.util.ArrayList;
import java.util.List;
public class ConcreteAggregate implements Aggregate<Integer> {
private List<Integer> items = new ArrayList<>();
public void add(Integer item) {
items.add(item);
}
@Override
public Iterator<Integer> createIterator() {
return new ConcreteIterator<>(items);
}
}
在上面的代码中,ConcreteAggregate
类使用一个ArrayList来存储元素,并提供了一个添加元素的方法。同时实现了createIterator()
方法,返回一个新的ConcreteIterator
实例。
接下来创建一个具体的迭代器类(ConcreteIterator),并实现迭代器接口:
import java.util.Iterator;
public class ConcreteIterator<T> implements Iterator<T> {
private final List<T> items;
private int position = 0;
public ConcreteIterator(List<T> items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.size();
}
@Override
public T next() {
if (!hasNext()) {
throw new IllegalStateException("No more elements");
}
return items.get(position++);
}
}
在这个例子中,ConcreteIterator
类包含了对聚合对象的引用,并通过一个位置变量来跟踪当前迭代的位置。hasNext()
方法检查是否有更多的元素可以迭代,next()
方法返回当前位置的元素并移动到下一个位置。
最后使用这些类来遍历和打印一个聚合对象中的所有元素:
public class Main {
public static void main(String[] args) {
Aggregate<Integer> aggregate = new ConcreteAggregate<>();
aggregate.add(1);
aggregate.add(2);
aggregate.add(3);
Iterator<Integer> iterator = aggregate.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
在这个例子中创建了一个ConcreteAggregate
实例,并添加了一些整数。然后通过调用createIterator()
方法获取一个迭代器,并使用while循环遍历所有的元素并打印它们。
迭代器模式的优点包括:
- 它支持以统一的方式遍历不同的数据结构。
- 它将遍历算法与数据结构分离,提高了代码的复用性和灵活性。
然而,迭代器模式也有一些潜在的缺点:
- 对于某些数据结构,可能需要实现多个迭代器版本(如正向迭代、反向迭代等)。
- 如果数据结构在迭代过程中发生变化(如添加或删除元素),可能会导致不确定的行为。
中介者模式(Mediator)
定义一个中介对象来封装一系列对象之间的交互。
中介者模式是一种行为设计模式,它定义了一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而降低对象之间的耦合度,使得系统更易于维护和扩展。
以下是一个简单的中介者模式的Java代码示例:
首先定义一个抽象同事类(Colleague),表示参与交互的对象:
public abstract class Colleague {
protected Mediator mediator;
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
}
在上面的代码中,Colleague
类包含了一个对中介者的引用,并提供了一个设置中介者的方法。
接下来创建两个具体的同事类(ConcreteColleagueA和ConcreteColleagueB):
public class ConcreteColleagueA extends Colleague {
public void send(String message) {
System.out.println("ConcreteColleagueA sends: " + message);
mediator.distribute(message, this);
}
public void receive(String message) {
System.out.println("ConcreteColleagueA receives: " + message);
}
}
public class ConcreteColleagueB extends Colleague {
public void send(String message) {
System.out.println("ConcreteColleagueB sends: " + message);
mediator.distribute(message, this);
}
public void receive(String message) {
System.out.println("ConcreteColleagueB receives: " + message);
}
}
在上面的代码中,ConcreteColleagueA
和ConcreteColleagueB
类分别实现了发送和接收消息的方法。当它们需要与其他同事通信时,它们会调用中介者的distribute()
方法。
然后定义一个中介者接口(Mediator):
public interface Mediator {
void register(Colleague colleague);
void distribute(String message, Colleague sender);
}
这个接口定义了两个方法:register()
用于注册同事对象,distribute()
用于将消息从发送者转发给其他同事。
接下来创建一个具体的中介者类(ConcreteMediator):
import java.util.ArrayList;
import java.util.List;
public class ConcreteMediator implements Mediator {
private List<Colleague> colleagues = new ArrayList<>();
@Override
public void register(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void distribute(String message, Colleague sender) {
for (Colleague colleague : colleagues) {
if (colleague != sender) {
colleague.receive(message);
}
}
}
}
在上面的代码中,ConcreteMediator
类使用一个列表来存储所有已注册的同事对象。当它接收到一个消息时,它会遍历这个列表并将消息转发给除了发送者之外的所有同事。
最后使用这些类来演示中介者模式的工作原理:
public class Main {
public static void main(String[] args) {
Mediator mediator = new ConcreteMediator();
Colleague colleagueA = new ConcreteColleagueA();
Colleague colleagueB = new ConcreteColleagueB();
colleagueA.setMediator(mediator);
colleagueB.setMediator(mediator);
mediator.register(colleagueA);
mediator.register(colleagueB);
colleagueA.send("Hello");
colleagueB.send("World");
}
}
在这个例子中,我们创建了一个ConcreteMediator
实例和两个ConcreteColleague
实例。然后,我们将这两个同事对象注册到中介者,并设置其中介者引用。最后,我们让每个同事对象发送一条消息,中介者会将这些消息转发给其他同事。
中介者模式的优点包括:
- 它降低了对象之间的耦合度,使得系统更易于维护和扩展。
- 它将复杂的对象交互逻辑集中在一个地方,提高了代码的可读性和可维护性。
然而中介者模式也有一些潜在的缺点:
- 中介者类可能会变得非常复杂,因为它需要处理所有的对象交互。
- 如果系统中的同事对象数量非常多,那么中介者可能会成为一个性能瓶颈。
备忘录模式(Memento)
在不破坏封装性的前提下,捕获和外部化对象的内部状态以便以后恢复。
备忘录模式是一种行为设计模式,它主要用于在不破坏对象封装性的情况下,捕获和存储对象的内部状态,以便在需要时能够恢复到先前的状态。这种模式常用于需要撤销操作或者保存对象状态以便后续恢复的场景。
以下是一个简单的备忘录模式的Java代码示例:
首先定义一个备忘录接口(Memento),它包含了一些方法来获取和设置内部状态:
public interface Memento {
String getState();
}
在这个例子中,Memento
接口只包含了一个getState()
方法,用于获取保存的对象状态。
接下来创建一个具体的备忘录类(ConcreteMemento):
public class ConcreteMemento implements Memento {
private String state;
public ConcreteMemento(String state) {
this.state = state;
}
@Override
public String getState() {
return state;
}
}
在上面的代码中,ConcreteMemento
类实现了Memento
接口,并包含了对象的内部状态。
然后定义一个原始类(Originator),它负责创建备忘录并存储其内部状态:
public class Originator {
private String state;
public void setState(String state) {
System.out.println("发起人:将状态设置为" + state);
this.state = state;
}
public String getState() {
return state;
}
public Memento createMemento() {
return new ConcreteMemento(state);
}
public void restoreFromMemento(Memento memento) {
setState(memento.getState());
System.out.println("发起人:状态恢复到" + getState());
}
}
在上面的代码中,Originator
类包含了对象的内部状态,并提供了设置和获取状态的方法。此外,它还包含了创建备忘录的createMemento()
方法和从备忘录恢复状态的restoreFromMemento()
方法。
最后使用这些类来演示备忘录模式的工作原理:
public class Main {
public static void main(String[] args) {
Originator originator = new Originator();
originator.setState("State 1");
System.out.println("当前状态: " + originator.getState());
Memento memento = originator.createMemento();
originator.setState("State 2");
System.out.println("当前状态: " + originator.getState());
originator.restoreFromMemento(memento);
System.out.println("已恢复状态: " + originator.getState());
}
}
在这个例子中,我们创建了一个Originator
实例,并设置了它的状态为"State 1"。然后,我们创建了一个备忘录并保存了当前状态。接着,我们将对象的状态更改为"State 2"。最后,我们从备忘录恢复先前的状态,并打印出结果。
备忘录模式的优点包括:
- 它允许在不影响对象封装性的情况下保存和恢复对象的状态。
- 它支持撤销操作和其他需要回滚状态的复杂功能。
然而备忘录模式也有一些潜在的缺点:
- 如果对象的内部状态非常复杂,那么存储和恢复状态可能会变得困难和耗时。
- 备忘录可能占用大量的内存资源,特别是当需要保存大量状态或频繁进行状态恢复时。
观察者模式(Observer)
当对象状态改变时,自动通知依赖它的对象。
观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,当一个对象(称为主题或被观察者)的状态发生改变时,所有依赖于它的对象(称为观察者)都会得到通知并自动更新。
以下是一个简单的观察者模式的Java代码示例:
首先定义一个观察者接口(Observer):
import java.util.Observable;
public interface Observer {
void update(Observable observable, Object arg);
}
在这个例子中,Observer
接口定义了一个update()
方法,当被观察者对象的状态发生改变时,这个方法会被调用。
接下来创建一个具体的观察者类(ConcreteObserver):
public class ConcreteObserver implements Observer {
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(Observable observable, Object arg) {
System.out.println(name + ": Received update from " + observable + ", new state: " + arg);
}
}
在上面的代码中,ConcreteObserver
类实现了Observer
接口,并在其update()
方法中打印出接收到的更新信息。
然后定义一个被观察者接口(Observable)或者使用Java内置的java.util.Observable
类:
import java.util.Observable;
import java.util.Observer;
public interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Object arg);
}
请注意:在实际的Java实现中,你不需要自己定义Observable
接口,因为Java已经提供了内置的java.util.Observable
类,你可以直接使用它。
接下来创建一个具体的被观察者类(ConcreteObservable):
import java.util.ArrayList;
import java.util.List;
public class ConcreteObservable implements Observable {
private List<Observer> observers = new ArrayList<>();
private String state;
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Object arg) {
for (Observer observer : observers) {
observer.update(this, arg);
}
}
public void setState(String state) {
this.state = state;
notifyObservers(state);
}
public String getState() {
return state;
}
}
在上面的代码中,ConcreteObservable
类实现了Observable
接口,并包含了对象的内部状态。当其状态发生改变时,它会调用notifyObservers()
方法来通知所有的观察者。
最后,我们使用这些类来演示观察者模式的工作原理:
public class Main {
public static void main(String[] args) {
ConcreteObservable observable = new ConcreteObservable();
Observer observer1 = new ConcreteObserver("Observer 1");
Observer observer2 = new ConcreteObserver("Observer 2");
observable.addObserver(observer1);
observable.addObserver(observer2);
observable.setState("State 1");
observable.setState("State 2");
}
}
在这个例子中创建了一个ConcreteObservable
实例和两个ConcreteObserver
实例。然后将这两个观察者添加到被观察者中。最后改变了被观察者的状态两次,每次改变都会触发观察者的update()
方法。
观察者模式的优点包括:
- 它简化了对象之间的通信,使得对象之间松散耦合。
- 它支持广播式的事件通知,一个被观察者的状态改变可以通知多个观察者。
然而观察者模式也有一些潜在的缺点:
- 如果有大量的观察者和频繁的状态变化,可能会导致性能问题。
- 如果观察者和被观察者之间的关系过于复杂,可能会导致代码难以理解和维护。
状态模式(State)
允许对象在内部状态改变时改变其行为。
状态模式是一种行为设计模式,它允许一个对象在其内部状态改变时改变其行为。这种模式通过将对象的状态和行为分离,使得对象在不同的状态下表现出不同的行为。
以下是一个简单的状态模式的Java代码示例:
首先定义一个状态接口(State):
public interface State {
void handle(Context context);
}
在这个例子中,State
接口定义了一个handle()
方法,当对象的状态发生改变时,这个方法会被调用。
接下来创建两个具体的状体类(ConcreteStateA和ConcreteStateB):
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("Handling in ConcreteStateA");
context.setState(new ConcreteStateB());
}
}
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("Handling in ConcreteStateB");
context.setState(new ConcreteStateA());
}
}
在上面的代码中,ConcreteStateA
和ConcreteStateB
类分别实现了State
接口,并在其handle()
方法中改变了对象的状态。
然后定义一个上下文类(Context),它包含了对当前状态的引用和切换状态的方法:
public class Context {
private State state;
public Context(State initialState) {
this.state = initialState;
}
public void setState(State newState) {
this.state = newState;
}
public void request() {
state.handle(this);
}
}
在上面的代码中,Context
类包含了对当前状态的引用,并提供了设置和获取状态的方法。此外,它还包含了一个request()
方法,当这个方法被调用时,它会调用当前状态的handle()
方法。
最后使用这些类来演示状态模式的工作原理:
public class Main {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request(); // 输出:Handling in ConcreteStateA
context.request(); // 输出:Handling in ConcreteStateB
context.request(); // 输出:Handling in ConcreteStateA
}
}
在这个例子中创建了一个Context
实例,并设置了初始状态为ConcreteStateA
。然后三次调用request()
方法,每次调用都会触发状态的切换并打印出相应的处理信息。
状态模式的优点包括:
- 它将对象的行为与其内部状态分离,使得对象在不同的状态下表现出不同的行为。
- 它简化了状态转换的逻辑,使得状态转换更加清晰和易于维护。
然而状态模式也有一些潜在的缺点:
- 如果有大量的状态和复杂的状态转换逻辑,可能会导致代码变得庞大和难以理解。
- 如果状态之间的耦合度较高,可能会导致修改一个状态会影响到其他状态的实现。
策略模式(Strategy)
定义了一系列算法,并将每一种算法封装起来,使它们可以相互替换。
策略模式是一种行为设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用该算法的客户,使得在运行时能够根据需要动态地改变算法。
以下是一个简单的策略模式的Java代码示例:
首先定义一个策略接口(Strategy):
public interface Strategy {
int doOperation(int num1, int num2);
}
在这个例子中,Strategy
接口定义了一个doOperation()
方法,不同的策略将实现这个方法以执行特定的算法。
接下来创建两个具体的策略类(ConcreteStrategyA和ConcreteStrategyB):
public class ConcreteStrategyA implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
在上面的代码中,ConcreteStrategyA
和ConcreteStrategyB
类分别实现了Strategy
接口,并在其doOperation()
方法中实现了加法和乘法算法。
然后定义一个上下文类(Context),它包含了对当前策略的引用和执行策略的方法:
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy(int num1, int num2) {
System.out.println("Result: " + strategy.doOperation(num1, num2));
}
public void changeStrategy(Strategy newStrategy) {
this.strategy = newStrategy;
}
}
在上面的代码中,Context
类包含了对当前策略的引用,并提供了设置和获取策略的方法。它还包含了一个executeStrategy()
方法,当这个方法被调用时,它会调用当前策略的doOperation()
方法。
最后使用这些类来演示策略模式的工作原理:
public class Main {
public static void main(String[] args) {
Context context = new Context(new ConcreteStrategyA());
context.executeStrategy(3, 5); // 输出:Result: 8
context.changeStrategy(new ConcreteStrategyB());
context.executeStrategy(3, 5); // 输出:Result: 15
}
}
在这个例子中创建了一个Context
实例,并设置了初始策略为ConcreteStrategyA
。然后两次调用executeStrategy()
方法,第一次执行加法操作,第二次通过调用changeStrategy()
方法切换到乘法策略并执行乘法操作。
策略模式的优点包括:
- 它将算法与使用该算法的客户解耦,使得算法的变化不影响客户的代码。
- 它支持在运行时动态地改变对象的行为。
然而策略模式也有一些潜在的缺点:
- 如果有许多策略和复杂的策略选择逻辑,可能会导致代码变得庞大和难以理解。
- 如果策略之间的差异很小,使用策略模式可能会增加额外的复杂性。
模板方法模式(Template Method)
在一个抽象类中定义一个操作中的框架,而将一些步骤延迟到子类中。
模板方法模式是一种行为设计模式,它在一个抽象类中定义了一个算法的框架或者模板,并将一些步骤推迟到子类中实现。这种模式使得子类可以在不改变算法结构的情况下重定义某些特定的步骤。
以下是一个简单的模板方法模式的Java代码示例:
首先定义一个抽象类(AbstractClass),它包含了算法的框架和一个或多个抽象方法:
public abstract class AbstractClass {
public final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
System.out.println("模板方法中的常见步骤");
hook();
}
protected abstract void primitiveOperation1();
protected abstract void primitiveOperation2();
protected void hook() {
// 默认实现为空,子类可以重写此方法
}
}
在上面的代码中,AbstractClass
定义了一个名为templateMethod()
的模板方法,这个方法包含了算法的主要步骤。其中,primitiveOperation1()
和primitiveOperation2()
是抽象方法,需要由子类来实现。此外,还有一个可选的钩子方法hook()
,子类可以选择是否重写它以插入自定义的行为。
接下来创建两个具体的子类(ConcreteClassA和ConcreteClassB):
public class ConcreteClassA extends AbstractClass {
@Override
protected void primitiveOperation1() {
System.out.println("ConcreteClassA: 原始操作 1");
}
@Override
protected void primitiveOperation2() {
System.out.println("ConcreteClassA: 原始操作 2");
}
@Override
protected void hook() {
System.out.println("ConcreteClassA: 钩子方法");
}
}
public class ConcreteClassB extends AbstractClass {
@Override
protected void primitiveOperation1() {
System.out.println("ConcreteClassB: 原始操作 1");
}
@Override
protected void primitiveOperation2() {
System.out.println("ConcreteClassB: 原始操作 2");
}
@Override
protected void hook() {
System.out.println("ConcreteClassB: 钩子方法");
}
}
在上面的代码中,ConcreteClassA
和ConcreteClassB
分别继承了AbstractClass
并实现了primitiveOperation1()
、primitiveOperation2()
和hook()
方法。
最后使用这些类来演示模板方法模式的工作原理:
public class Main {
public static void main(String[] args) {
AbstractClass concreteClassA = new ConcreteClassA();
concreteClassA.templateMethod(); // 输出:ConcreteClassA: 原始操作 1, ConcreteClassA: 原始操作 2, 模板方法中的常见步骤, ConcreteClassA: 钩子方法
AbstractClass concreteClassB = new ConcreteClassB();
concreteClassB.templateMethod(); // 输出:ConcreteClassB: 原始操作 1, ConcreteClassB: 原始操作 2, 模板方法中的常见步骤, ConcreteClassB: 钩子方法
}
}
在这个例子中创建了ConcreteClassA
和ConcreteClassB
的实例,并调用了它们的templateMethod()
方法。由于模板方法是最终的(final),所以子类不能覆盖它,但可以通过实现抽象方法和钩子方法来定制算法的某些部分。
模板方法模式的优点包括:
- 它封装了算法的框架,使得代码更易于维护和扩展。
- 它允许子类在不改变算法整体结构的情况下重定义某些步骤。
然而模板方法模式也有一些潜在的缺点:
- 如果抽象类中的模板方法过于复杂,可能会导致代码难以理解和维护。
- 如果新的操作无法插入到现有的模板方法中,可能需要修改抽象类,这可能影响所有子类。
访问者模式(Visitor)
表示一个作用于某对象结构中的各元素的操作。
访问者模式是一种行为设计模式,它允许在不改变现有类结构的情况下为对象添加新的操作。这种模式通过将算法与对象结构分离,使得可以在不修改对象本身的情况下扩展对象的行为。
以下是一个简单的访问者模式的Java代码示例:
首先定义一个元素接口(Element)和两个具体的元素类(ConcreteElementA和ConcreteElementB):
public interface Element {
void accept(Visitor visitor);
}
public class ConcreteElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationA() {
System.out.println("ConcreteElementA: Operation A");
}
}
public class ConcreteElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public void operationB() {
System.out.println("ConcreteElementB: Operation B");
}
}
在上面的代码中,Element
接口定义了一个accept()
方法,用于接受访问者对象并调用其visit()
方法。ConcreteElementA
和ConcreteElementB
分别实现了Element
接口,并提供了自己的特定操作。
接下来定义一个访问者接口(Visitor)和两个具体的访问者类(ConcreteVisitor1 和 ConcreteVisitor2):
public interface Visitor {
void visit(ConcreteElementA element);
void visit(ConcreteElementB element);
}
public class ConcreteVisitor1 implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("ConcreteVisitor1 正在访问 ConcreteElementA");
element.operationA();
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("ConcreteVisitor1 正在访问 ConcreteElementB");
element.operationB();
}
}
public class ConcreteVisitor2 implements Visitor {
@Override
public void visit(ConcreteElementA element) {
System.out.println("ConcreteVisitor2 正在访问 ConcreteElementA");
element.operationA();
}
@Override
public void visit(ConcreteElementB element) {
System.out.println("ConcreteVisitor2 正在访问 ConcreteElementB");
element.operationB();
}
}
在上面的代码中,Visitor
接口定义了访问者需要实现的方法,这些方法对应于不同的元素类型。ConcreteVisitor1
和ConcreteVisitor2
分别实现了Visitor
接口,并提供了对不同元素类型的访问操作。
最后使用这些类来演示访问者模式的工作原理:
public class Main {
public static void main(String[] args) {
Element[] elements = {new ConcreteElementA(), new ConcreteElementB()};
Visitor visitor1 = new ConcreteVisitor1();
Visitor visitor2 = new ConcreteVisitor2();
for (Element element : elements) {
element.accept(visitor1);
}
System.out.println("\n");
for (Element element : elements) {
element.accept(visitor2);
}
}
}
在这个例子中创建了一个包含不同元素类型的数组,并创建了两个访问者对象。然后遍历元素数组,并对每个元素调用accept()
方法,从而触发访问者的visit()
方法。
访问者模式的优点包括:
- 它将对象结构和行为解耦,使得两者可以独立变化。
- 它支持在不修改对象结构的情况下添加新的操作。
然而访问者模式也有一些潜在的缺点:
- 当元素类或访问者类增加时,需要修改相关的接口和类,这可能违反开闭原则。
- 访问者模式可能会导致代码复杂性增加,特别是当对象结构和行为都很复杂时。
以上就是Java的23种设计模式,每种模式都是针对特定问题的解决方案,理解并恰当应用这些模式可以在编写代码时更灵活、更好扩展和维护。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!