设计模式面试专题


请列举出在 JDK 中几个常用的设计模式?

  1. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。在 JDK 中,Runtime 类就是一个典型的单例模式的应用,通过 Runtime.getRuntime() 方法获取运行时对象的唯一实例。

  2. 工厂模式(Factory Pattern):定义一个用于创建对象的接口,但由子类决定实例化哪个类。在 JDK 中,例如 Boolean.valueOf(boolean) 方法就是一个工厂方法,根据传入的参数返回 Boolean 类的实例。

  3. 观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在 JDK 中,Swing 中的事件监听机制就是观察者模式的一个典型应用。

  4. 装饰器模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,就扩展功能而言,装饰器模式比继承更为灵活。在 JDK 中,java.io 包中的很多类(如 BufferedInputStreamBufferedOutputStream 等)就使用了装饰器模式来增强 I/O 功能。

什么是设计模式?

设计模式是解决特定问题的经过反复验证的通用解决方案。它们提供了一种标准的方法来解决常见的设计问题,帮助开发人员编写更加模块化、可维护和可扩展的代码。

Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式

单例设计模式确保某个类只有一个实例,并提供一个全局访问点。这种模式通常用于管理共享资源,或者需要限制某个类的实例个数的情况。

以下是一个使用枚举实现的线程安全的单例模式示例:

public enum Singleton {
    INSTANCE;

    // 可以在这里添加单例的其他属性和方法

    // 示例方法
    public void showMessage() {
        System.out.println("Hello, World!");
    }
}

// 在其他类中使用单例
// Singleton.INSTANCE.showMessage();

使用枚举实现的单例模式具有以下优点:

  1. 线程安全:枚举类的实例是在类加载时创建的,由 JVM 保证线程安全。
  2. 简洁明了:代码简洁,无需手动实现双重检查锁等复杂的线程安全机制。
  3. 序列化安全:枚举类默认实现了 Serializable 接口,并且保证在反序列化时仍然是单例的。

因此,在现代 Java 应用中,推荐使用枚举实现单例模式。

在 Java 中,什么叫观察者设计模式(observer design pattern)?

观察者设计模式(Observer Design Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象状态发生变化时,所有的观察者都会得到通知并自动更新。

在观察者模式中,有以下几个核心角色:

  1. 主题(Subject): 也称为被观察者或可观察者,它是被观察的对象。当其状态发生变化时,会通知所有注册的观察者。
  2. 观察者(Observer): 也称为订阅者或监听者,它是接收主题状态变化通知的对象,以便进行相应的操作。
  3. 具体主题(Concrete Subject): 实现了主题接口,负责维护一组观察者,并在自身状态发生变化时通知观察者。
  4. 具体观察者(Concrete Observer): 实现了观察者接口,定义了收到通知后所需执行的操作。

观察者模式在实际应用中具有广泛的应用,例如:

  • GUI 程序中的事件处理机制。
  • 订阅-发布系统中,发布者是主题,订阅者是观察者。
  • 观察者模式也被广泛应用于 Java 中的事件监听器(Listener)机制、Swing 中的 MVC 架构等。

使用观察者模式可以实现松耦合的对象间交互,提高系统的灵活性和可扩展性。

使用工厂模式最主要的好处是什么?在哪里使用?

工厂模式的主要好处包括:

  1. 封装创建过程: 工厂模式将对象的创建过程封装在工厂类中,客户端代码只需要通过工厂类来获取所需对象,而无需了解对象创建的具体细节和逻辑。这样可以降低客户端与具体产品类之间的耦合度。

  2. 简化客户端代码: 客户端只需关注所需对象的接口或抽象类,而不需要关心具体的实现类。这样使得客户端代码更加简洁清晰。

  3. 提高代码的可维护性和扩展性: 当需要添加新的产品类时,只需创建相应的产品类和工厂类即可,无需修改现有客户端代码,符合开闭原则。

  4. 隐藏产品类的实现细节: 客户端无需了解产品类的具体实现细节,只需要知道如何使用产品对象即可。这样可以有效地隐藏产品类的实现细节,提高了系统的安全性和稳定性。

工厂模式通常在以下情况下使用:

  • 当一个类不知道它所必须创建的对象的类时。
  • 当一个类希望它的子类来指定它所创建的对象时。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望能够将哪一个帮助子类是代理决策者。

工厂模式是一种常见的设计模式,被广泛应用于软件开发中,特别是在需要根据不同条件创建不同类型对象实例的场景中。

举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?

装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是
Buffered 系列类如 BufferedReader 和 BufferedWriter,它们增强了 Reader 和 Writer 对象,
以实现提升性能的 Buffer 层次的读取和写入。

以下是一个简单的示例,演示了如何使用装饰模式实现一个咖啡店的咖啡订单系统:

// 定义咖啡接口
interface Coffee {
    String getDescription();
    double getCost();
}

// 实现基础咖啡类
class BasicCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Basic Coffee";
    }

    @Override
    public double getCost() {
        return 2.0;
    }
}

