探索Java设计模式:原理、应用与实践

导语

Java设计模式作为软件开发领域的瑰宝,不仅体现了面向对象设计原则的应用,更是解决复杂编程问题、提升代码质量和可维护性的强大工具。本文将深入探讨Java设计模式的基本概念、分类、核心原理,并结合具体示例阐述几种重要设计模式的运用,旨在引导读者理解并掌握这一重要知识体系,将其应用于实际项目开发中。

创建型模式(Creational Patterns)

创建型模式关注对象的创建过程,旨在提供灵活、高效、低耦合的实例化机制。以下是四种常见的创建型模式——单例模式、工厂方法模式、抽象工厂模式和建造者模式的代码示例与详细讲解。

1. 单例模式(Singleton Pattern)

问题场景:某些类只需要一个全局唯一的实例,比如配置文件读取器、数据库连接池等。

解决方案:确保此类只能创建一个实例,并提供一个全局访问点。

代码示例:

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();

2. 工厂方法模式(Factory Method Pattern)

问题场景:一个类无法预见需要创建哪种具体产品对象,或者希望将产品对象的创建过程延迟到子类中进行。

解决方案:定义一个创建对象的接口(或抽象类),让子类决定实例化哪个具体类。

代码示例:

// 抽象产品角色
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();

3. 抽象工厂模式(Abstract Factory Pattern)

问题场景:需要创建一系列相关或相互依赖的对象,而无需指定具体类;或者需要为一组相关的产品提供一个统一的接口。

解决方案:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们的具体类。

代码示例:

// 抽象产品族接口
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());

4. 建造者模式(Builder Pattern)

问题场景:复杂对象的构建过程可能涉及多个步骤,且步骤间存在一定的依赖关系;或者希望将构建过程与表示分离,使得相同的构建过程可以创建不同的表示。

解决方案:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

代码示例:

// 产品类
class Pizza {
    private String dough;
    private String sauce;
    private List<String> 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<String> 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);

 5.原型模式(Prototype Pattern)

问题场景:某些对象的创建成本较高或者构造过程较复杂,频繁创建此类对象可能导致资源浪费。同时,有时需要创建与现有对象状态完全一致的新对象,而不仅仅是通过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);
    }
}

结构型模式(Structural Patterns)

结构型模式关注类和对象的组合,旨在简化系统结构,使之更易于理解和管理。以下是六种常见的结构型模式——适配器模式、装饰器模式、代理模式、组合模式、外观模式和桥接模式的代码示例与详细讲解。

1. 适配器模式(Adapter Pattern)

问题场景:已有的接口不符合使用要求,需要对其进行转换以匹配现有系统的需求。

解决方案:创建一个适配器类,使其既具备原有接口的功能,又符合新接口的要求。

代码示例:

// 已有接口(被适配者)
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");

2. 装饰器模式(Decorator Pattern)

问题场景:在运行时为对象动态地添加额外职责(属性或行为),同时保持类结构的稳定。

解决方案:创建一个装饰类,包裹原始对象,并在其上添加额外功能。

代码示例:

// 原始组件接口
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());

3. 代理模式(Proxy Pattern)

问题场景:为了控制访问、增强功能或减轻真实对象的负担,需要为某个对象提供一个代理对象。

解决方案:创建一个代理类,包含对真实对象的引用,代理对象在客户端和真实对象之间起到中介作用。

代码示例:

// 真实主题接口
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();

4. 组合模式(Composite Pattern)

问题场景:树形结构中,叶子节点和容器节点(包含多个子节点)应该具有相同的接口,使得客户端可以以一致的方式处理单个对象和组合对象。

解决方案:定义抽象构件类,包含所有子类共有的操作,并定义容器构件类,包含集合操作以及对子构件的操作。

代码示例:

// 抽象构件接口
interface Component {
    void operation();
}

// 叶子构件
class Leaf implements Component {
    @Override
    public void operation() {
        System.out.println("Leaf operation");
    }
}

// 容器构件
class Composite implements Component {
    private List<Component> 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();

5. 外观模式(Facade Pattern)

问题场景:系统中有多个复杂的子系统,客户端需要与这些子系统交互,但直接调用子系统的接口会带来复杂性。

解决方案:创建一个外观类,为子系统提供一个简化的、统一的接口,客户端通过该接口与子系统交互。

代码示例:

// 子系统接口和实现
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();
    }
}

6. 享元模式(Flyweight Pattern)

问题场景:系统中存在大量相似对象,它们的大部分状态是相同的,只有小部分状态不同。如果不加以优化,可能会造成大量的内存开销。

解决方案:创建一个享元类,将内部状态作为享元对象的成员变量,外部状态作为方法参数传入。通过享元池(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<CharacterKey, FontCharacter> 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);
    }
}

7.桥接模式(Bridge Pattern)

问题场景:系统中存在两个独立变化的维度,如算法与数据结构、用户界面与操作系统平台、图形设备与渲染引擎等。传统的继承方式会导致类的数目急剧增加(类爆炸问题),并且难以应对未来需求的变化。

