导语
Java设计模式作为软件开发领域的瑰宝,不仅体现了面向对象设计原则的应用,更是解决复杂编程问题、提升代码质量和可维护性的强大工具。本文将深入探讨Java设计模式的基本概念、分类、核心原理,并结合具体示例阐述几种重要设计模式的运用,旨在引导读者理解并掌握这一重要知识体系,将其应用于实际项目开发中。
创建型模式关注对象的创建过程,旨在提供灵活、高效、低耦合的实例化机制。以下是四种常见的创建型模式——单例模式、工厂方法模式、抽象工厂模式和建造者模式的代码示例与详细讲解。
问题场景:某些类只需要一个全局唯一的实例,比如配置文件读取器、数据库连接池等。
解决方案:确保此类只能创建一个实例,并提供一个全局访问点。
代码示例:
public class Singleton {
// 私有构造函数,防止外部直接实例化
private Singleton() {}
// 单例对象
private static final Singleton INSTANCE = new Singleton();
// 提供全局访问点
public static Singleton getInstance() {
return INSTANCE;
}
// 示例方法
public void doSomething() {
System.out.println("Singleton instance is working.");
}
}
// 使用示例
Singleton singleton = Singleton.getInstance();
singleton.doSomething();
问题场景:一个类无法预见需要创建哪种具体产品对象,或者希望将产品对象的创建过程延迟到子类中进行。
解决方案:定义一个创建对象的接口(或抽象类),让子类决定实例化哪个具体类。
代码示例:
// 抽象产品角色
abstract class Animal {
abstract void makeSound();
}
// 具体产品角色
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Meow!");
}
}
// 抽象工厂角色
abstract class AnimalFactory {
abstract Animal createAnimal();
}
// 具体工厂角色
class DogFactory extends AnimalFactory {
@Override
Animal createAnimal() {
return new Dog();
}
}
class CatFactory extends AnimalFactory {
@Override
Animal createAnimal() {
return new Cat();
}
}
// 使用示例
AnimalFactory dogFactory = new DogFactory();
Animal dog = dogFactory.createAnimal();
dog.makeSound();
AnimalFactory catFactory = new CatFactory();
Animal cat = catFactory.createAnimal();
cat.makeSound();
问题场景:需要创建一系列相关或相互依赖的对象,而无需指定具体类;或者需要为一组相关的产品提供一个统一的接口。
解决方案:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。
代码示例:
// 抽象产品族接口
interface Color {
String getColorName();
}
interface Shape {
String getShapeName();
}
// 具体产品族
class Red implements Color {
@Override
public String getColorName() {
return "Red";
}
}
class Blue implements Color {
@Override
public String getColorName() {
return "Blue";
}
}
class Square implements Shape {
@Override
public String getShapeName() {
return "Square";
}
}
class Circle implements Shape {
@Override
public String getShapeName() {
return "Circle";
}
}
// 抽象工厂接口
interface ColorShapeFactory {
Color createColor();
Shape createShape();
}
// 具体工厂
class RedSquareFactory implements ColorShapeFactory {
@Override
public Color createColor() {
return new Red();
}
@Override
public Shape createShape() {
return new Square();
}
}
class BlueCircleFactory implements ColorShapeFactory {
@Override
public Color createColor() {
return new Blue();
}
@Override
public Shape createShape() {
return new Circle();
}
}
// 使用示例
ColorShapeFactory redSquareFactory = new RedSquareFactory();
Color color = redSquareFactory.createColor();
Shape shape = redSquareFactory.createShape();
System.out.printf("Produced: %s %s%n", color.getColorName(), shape.getShapeName());
ColorShapeFactory blueCircleFactory = new BlueCircleFactory();
color = blueCircleFactory.createColor();
shape = blueCircleFactory.createShape();
System.out.printf("Produced: %s %s%n", color.getColorName(), shape.getShapeName());
问题场景:复杂对象的构建过程可能涉及多个步骤,且步骤间存在一定的依赖关系;或者希望将构建过程与表示分离,使得相同的构建过程可以创建不同的表示。
解决方案:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
代码示例:
// 产品类
class Pizza {
private String dough;
private String sauce;
private List toppings;
// 构造器私有化,禁止直接创建
private Pizza(PizzaBuilder builder) {
this.dough = builder.dough;
this.sauce = builder.sauce;
this.toppings = builder.toppings;
}
public static class PizzaBuilder {
private String dough = "Regular";
private String sauce = "Marinara";
private List toppings = new ArrayList<>();
public PizzaBuilder addTopping(String topping) {
toppings.add(topping);
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
@Override
public String toString() {
return "Pizza{" +
"dough='" + dough + '\'' +
", sauce='" + sauce + '\'' +
", toppings=" + toppings +
'}';
}
}
// 使用示例
Pizza pizza = new Pizza.PizzaBuilder()
.addTopping("Pepperoni")
.addTopping("Mushrooms")
.build();
System.out.println(pizza);
问题场景:某些对象的创建成本较高或者构造过程较复杂,频繁创建此类对象可能导致资源浪费。同时,有时需要创建与现有对象状态完全一致的新对象,而不仅仅是通过new关键字简单地创建一个新对象。
解决方案:提供一个原型接口,使得任何类只要实现该接口就可以被复制。当需要创建新对象时,不是直接新建,而是通过复制一个现有原型对象(克隆)来创建。这样,通过复制已有的原型对象,可以快速创建大量相似或相同状态的新对象,同时避免了复杂的初始化过程。
代码示例:
import java.util.Date;
// 原型接口
interface Prototype {
Prototype clone();
}
// 具体原型类
class ConcretePrototype implements Prototype {
private String id;
private Date dateCreated;
public ConcretePrototype(String id) {
this.id = id;
this.dateCreated = new Date();
}
@Override
public Prototype clone() {
try {
return (Prototype) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 不应该抛出此异常,因为Object类默认支持浅克隆
}
}
@Override
public String toString() {
return "ConcretePrototype{" +
"id='" + id + '\'' +
", dateCreated=" + dateCreated +
'}';
}
}
// 客户端代码
public class PrototypePatternDemo {
public static void main(String[] args) {
ConcretePrototype original = new ConcretePrototype("Original");
// 通过原型模式创建新对象
Prototype clone = original.clone();
System.out.println("Original object: " + original);
System.out.println("Cloned object: " + clone);
}
}
结构型模式关注类和对象的组合,旨在简化系统结构,使之更易于理解和管理。以下是六种常见的结构型模式——适配器模式、装饰器模式、代理模式、组合模式、外观模式和桥接模式的代码示例与详细讲解。
问题场景:已有的接口不符合使用要求,需要对其进行转换以匹配现有系统的需求。
解决方案:创建一个适配器类,使其既具备原有接口的功能,又符合新接口的要求。
代码示例:
// 已有接口(被适配者)
interface MediaPlayer {
void play(String audioType, String fileName);
}
// 实现已有接口的类
class AudioPlayer implements MediaPlayer {
@Override
public void play(String audioType, String fileName) {
if ("mp3".equals(audioType)) {
System.out.println("Playing MP3 file: " + fileName);
} else if ("vlc".equals(audioType)) {
System.out.println("Playing VLC file: " + fileName);
} else {
System.out.println("Unsupported format");
}
}
}
// 目标接口
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// 实现目标接口的类
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
// 适配器类
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedPlayer;
public MediaAdapter(String audioType) {
if ("vlc".equals(audioType)) {
advancedPlayer = new VlcPlayer();
} else if ("mp4".equals(audioType)) {
// 实例化相应高级播放器
}
}
@Override
public void play(String audioType, String fileName) {
if ("vlc".equals(audioType)) {
advancedPlayer.playVlc(fileName);
} else if ("mp4".equals(audioType)) {
advancedPlayer.playMp4(fileName);
}
}
}
// 使用示例
MediaPlayer player = new MediaAdapter("vlc");
player.play("vlc", "myFile.vlc");
问题场景:在运行时为对象动态地添加额外职责(属性或行为),同时保持类结构的稳定。
解决方案:创建一个装饰类,包裹原始对象,并在其上添加额外功能。
代码示例:
// 原始组件接口
interface Coffee {
double getCost();
String getDescription();
}
// 原始组件实现
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1.0;
}
@Override
public String getDescription() {
return "Simple coffee";
}
}
// 装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
}
// 具体装饰器类
class MilkCoffeeDecorator extends CoffeeDecorator {
public MilkCoffeeDecorator(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
@Override
public String getDescription() {
return super.getDescription() + ", with milk";
}
}
// 使用示例
Coffee simpleCoffee = new SimpleCoffee();
System.out.println(simpleCoffee.getDescription() + " costs $" + simpleCoffee.getCost());
Coffee milkCoffee = new MilkCoffeeDecorator(simpleCoffee);
System.out.println(milkCoffee.getDescription() + " costs $" + milkCoffee.getCost());
问题场景:为了控制访问、增强功能或减轻真实对象的负担,需要为某个对象提供一个代理对象。
解决方案:创建一个代理类,包含对真实对象的引用,代理对象在客户端和真实对象之间起到中介作用。
代码示例:
// 真实主题接口
interface Image {
void display();
}
// 真实主题实现
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName); // 加载图片到内存
}
private void loadFromDisk(String fileName) {
System.out.println("Loading image from disk: " + fileName);
}
@Override
public void display() {
System.out.println("Displaying image: " + fileName);
}
}
// 代理类
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();
}
}
// 使用示例
Image proxyImage = new ProxyImage("image.jpg");
proxyImage.display();
问题场景:树形结构中,叶子节点和容器节点(包含多个子节点)应该具有相同的接口,使得客户端可以以一致的方式处理单个对象和组合对象。
解决方案:定义抽象构件类,包含所有子类共有的操作,并定义容器构件类,包含集合操作以及对子构件的操作。
代码示例:
// 抽象构件接口
interface Component {
void operation();
}
// 叶子构件
class Leaf implements Component {
@Override
public void operation() {
System.out.println("Leaf operation");
}
}
// 容器构件
class Composite implements Component {
private List children = new ArrayList<>();
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
@Override
public void operation() {
for (Component child : children) {
child.operation();
}
System.out.println("Composite operation");
}
}
// 使用示例
Composite root = new Composite();
root.operation();
Leaf leaf1 = new Leaf();
root.add(leaf1);
Composite branch = new Composite();
root.add(branch);
Leaf leaf2 = new Leaf();
branch.add(leaf2);
root.operation();
问题场景:系统中有多个复杂的子系统,客户端需要与这些子系统交互,但直接调用子系统的接口会带来复杂性。
解决方案:创建一个外观类,为子系统提供一个简化的、统一的接口,客户端通过该接口与子系统交互。
代码示例:
// 子系统接口和实现
interface SubSystemA {
void operationA();
}
class SubSystemAImpl implements SubSystemA {
@Override
public void operationA() {
System.out.println("SubSystemA: Operation A executed.");
}
}
interface SubSystemB {
void operationB();
}
class SubSystemBImpl implements SubSystemB {
@Override
public void operationB() {
System.out.println("SubSystemB: Operation B executed.");
}
}
interface SubSystemC {
void operationC();
}
class SubSystemCImpl implements SubSystemC {
@Override
public void operationC() {
System.out.println("SubSystemC: Operation C executed.");
}
}
// 外观类
class Facade {
private SubSystemA subSystemA;
private SubSystemB subSystemB;
private SubSystemC subSystemC;
public Facade() {
subSystemA = new SubSystemAImpl();
subSystemB = new SubSystemBImpl();
subSystemC = new SubSystemCImpl();
}
public void performComplexOperation() {
System.out.println("Starting complex operation...");
subSystemA.operationA();
subSystemB.operationB();
subSystemC.operationC();
System.out.println("Complex operation completed.");
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.performComplexOperation();
}
}
问题场景:系统中存在大量相似对象,它们的大部分状态是相同的,只有小部分状态不同。如果不加以优化,可能会造成大量的内存开销。
解决方案:创建一个享元类,将内部状态作为享元对象的成员变量,外部状态作为方法参数传入。通过享元池(Flyweight Pool)管理共享的享元对象,客户端请求对象时,如果享元池中已有对应的对象,则直接返回;否则创建新的享元对象并加入享元池。
代码示例:
import java.util.HashMap;
import java.util.Map;
// 享元接口
interface Character {
void display(int fontSize, int x, int y);
}
// 内部状态(颜色)
enum Color {
RED, GREEN, BLUE
}
// 享元类
class FontCharacter implements Character {
private final char character;
private final Color color;
// 构造函数私有化,仅在享元工厂内使用
private FontCharacter(char character, Color color) {
this.character = character;
this.color = color;
}
@Override
public void display(int fontSize, int x, int y) {
System.out.printf("Displaying character '%c' in color %s at (%d, %d), font size %d%n",
character, color, x, y, fontSize);
}
// 享元工厂
static class Factory {
private final Map flyweights = new HashMap<>();
public FontCharacter getFontCharacter(char character, Color color) {
CharacterKey key = new CharacterKey(character, color);
FontCharacter fc = flyweights.get(key);
if (fc == null) {
fc = new FontCharacter(character, color);
flyweights.put(key, fc);
}
return fc;
}
// 用于标识享元对象的键
private static class CharacterKey {
private final char character;
private final Color color;
CharacterKey(char character, Color color) {
this.character = character;
this.color = color;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CharacterKey that = (CharacterKey) o;
return character == that.character && color == that.color;
}
@Override
public int hashCode() {
return Objects.hash(character, color);
}
}
}
}
// 客户端代码
public class Client {
public static void main(String[] args) {
FontCharacter.Factory factory = new FontCharacter.Factory();
// 请求两个相同的字符,颜色相同
Character c1 = factory.getFontCharacter('A', Color.RED);
Character c2 = factory.getFontCharacter('A', Color.RED);
assert c1 == c2; // 证明是同一个对象
// 请求两个相同的字符,颜色不同
Character d1 = factory.getFontCharacter('B', Color.GREEN);
Character d2 = factory.getFontCharacter('B', Color.BLUE);
assert d1 != d2; // 证明是不同对象,因为颜色不同
// 使用享元对象
c1.display(12, 100, 100);
d1.display(14, 200, 200);
}
}
问题场景:系统中存在两个独立变化的维度,如算法与数据结构、用户界面与操作系统平台、图形设备与渲染引擎等。传统的继承方式会导致类的数目急剧增加(类爆炸问题),并且难以应对未来需求的变化。
解决方案:创建一个抽象类(Abstraction),定义与抽象部分相关的操作,这些操作委托给实现了特定接口(Implementor)的对象。抽象类和实现接口之间形成“桥”,允许两者独立发展和组合。
代码示例:
// 实现接口(Implementor)
interface DrawingAPI {
void drawCircle(double x, double y, double radius);
}
// 实现接口的具体实现类
class DrawingAPI1 implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.printf("Drawing API 1: Circle at (%f, %f) with radius %f%n", x, y, radius);
}
}
class DrawingAPI2 implements DrawingAPI {
@Override
public void drawCircle(double x, double y, double radius) {
System.out.printf("Drawing API 2: Circle at (%f, %f) with radius %f%n", x, y, radius);
}
}
// 抽象类(Abstraction)
abstract class Shape {
protected DrawingAPI drawingAPI;
public Shape(DrawingAPI drawingAPI) {
this.drawingAPI = drawingAPI;
}
abstract void draw();
// 其他共有操作...
}
// 具体子类(Concrete Implementations of Abstraction)
class CircleShape extends Shape {
private double x, y, radius;
public CircleShape(double x, double y, double radius, DrawingAPI drawingAPI) {
super(drawingAPI);
this.x = x;
this.y = y;
this.radius = radius;
}
@Override
void draw() {
drawingAPI.drawCircle(x, y, radius);
}
}
// 客户端代码
public class BridgePatternDemo {
public static void main(String[] args) {
Shape circle1 = new CircleShape(1.0, 2.0, 3.0, new DrawingAPI1());
Shape circle2 = new CircleShape(4.0, 5.0, 6.0, new DrawingAPI2());
circle1.draw(); // 输出:Drawing API 1: Circle at (1.000000, 2.000000) with radius 3.000000
circle2.draw(); // 输出:Drawing API 2: Circle at (4.000000, 5.000000) with radius 6.000000
}
}
行为型设计模式关注的是对象之间的通信、职责分配以及算法的组织。以下是几种常见的行为型模式及其代码示例和详细讲解:
问题场景:一个类的行为或算法在其生命周期内可能会发生改变,或者存在多种行为算法可供选择,且这些行为或算法在概念上属于同一组。直接将这些行为或算法硬编码到类中会导致类变得庞大且不易维护,而且难以应对后续需求变化。
解决方案:定义一个策略接口(或抽象类),为每一种具体的行为或算法提供一个单独的实现类。客户端代码针对接口编程,根据实际需求选择并注入相应的策略实现。这样,行为的改变或扩展只需替换或添加策略实现类,而不影响使用策略的上下文对象。
代码示例
// 策略接口
interface PaymentStrategy {
void pay(double amount);
}
// 具体策略类
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid via credit card: $" + amount);
}
}
class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid via PayPal: $" + amount);
}
}
class BankTransferPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid via bank transfer: $" + amount);
}
}
// 上下文类(使用策略的类)
class ShoppingCart {
private List- items;
private PaymentStrategy paymentStrategy;
public ShoppingCart(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
this.items = new ArrayList<>();
}
public void addItem(Item item) {
items.add(item);
}
public void checkout() {
double totalAmount = calculateTotal();
paymentStrategy.pay(totalAmount);
}
private double calculateTotal() {
// 计算购物车总金额(此处简化为固定值)
return 100.0;
}
}
// 客户端代码
public class StrategyPatternDemo {
public static void main(String[] args) {
ShoppingCart cartWithCreditCard = new ShoppingCart(new CreditCardPayment());
ShoppingCart cartWithPayPal = new ShoppingCart(new PayPalPayment());
ShoppingCart cartWithBankTransfer = new ShoppingCart(new BankTransferPayment());
cartWithCreditCard.checkout(); // 输出:Paid via credit card: $100.0
cartWithPayPal.checkout(); // 输出:Paid via PayPal: $100.0
cartWithBankTransfer.checkout(); // 输出:Paid via bank transfer: $100.0
}
}
问题场景:一个类中有一系列操作步骤构成了一个算法骨架,这些步骤中大部分是固定的,只有个别步骤可能根据具体情况有所不同。如果直接在基类中把这些步骤全部实现,会导致代码重复;如果把所有步骤都抽象为虚方法,又会让子类负担过重,需要实现所有步骤。此外,希望控制算法的整体流程,规定不变的部分,让子类专注于实现可变的细节。
解决方案:定义一个抽象类,它包含一个或多个模板方法(final方法),这些方法按照预定的逻辑顺序调用一系列基本方法(抽象方法或具体方法)。其中,抽象方法留给子类实现,具体方法则在抽象类中实现。这样,子类只需要关注那些与应用相关的差异性部分,而通用的算法结构和控制流则由抽象类统一管理。
代码示例:
// 抽象类(定义模板方法和基本方法)
abstract class CoffeeMaker {
final void makeCoffee() {
boilWater();
brewCoffeeGrinds();
pourIntoCup();
addCondiments();
}
// 基本方法:具体方法
void boilWater() {
System.out.println("Boiling water...");
}
// 基本方法:抽象方法,由子类实现
abstract void brewCoffeeGrinds();
// 基本方法:具体方法
void pourIntoCup() {
System.out.println("Pouring coffee into a cup...");
}
// 基本方法:抽象方法,由子类实现
abstract void addCondiments();
// 可选的钩子方法:具体方法,子类可以选择覆盖
void report() {
System.out.println("Coffee ready!");
}
}
// 具体子类(实现基本方法)
class AmericanoCoffeeMaker extends CoffeeMaker {
@Override
void brewCoffeeGrinds() {
System.out.println("Brewing Americano coffee grinds...");
}
@Override
void addCondiments() {
System.out.println("Adding hot water to Americano...");
}
}
// 客户端代码
public class TemplateMethodPatternDemo {
public static void main(String[] args) {
CoffeeMaker coffeeMaker = new AmericanoCoffeeMaker();
coffeeMaker.makeCoffee();
coffeeMaker.report();
}
}
问题场景:当对象状态发生改变时,需要自动通知其他对象。这些对象间通常是松散耦合的,不应硬编码它们之间的依赖。典型的应用场景包括事件驱动框架、模型-视图-控制器(MVC)架构中的视图更新、发布/订阅系统等。
解决方案:定义一个目标对象(Subject),它维护一个观察者列表。观察者(Observer)订阅目标对象以接收状态更新。当目标对象状态改变时,它遍历观察者列表并通知每个观察者。
代码示例:
import java.util.ArrayList;
import java.util.List;
// 目标接口(Subject)
interface Observable {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体目标(ConcreteSubject)
class WeatherData implements Observable {
private List observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
@Override
public void addObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// 观察者接口(Observer)
interface Observer {
void update(float temperature, float humidity, float pressure);
}
// 具体观察者(ConcreteObserver)
class DisplayElement implements Observer {
@Override
public void update(float temperature, float humidity, float pressure) {
display(temperature, humidity, pressure);
}
protected void display(float temperature, float humidity, float pressure) {
System.out.printf("Temperature: %.1f°C, Humidity: %.1f%%, Pressure: %.1f hPa%n",
temperature, humidity, pressure);
}
}
// 具体观察者(ConcreteObserver)
class CurrentConditionsDisplay extends DisplayElement {
public CurrentConditionsDisplay(Observable weatherData) {
weatherData.addObserver(this);
}
}
// 客户端代码
public class ObserverPatternDemo {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
// ... 添加更多观察者 ...
weatherData.setMeasurements(25.5f, 70.0f, 992.5f);
}
}
问题场景:不同的数据结构(如数组、链表、集合等)通常有不同的遍历方式。客户端代码在访问这些数据结构时,若直接暴露底层实现细节,不仅导致客户端与数据结构的紧密耦合,而且每次更换数据结构都需要修改客户端代码。此外,如果每种数据结构都要提供一套遍历接口,也会造成接口的冗余和混乱。
解决方案:定义一个迭代器接口,为不同类型的数据结构提供统一的遍历方式。具体数据结构(聚合)负责创建相应迭代器实例,迭代器负责跟踪遍历状态并提供遍历方法(如hasNext()、next()等)。客户端代码通过迭代器接口与数据结构交互,无须关心数据结构的具体类型。
代码示例:
// 抽象迭代器接口
interface Iterator {
boolean hasNext();
T next();
}
// 聚合接口
interface Aggregate {
Iterator createIterator();
}
// 具体聚合类:整数数组
class IntegerArray implements Aggregate {
private int[] elements;
public IntegerArray(int... elements) {
this.elements = elements;
}
@Override
public Iterator createIterator() {
return new IntegerArrayIterator(elements);
}
}
// 具体迭代器类:整数数组迭代器
class IntegerArrayIterator implements Iterator {
private int[] elements;
private int index;
public IntegerArrayIterator(int[] elements) {
this.elements = elements;
this.index = 0;
}
@Override
public boolean hasNext() {
return index < elements.length;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return elements[index++];
}
}
// 客户端代码
public class IteratorPatternDemo {
public static void main(String[] args) {
Aggregate aggregate = new IntegerArray(1, 2, 3, 4, 5);
Iterator iterator = aggregate.createIterator();
while (iterator.hasNext()) {
System.out.print(iterator.next() + " ");
}
}
}
问题场景:在一个系统中,多个对象可能都可以处理某个请求,但不确定哪个对象应该负责处理。传统的做法是将请求发送给每一个对象,直到找到合适的处理器。这种方式会导致请求发送者与各个处理器之间产生大量的耦合,且随着处理器数量的增长,系统复杂度和维护成本也随之增加。
解决方案:定义一个处理请求的抽象类(或接口),并将请求的处理责任沿着一条链传递下去,直到链上的某个处理器对象能够处理该请求为止。链上的每个节点都是一个处理器对象,它们拥有相同的接口,可以对请求做出处理或转发。客户端只需向链头提交请求,无需关心请求的处理细节和链条内部结构。
代码示例:
// 抽象处理器(定义处理请求的接口)
abstract class Handler {
protected Handler successor; // 下一个处理器
public void setSuccessor(Handler successor) {
this.successor = successor;
}
public abstract void handleRequest(int request);
}
// 具体处理器A
class ConcreteHandlerA extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 0 && request < 10) {
System.out.println("ConcreteHandlerA handled request: " + request);
} else if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("Request not handled.");
}
}
}
// 具体处理器B
class ConcreteHandlerB extends Handler {
@Override
public void handleRequest(int request) {
if (request >= 10 && request < 20) {
System.out.println("ConcreteHandlerB handled request: " + request);
} else if (successor != null) {
successor.handleRequest(request);
} else {
System.out.println("Request not handled.");
}
}
}
// 客户端代码
public class ChainOfResponsibilityPatternDemo {
public static void main(String[] args) {
Handler handlerA = new ConcreteHandlerA();
Handler handlerB = new ConcreteHandlerB();
handlerA.setSuccessor(handlerB); // 构建责任链
handlerA.handleRequest(5); // 输出:ConcreteHandlerA handled request: 5
handlerA.handleRequest(15); // 输出:ConcreteHandlerB handled request: 15
handlerA.handleRequest(25); // 输出:Request not handled.
}
}
问题场景:在软件设计中,有时需要将“请求”封装成对象,以便参数化、队列化、记录日志、支持撤销/重做等操作。直接将请求与接收者(执行请求的对象)紧耦合在一起,难以应对需求变更和扩展。此外,请求的发起者(调用者)应当与请求的执行细节解耦,以便独立演化。
解决方案:引入命令模式,将请求封装成对象,这个对象称为“命令”,包含一个或一组动作及其执行者。命令对象通常有一个公共接口,其中包括一个execute()方法,用于执行请求。调用者只需通过命令对象的execute()方法发出请求,无需了解请求的具体实现。命令模式使得请求的发起者、请求本身以及请求的接收者三者相互独立。
代码示例:
// 抽象命令接口
interface Command {
void execute();
}
// 具体命令类:开灯命令
class TurnOnLightCommand implements Command {
private final Light light;
public TurnOnLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOn();
}
}
// 具体命令类:关灯命令
class TurnOffLightCommand implements Command {
private final Light light;
public TurnOffLightCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
}
// 请求接收者:电灯
class Light {
public void turnOn() {
System.out.println("Light is turned on.");
}
public void turnOff() {
System.out.println("Light is turned off.");
}
}
// 调用者:遥控器
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 客户端代码
public class CommandPatternDemo {
public static void main(String[] args) {
Light light = new Light();
RemoteControl remote = new RemoteControl();
Command turnOnCommand = new TurnOnLightCommand(light);
Command turnOffCommand = new TurnOffLightCommand(light);
remote.setCommand(turnOnCommand);
remote.pressButton(); // 输出:Light is turned on.
remote.setCommand(turnOffCommand);
remote.pressButton(); // 输出:Light is turned off.
}
}
问题场景:在面向对象设计中,多个相关对象之间可能存在复杂的相互依赖和通信。如果这些对象直接引用并调用对方的方法,会导致高度耦合,难以理解和维护。尤其是在需求变更时,可能会牵一发而动全身,影响整个系统的稳定性。
解决方案:引入中介者模式,将原本直接交互的对象之间的关系转移到一个中介对象上。中介者封装了对象间的交互逻辑,简化了对象之间的联系。每个对象只需与中介者交互,而不直接与其他对象通信。这样既降低了对象间的耦合度,又保持了对象间行为的协调性。
代码示例:
// 抽象同事类
interface Colleague {
void send(String message, Mediator mediator);
void receive(String message);
}
// 具体同事类:用户A
class UserA implements Colleague {
private Mediator mediator;
public UserA(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void send(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println("UserA received message: " + message);
}
}
// 具体同事类:用户B
class UserB implements Colleague {
private Mediator mediator;
public UserB(Mediator mediator) {
this.mediator = mediator;
}
@Override
public void send(String message) {
mediator.sendMessage(message, this);
}
@Override
public void receive(String message) {
System.out.println("UserB received message: " + message);
}
}
// 中介者接口
interface Mediator {
void sendMessage(String message, Colleague sender);
}
// 具体中介者:聊天室
class ChatRoom implements Mediator {
private List colleagues = new ArrayList<>();
public void register(Colleague colleague) {
colleagues.add(colleague);
}
@Override
public void sendMessage(String message, Colleague sender) {
colleagues.forEach(colleague -> {
if (colleague != sender) {
colleague.receive(message);
}
});
}
}
// 客户端代码
public class MediatorPatternDemo {
public static void main(String[] args) {
Mediator chatRoom = new ChatRoom();
Colleague userA = new UserA(chatRoom);
Colleague userB = new UserB(chatRoom);
chatRoom.register(userA);
chatRoom.register(userB);
userA.send("Hello, UserB!");
userB.send("Hi, UserA!");
}
}
问题场景:一个对象在其生命周期内,其行为随内在状态的不同而变化。传统的实现方式是在一个类中使用条件语句(如if-else或switch-case)来根据对象状态选择不同的行为。这种实现方式会导致类变得庞大且难以维护,状态变化的逻辑分散,且不易于扩展新的状态。
解决方案:引入状态模式,将对象的各种状态封装成独立的类(状态类),每个状态类负责一种行为。原对象(上下文类)持有对当前状态对象的引用,并将与状态相关的操作委托给当前状态对象处理。当状态发生变化时,只需改变上下文中的状态对象即可,无需修改上下文类的代码。这样,就将状态与行为分离,使得状态的增删改查更加清晰、易于管理。
代码示例:
// 抽象状态接口
interface State {
void doAction(Context context);
}
// 具体状态类:正常状态
class NormalState implements State {
@Override
public void doAction(Context context) {
System.out.println("In normal state, performing normal action...");
context.setState(this); // 假设某种情况下返回到自身状态
}
}
// 具体状态类:紧急状态
class EmergencyState implements State {
@Override
public void doAction(Context context) {
System.out.println("In emergency state, performing emergency action...");
context.setState(new NormalState()); // 假设恢复正常状态
}
}
// 上下文类
class Context {
private State currentState;
public Context(State initialState) {
currentState = initialState;
}
public void setState(State newState) {
currentState = newState;
}
public void performAction() {
currentState.doAction(this);
}
}
// 客户端代码
public class StatePatternDemo {
public static void main(String[] args) {
Context context = new Context(new NormalState());
context.performAction(); // 输出:In normal state, performing normal action...
context.setState(new EmergencyState());
context.performAction(); // 输出:In emergency state, performing emergency action...
}
}
问题场景:在一个对象结构(如树形结构、复合对象等)中,存在多种不同类型的元素,且这些元素具有共同的操作接口。然而,针对这些元素的操作可能涉及跨越元素类型的逻辑,直接在元素类中添加新操作会导致类的膨胀,且违反了“单一职责原则”。另外,如果新增元素类型,原有的操作也需要在所有元素类中进行更新,违背了“开闭原则”。
解决方案:引入访问者模式,将对元素结构的操作集中到一个单独的访问者类中。元素类提供一个接受访问者的方法,允许访问者访问其内部结构并执行相应操作。这样,元素类专注于自身的数据和行为,而与元素操作相关的逻辑则封装在访问者类中,便于集中管理和扩展。
代码示例:
// 抽象元素接口
interface Element {
void accept(Visitor visitor);
}
// 具体元素类:员工
class Employee implements Element {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
}
// 访问者接口
interface Visitor {
void visit(Employee employee);
}
// 具体访问者类:薪资统计
class SalaryStatistics implements Visitor {
private double totalSalary;
public void visit(Employee employee) {
totalSalary += employee.getSalary();
System.out.println("Employee " + employee.getName() + " has a salary of " + employee.getSalary());
}
public double getTotalSalary() {
return totalSalary;
}
}
// 客户端代码
public class VisitorPatternDemo {
public static void main(String[] args) {
List employees = Arrays.asList(
new Employee("Alice", 5000),
new Employee("Bob", 7000),
new Employee("Charlie", 6000)
);
SalaryStatistics visitor = new SalaryStatistics();
for (Element employee : employees) {
employee.accept(visitor);
}
System.out.println("Total salary of all employees: " + visitor.getTotalSalary());
}
}
问题场景:在某些应用程序中,对象可能拥有复杂的状态,并且用户可能希望在任何时候能够恢复到先前的状态。直接暴露对象的状态或者将其序列化可能导致状态信息的不完整、对象的内部细节被暴露,甚至破坏对象的封装性。此外,如果对象状态的恢复逻辑直接嵌入对象自身,会使对象承担过多责任,不利于代码复用和维护。
解决方案:引入备忘录模式,通过创建一个备忘录类(Memento)来捕获和存储对象的内部状态。原始对象(Originator)负责创建备忘录,并在需要时利用备忘录恢复其状态。管理者(Caretaker)角色负责保存和提供备忘录,但不能直接访问备忘录的内容,从而保护了原始对象的封装性。
代码示例:
// 备忘录接口,定义了获取内部状态的窄接口
interface Memento {
String getState();
}
// 原始对象(Originator),拥有复杂状态并负责创建备忘录
class Originator {
private String state;
public void setState(String state) {
System.out.println("Setting state to: " + state);
this.state = state;
}
public Memento saveStateToMemento() {
System.out.println("Saving state to Memento...");
return new ConcreteMemento(state);
}
public void restoreStateFromMemento(Memento memento) {
String savedState = memento.getState();
System.out.println("Restoring state from Memento: " + savedState);
setState(savedState);
}
}
// 具体备忘录类,存储原始对象的内部状态
class ConcreteMemento implements Memento {
private final String state;
public ConcreteMemento(String state) {
this.state = state;
}
@Override
public String getState() {
return state;
}
}
// 管理者(Caretaker),负责保存和提供备忘录,但不能直接访问备忘录的内容
class Caretaker {
private List mementos = new ArrayList<>();
public void addMemento(Memento memento) {
mementos.add(memento);
}
public Memento getMemento(int index) {
return mementos.get(index);
}
}
// 客户端代码
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("State A");
caretaker.addMemento(originator.saveStateToMemento());
originator.setState("State B");
caretaker.addMemento(originator.saveStateToMemento());
originator.setState("State C");
System.out.println("\nNow restoring previous states:");
originator.restoreStateFromMemento(caretaker.getMemento(1)); // 回到 State B
originator.restoreStateFromMemento(caretaker.getMemento(0)); // 回到 State A
}
}
问题场景:在一些特定应用中,需要对特定领域的语言或规则进行解析和解释,例如简单的算术表达式、查询语言、配置文件等。如果直接使用硬编码的方式实现这些解释逻辑,会使得代码过于复杂,且难以应对未来规则的变化。此外,如果语言或规则有较强的结构规律,那么硬编码的实现方式往往无法充分利用这些规律,导致代码冗余和低效。
解决方案:引入解释器模式,将语言或规则定义为一系列符号(如终结符和非终结符)及其组合规则,并创建相应的解释器类来解释这些符号。通过这种方式,可以将复杂的解释逻辑分解为一组较小、较简单的解释器,每个解释器专注于解释某一类符号或表达式。当语言或规则发生变化时,只需要修改或扩展相应的解释器类,而不是修改大量的业务逻辑代码。
代码示例:
// 抽象表达式接口
interface Expression {
int interpret(Map variables);
}
// 终结符表达式:变量
class VariableExpression implements Expression {
private String variableName;
public VariableExpression(String variableName) {
this.variableName = variableName;
}
@Override
public int interpret(Map variables) {
return variables.get(variableName);
}
}
// 非终结符表达式:加法
class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Map variables) {
return left.interpret(variables) + right.interpret(variables);
}
}
// 客户端代码
public class InterpreterPatternDemo {
public static void main(String[] args) {
Map variables = new HashMap<>();
variables.put("x", 10);
variables.put("y", 20);
Expression expression = new AddExpression(
new VariableExpression("x"),
new VariableExpression("y")
);
int result = expression.interpret(variables);
System.out.println("Result: " + result); // Output: Result: 30
}
}
结语
Java设计模式的学习与实践对于提升软件开发水平具有重要意义。理解并熟练运用这些模式,可以帮助开发者编写出更易于维护、扩展、复用的高质量代码。然而,设计模式并非银弹,关键在于合理选择、适时运用。在实际项目中,应结合具体业务需求、技术栈特点及团队开发规范,权衡利弊,避免过度设计。持续探索、实践与反思,方能真正领略设计模式的魅力,成为更优秀的Java开发者。