// 定义装饰器抽象类
abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee decoratedCoffee) {
        this.decoratedCoffee = decoratedCoffee;
    }

    @Override
    public String getDescription() {
        return decoratedCoffee.getDescription();
    }

    @Override
    public double getCost() {
        return decoratedCoffee.getCost();
    }
}

// 具体的装饰器类:加奶
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " + Milk";
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.5; // 加奶价格
    }
}

// 具体的装饰器类:加糖
class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee decoratedCoffee) {
        super(decoratedCoffee);
    }

    @Override
    public String getDescription() {
        return super.getDescription() + " + Sugar";
    }

    @Override
    public double getCost() {
        return super.getCost() + 0.3; // 加糖价格
    }
}

// 客户端代码
public class Main {
    public static void main(String[] args) {
        // 创建基础咖啡对象
        Coffee basicCoffee = new BasicCoffee();

        // 使用装饰器逐步装饰咖啡
        Coffee milkCoffee = new MilkDecorator(basicCoffee);
        Coffee sugarMilkCoffee = new SugarDecorator(milkCoffee);

        // 输出最终咖啡的描述和价格
        System.out.println("Description: " + sugarMilkCoffee.getDescription());
        System.out.println("Cost: $" + sugarMilkCoffee.getCost());
    }
}

在这个示例中,Coffee 接口定义了咖啡对象的基本行为,BasicCoffee 类是一个具体的咖啡实现。CoffeeDecorator 抽象类实现了 Coffee 接口,并持有一个 Coffee 对象作为成员变量,它是装饰器的基类。具体的装饰器类 MilkDecoratorSugarDecorator 继承自 CoffeeDecorator,并在原有的基础上添加了额外的功能。通过装饰器模式,可以动态地组合各种功能来创建不同的咖啡对象,而且可以灵活地添加或删除装饰器,而不影响原有的对象结构。

在 Java 中,为什么不允许从静态方法中访问非静态变量?

在 Java 中,静态方法属于类而不是对象实例,它们在类加载时就被加载到内存中,并且可以直接通过类名调用,而不需要创建类的实例。由于静态方法没有随着对象的创建而创建,因此在静态方法中无法直接访问非静态的成员变量,因为非静态变量是与对象实例相关联的,而静态方法在调用时可能并不存在任何对象实例。

如果静态方法需要访问非静态成员变量,通常需要满足以下两种方式之一:

  1. 将非静态变量也声明为静态变量,使其与类相关联而不是对象实例。这样静态方法就可以直接访问它们。
  2. 在静态方法中通过参数传递对象实例的引用,并通过该引用访问对象的非静态成员变量。

总之,静态方法在设计时应该尽量避免依赖于对象实例的状态,而应该专注于与类相关的操作。

设计一个 ATM 机,请说出你的设计思路?