解决方案:创建一个抽象类(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
    }
}

行为型模式(Behavioral Patterns)

 行为型设计模式关注的是对象之间的通信、职责分配以及算法的组织。以下是几种常见的行为型模式及其代码示例和详细讲解:

1.策略模式(Strategy Pattern)

问题场景:一个类的行为或算法在其生命周期内可能会发生改变,或者存在多种行为算法可供选择,且这些行为或算法在概念上属于同一组。直接将这些行为或算法硬编码到类中会导致类变得庞大且不易维护,而且难以应对后续需求变化。

解决方案:定义一个策略接口(或抽象类),为每一种具体的行为或算法提供一个单独的实现类。客户端代码针对接口编程,根据实际需求选择并注入相应的策略实现。这样,行为的改变或扩展只需替换或添加策略实现类,而不影响使用策略的上下文对象。

代码示例

// 策略接口
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<Item> 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
    }
}

2.模板方法模式(Template Method Pattern)

问题场景:一个类中有一系列操作步骤构成了一个算法骨架,这些步骤中大部分是固定的,只有个别步骤可能根据具体情况有所不同。如果直接在基类中把这些步骤全部实现,会导致代码重复;如果把所有步骤都抽象为虚方法,又会让子类负担过重,需要实现所有步骤。此外,希望控制算法的整体流程,规定不变的部分,让子类专注于实现可变的细节。

解决方案:定义一个抽象类,它包含一个或多个模板方法(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();
    }
}

3.观察者模式(Observer Pattern)

问题场景:当对象状态发生改变时,需要自动通知其他对象。这些对象间通常是松散耦合的,不应硬编码它们之间的依赖。典型的应用场景包括事件驱动框架、模型-视图-控制器(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<Observer> 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);
    }
}

4.迭代器模式(Iterator Pattern)

问题场景:不同的数据结构(如数组、链表、集合等)通常有不同的遍历方式。客户端代码在访问这些数据结构时,若直接暴露底层实现细节,不仅导致客户端与数据结构的紧密耦合,而且每次更换数据结构都需要修改客户端代码。此外,如果每种数据结构都要提供一套遍历接口,也会造成接口的冗余和混乱。

解决方案:定义一个迭代器接口,为不同类型的数据结构提供统一的遍历方式。具体数据结构(聚合)负责创建相应迭代器实例,迭代器负责跟踪遍历状态并提供遍历方法(如hasNext()、next()等)。客户端代码通过迭代器接口与数据结构交互,无须关心数据结构的具体类型。

代码示例:

// 抽象迭代器接口
interface Iterator<T> {
    boolean hasNext();
    T next();
}

// 聚合接口
interface Aggregate<T> {
    Iterator<T> createIterator();
}

// 具体聚合类:整数数组
class IntegerArray implements Aggregate<Integer> {
    private int[] elements;

    public IntegerArray(int... elements) {
        this.elements = elements;
    }

    @Override
    public Iterator<Integer> createIterator() {
        return new IntegerArrayIterator(elements);
    }
}

// 具体迭代器类:整数数组迭代器
class IntegerArrayIterator implements Iterator<Integer> {
    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<Integer> aggregate = new IntegerArray(1, 2, 3, 4, 5);

        Iterator<Integer> iterator = aggregate.createIterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
    }
}

5.责任链模式(Chain of Responsibility Pattern)

问题场景:在一个系统中,多个对象可能都可以处理某个请求,但不确定哪个对象应该负责处理。传统的做法是将请求发送给每一个对象,直到找到合适的处理器。这种方式会导致请求发送者与各个处理器之间产生大量的耦合,且随着处理器数量的增长,系统复杂度和维护成本也随之增加。

解决方案:定义一个处理请求的抽象类(或接口),并将请求的处理责任沿着一条链传递下去,直到链上的某个处理器对象能够处理该请求为止。链上的每个节点都是一个处理器对象,它们拥有相同的接口,可以对请求做出处理或转发。客户端只需向链头提交请求,无需关心请求的处理细节和链条内部结构。

代码示例:

// 抽象处理器(定义处理请求的接口)
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.
    }
}

6.命令模式(Command Pattern)

问题场景:在软件设计中,有时需要将“请求”封装成对象,以便参数化、队列化、记录日志、支持撤销/重做等操作。直接将请求与接收者(执行请求的对象)紧耦合在一起,难以应对需求变更和扩展。此外,请求的发起者(调用者)应当与请求的执行细节解耦,以便独立演化。

解决方案:引入命令模式,将请求封装成对象,这个对象称为“命令”,包含一个或一组动作及其执行者。命令对象通常有一个公共接口,其中包括一个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.
    }
}

7.中介者模式(Mediator Pattern)

问题场景:在面向对象设计中,多个相关对象之间可能存在复杂的相互依赖和通信。如果这些对象直接引用并调用对方的方法,会导致高度耦合,难以理解和维护。尤其是在需求变更时,可能会牵一发而动全身,影响整个系统的稳定性。