设计一个ATM机可以包括以下几个方面的考虑:

  1. 用户认证和授权:用户需要提供有效的身份验证信息,如银行卡号和密码,以便ATM机能够确认其身份并授权其进行交易。

  2. 用户界面:ATM机需要提供友好的用户界面,包括屏幕显示、按键输入和语音提示等功能,以便用户能够方便地操作。

  3. 交易处理:ATM机需要支持各种交易类型,如取款、存款、转账、查询余额等,并且需要保证交易的安全性和可靠性。

  4. 硬件设备:ATM机需要包括各种硬件设备,如卡片读取器、键盘、屏幕、打印机、钞箱等,以支持各种交易需求。

  5. 安全性:ATM机需要具有高度的安全性,包括物理安全和逻辑安全,以防止各种安全威胁,如盗窃、欺诈和黑客攻击等。

  6. 日志和监控:ATM机需要记录所有的交易操作并生成相应的日志,以便进行交易追踪和监控,并且需要实时监控ATM机的状态和运行情况。

  7. 网络连接:ATM机需要与银行系统进行实时通讯,以便进行交易处理和账户信息更新等操作。

综上所述,设计一个ATM机需要综合考虑用户需求、系统安全性、硬件设备、网络通讯等多个方面,以确保ATM机能够稳定可靠地运行,并为用户提供良好的交易体验。

在 Java 中,什么时候用重载,什么时候用重写?

在 Java 中,当需要在同一个类中为相同的方法提供不同的实现时,可以使用方法的重载(overloading)。方法的重载是指在同一个类中定义多个方法,它们具有相同的名称但参数列表不同的特性。通过方法的重载,可以根据方法的参数类型、个数或顺序来决定调用哪个方法。

而当需要子类继承父类的方法并提供不同的实现时,可以使用方法的重写(overriding)。方法的重写是指子类定义一个与父类方法签名相同的方法,从而覆盖父类的方法实现。在重写中,子类可以重新定义方法的实现逻辑,以满足自己的特定需求。在重写时,子类方法的访问修饰符不能比父类方法的更严格,并且不能抛出比父类方法更多的异常。

总的来说,重载是在同一个类中为同一个方法提供不同的版本,而重写是子类继承父类方法并提供新的实现。重载用于方法名相同但参数不同的情况,而重写用于继承和多态的实现。

举例说明什么情况下会更倾向于使用抽象类而不是接口?

以下情况下可能更倾向于使用抽象类而不是接口:

  1. 当你需要在类的继承层次结构中提供一组通用行为,并且这些行为具有某种默认实现时,可以使用抽象类。抽象类可以为一些方法提供默认实现,而子类可以选择性地覆盖这些方法以满足特定需求。这样可以避免在每个子类中重复编写相同的代码。
abstract class Shape {
    // 抽象方法
    abstract double area();
    abstract double perimeter();
    
    // 默认实现
    void display() {
        System.out.println("This is a shape.");
    }
}
  1. 当你希望将一些相关的类组织在一起,并且它们共享某些通用特征时,可以使用抽象类。通过将这些类放在同一个抽象类的继承层次结构中,可以更清晰地表达它们之间的关系。
abstract class Animal {
    abstract void makeSound();
}

class Dog extends Animal {
    void makeSound() {
        System.out.println("Woof");
    }
}

class Cat extends Animal {
    void makeSound() {
        System.out.println("Meow");
    }
}
  1. 当你需要在类中定义一些非抽象方法,并且希望子类共享这些方法时,可以使用抽象类。抽象类可以包含非抽象方法的实现,这些方法可以直接在子类中使用,而无需重新实现。
abstract class Vehicle {
    void startEngine() {
        System.out.println("Engine started");
    }
    
    abstract void accelerate();
}

class Car extends Vehicle {
    void accelerate() {
        System.out.println("Car is accelerating");
    }
}

总的来说,抽象类适合于在类的继承层次结构中提供通用行为和共享特征,以及需要为子类提供一些默认实现的情况。

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XMYX-0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值