解决方案:引入中介者模式,将原本直接交互的对象之间的关系转移到一个中介对象上。中介者封装了对象间的交互逻辑,简化了对象之间的联系。每个对象只需与中介者交互,而不直接与其他对象通信。这样既降低了对象间的耦合度,又保持了对象间行为的协调性。

代码示例:

// 抽象同事类
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<Colleague> 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!");
    }
}

8.状态模式(State Pattern)

问题场景:一个对象在其生命周期内,其行为随内在状态的不同而变化。传统的实现方式是在一个类中使用条件语句(如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...
    }
}

9.访问者模式(Visitor Pattern)

问题场景:在一个对象结构(如树形结构、复合对象等)中,存在多种不同类型的元素,且这些元素具有共同的操作接口。然而,针对这些元素的操作可能涉及跨越元素类型的逻辑,直接在元素类中添加新操作会导致类的膨胀,且违反了“单一职责原则”。另外,如果新增元素类型,原有的操作也需要在所有元素类中进行更新,违背了“开闭原则”。

解决方案:引入访问者模式,将对元素结构的操作集中到一个单独的访问者类中。元素类提供一个接受访问者的方法,允许访问者访问其内部结构并执行相应操作。这样,元素类专注于自身的数据和行为,而与元素操作相关的逻辑则封装在访问者类中,便于集中管理和扩展。

代码示例:

// 抽象元素接口
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<Element> 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());
    }
}

10.备忘录模式(Memento Pattern)

问题场景:在某些应用程序中,对象可能拥有复杂的状态,并且用户可能希望在任何时候能够恢复到先前的状态。直接暴露对象的状态或者将其序列化可能导致状态信息的不完整、对象的内部细节被暴露,甚至破坏对象的封装性。此外,如果对象状态的恢复逻辑直接嵌入对象自身,会使对象承担过多责任,不利于代码复用和维护。

解决方案:引入备忘录模式,通过创建一个备忘录类(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<Memento> 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
    }
}

11.解释器模式(Interpreter Pattern)

问题场景:在一些特定应用中,需要对特定领域的语言或规则进行解析和解释,例如简单的算术表达式、查询语言、配置文件等。如果直接使用硬编码的方式实现这些解释逻辑,会使得代码过于复杂,且难以应对未来规则的变化。此外,如果语言或规则有较强的结构规律,那么硬编码的实现方式往往无法充分利用这些规律,导致代码冗余和低效。

解决方案:引入解释器模式,将语言或规则定义为一系列符号(如终结符和非终结符)及其组合规则,并创建相应的解释器类来解释这些符号。通过这种方式,可以将复杂的解释逻辑分解为一组较小、较简单的解释器,每个解释器专注于解释某一类符号或表达式。当语言或规则发生变化时,只需要修改或扩展相应的解释器类,而不是修改大量的业务逻辑代码。

代码示例:

// 抽象表达式接口
interface Expression {
    int interpret(Map<String, Integer> variables);
}

// 终结符表达式:变量
class VariableExpression implements Expression {
    private String variableName;

    public VariableExpression(String variableName) {
        this.variableName = variableName;
    }

    @Override
    public int interpret(Map<String, Integer> 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<String, Integer> variables) {
        return left.interpret(variables) + right.interpret(variables);
    }
}

// 客户端代码
public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Map<String, Integer> 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开发者。

Java设计模式是一种用于解决软件设计问题的经验总结,它们提供了一套被广泛接受的最佳实践,用于创建可维护、可扩展和可复用的代码。下面是一些常见的Java设计模式及其实践: 1. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。它通常用于需要共享资源的情况,例如数据库连接池或日志记录器。 2. 工厂模式(Factory Pattern):定义一个创建对象的接口,但让子类决定实例化哪个类。它将对象的实例化过程与客户端代码解耦,提供了一种灵活的方式来创建对象。 3. 观察者模式(Observer Pattern):定义了一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新。它常用于事件驱动的系统。 4. 装饰器模式(Decorator Pattern):动态地将责任附加到对象上,提供了一种灵活的方式来扩展功能。它可以在不修改原始类代码的情况下,通过组合多个装饰器来增加对象的行为。 5. 策略模式(Strategy Pattern):定义一系列算法,并将每个算法封装在独立的类,使它们可以互换使用。它使得算法的变化不会影响到使用算法的客户端。 6. 适配器模式(Adapter Pattern):将一个类的接口转换成客户端所期望的另一个接口。它常用于集成已有代码或使不兼容的类能够一起工作。 7. 模板方法模式(Template Method Pattern):定义一个算法的骨架,将一些步骤的实现延迟到子类。它提供了一种在不改变算法结构的情况下,允许子类重新定义某些步骤的方式。 以上只是一些常见的Java设计模式,还有其他模式如建造者模式、原型模式等。选择合适的设计模式可以提高代码的可读性、可维护性和可扩展性。在实践,我们应当根据具体的需求和场景来选择合适的设计模式,并灵活运用它们。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小码快撩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值