文章目录
- 请列举出在 JDK 中几个常用的设计模式?
- 什么是设计模式?
- Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
- 在 Java 中,什么叫观察者设计模式(observer design pattern)?
- 使用工厂模式最主要的好处是什么?在哪里使用?
- 举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?
- 在 Java 中,为什么不允许从静态方法中访问非静态变量?
- 设计一个 ATM 机,请说出你的设计思路?
- 在 Java 中,什么时候用重载,什么时候用重写?
- 举例说明什么情况下会更倾向于使用抽象类而不是接口?
请列举出在 JDK 中几个常用的设计模式?
-
单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。在 JDK 中,
Runtime
类就是一个典型的单例模式的应用,通过Runtime.getRuntime()
方法获取运行时对象的唯一实例。 -
工厂模式(Factory Pattern):定义一个用于创建对象的接口,但由子类决定实例化哪个类。在 JDK 中,例如
Boolean.valueOf(boolean)
方法就是一个工厂方法,根据传入的参数返回Boolean
类的实例。 -
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。在 JDK 中,Swing 中的事件监听机制就是观察者模式的一个典型应用。
-
装饰器模式(Decorator Pattern):动态地给一个对象添加一些额外的职责,就扩展功能而言,装饰器模式比继承更为灵活。在 JDK 中,
java.io
包中的很多类(如BufferedInputStream
、BufferedOutputStream
等)就使用了装饰器模式来增强 I/O 功能。
什么是设计模式?
设计模式是解决特定问题的经过反复验证的通用解决方案。它们提供了一种标准的方法来解决常见的设计问题,帮助开发人员编写更加模块化、可维护和可扩展的代码。
Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式
单例设计模式确保某个类只有一个实例,并提供一个全局访问点。这种模式通常用于管理共享资源,或者需要限制某个类的实例个数的情况。
以下是一个使用枚举实现的线程安全的单例模式示例:
public enum Singleton {INSTANCE;// 可以在这里添加单例的其他属性和方法// 示例方法public void showMessage() {System.out.println("Hello, World!");}
}// 在其他类中使用单例
// Singleton.INSTANCE.showMessage();
使用枚举实现的单例模式具有以下优点:
- 线程安全:枚举类的实例是在类加载时创建的,由 JVM 保证线程安全。
- 简洁明了:代码简洁,无需手动实现双重检查锁等复杂的线程安全机制。
- 序列化安全:枚举类默认实现了 Serializable 接口,并且保证在反序列化时仍然是单例的。
因此,在现代 Java 应用中,推荐使用枚举实现单例模式。
在 Java 中,什么叫观察者设计模式(observer design pattern)?
观察者设计模式(Observer Design Pattern)是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听一个主题对象,当主题对象状态发生变化时,所有的观察者都会得到通知并自动更新。
在观察者模式中,有以下几个核心角色:
- 主题(Subject): 也称为被观察者或可观察者,它是被观察的对象。当其状态发生变化时,会通知所有注册的观察者。
- 观察者(Observer): 也称为订阅者或监听者,它是接收主题状态变化通知的对象,以便进行相应的操作。
- 具体主题(Concrete Subject): 实现了主题接口,负责维护一组观察者,并在自身状态发生变化时通知观察者。
- 具体观察者(Concrete Observer): 实现了观察者接口,定义了收到通知后所需执行的操作。
观察者模式在实际应用中具有广泛的应用,例如:
- GUI 程序中的事件处理机制。
- 订阅-发布系统中,发布者是主题,订阅者是观察者。
- 观察者模式也被广泛应用于 Java 中的事件监听器(Listener)机制、Swing 中的 MVC 架构等。
使用观察者模式可以实现松耦合的对象间交互,提高系统的灵活性和可扩展性。
使用工厂模式最主要的好处是什么?在哪里使用?
工厂模式的主要好处包括:
-
封装创建过程: 工厂模式将对象的创建过程封装在工厂类中,客户端代码只需要通过工厂类来获取所需对象,而无需了解对象创建的具体细节和逻辑。这样可以降低客户端与具体产品类之间的耦合度。
-
简化客户端代码: 客户端只需关注所需对象的接口或抽象类,而不需要关心具体的实现类。这样使得客户端代码更加简洁清晰。
-
提高代码的可维护性和扩展性: 当需要添加新的产品类时,只需创建相应的产品类和工厂类即可,无需修改现有客户端代码,符合开闭原则。
-
隐藏产品类的实现细节: 客户端无需了解产品类的具体实现细节,只需要知道如何使用产品对象即可。这样可以有效地隐藏产品类的实现细节,提高了系统的安全性和稳定性。
工厂模式通常在以下情况下使用:
- 当一个类不知道它所必须创建的对象的类时。
- 当一个类希望它的子类来指定它所创建的对象时。
- 当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望能够将哪一个帮助子类是代理决策者。
工厂模式是一种常见的设计模式,被广泛应用于软件开发中,特别是在需要根据不同条件创建不同类型对象实例的场景中。
举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类层次?
装饰模式增加强了单个对象的能力。Java IO 到处都使用了装饰模式,典型例子就是
Buffered 系列类如 BufferedReader 和 BufferedWriter,它们增强了 Reader 和 Writer 对象,
以实现提升性能的 Buffer 层次的读取和写入。
以下是一个简单的示例,演示了如何使用装饰模式实现一个咖啡店的咖啡订单系统:
// 定义咖啡接口
interface Coffee {String getDescription();double getCost();
}// 实现基础咖啡类
class BasicCoffee implements Coffee {@Overridepublic String getDescription() {return "Basic Coffee";}@Overridepublic double getCost() {return 2.0;}
}// 定义装饰器抽象类
abstract class CoffeeDecorator implements Coffee {protected Coffee decoratedCoffee;public CoffeeDecorator(Coffee decoratedCoffee) {this.decoratedCoffee = decoratedCoffee;}@Overridepublic String getDescription() {return decoratedCoffee.getDescription();}@Overridepublic double getCost() {return decoratedCoffee.getCost();}
}// 具体的装饰器类:加奶
class MilkDecorator extends CoffeeDecorator {public MilkDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic String getDescription() {return super.getDescription() + " + Milk";}@Overridepublic double getCost() {return super.getCost() + 0.5; // 加奶价格}
}// 具体的装饰器类:加糖
class SugarDecorator extends CoffeeDecorator {public SugarDecorator(Coffee decoratedCoffee) {super(decoratedCoffee);}@Overridepublic String getDescription() {return super.getDescription() + " + Sugar";}@Overridepublic 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
对象作为成员变量,它是装饰器的基类。具体的装饰器类 MilkDecorator
和 SugarDecorator
继承自 CoffeeDecorator
,并在原有的基础上添加了额外的功能。通过装饰器模式,可以动态地组合各种功能来创建不同的咖啡对象,而且可以灵活地添加或删除装饰器,而不影响原有的对象结构。
在 Java 中,为什么不允许从静态方法中访问非静态变量?
在 Java 中,静态方法属于类而不是对象实例,它们在类加载时就被加载到内存中,并且可以直接通过类名调用,而不需要创建类的实例。由于静态方法没有随着对象的创建而创建,因此在静态方法中无法直接访问非静态的成员变量,因为非静态变量是与对象实例相关联的,而静态方法在调用时可能并不存在任何对象实例。
如果静态方法需要访问非静态成员变量,通常需要满足以下两种方式之一:
- 将非静态变量也声明为静态变量,使其与类相关联而不是对象实例。这样静态方法就可以直接访问它们。
- 在静态方法中通过参数传递对象实例的引用,并通过该引用访问对象的非静态成员变量。
总之,静态方法在设计时应该尽量避免依赖于对象实例的状态,而应该专注于与类相关的操作。
设计一个 ATM 机,请说出你的设计思路?
设计一个ATM机可以包括以下几个方面的考虑:
-
用户认证和授权:用户需要提供有效的身份验证信息,如银行卡号和密码,以便ATM机能够确认其身份并授权其进行交易。
-
用户界面:ATM机需要提供友好的用户界面,包括屏幕显示、按键输入和语音提示等功能,以便用户能够方便地操作。
-
交易处理:ATM机需要支持各种交易类型,如取款、存款、转账、查询余额等,并且需要保证交易的安全性和可靠性。
-
硬件设备:ATM机需要包括各种硬件设备,如卡片读取器、键盘、屏幕、打印机、钞箱等,以支持各种交易需求。
-
安全性:ATM机需要具有高度的安全性,包括物理安全和逻辑安全,以防止各种安全威胁,如盗窃、欺诈和黑客攻击等。
-
日志和监控:ATM机需要记录所有的交易操作并生成相应的日志,以便进行交易追踪和监控,并且需要实时监控ATM机的状态和运行情况。
-
网络连接:ATM机需要与银行系统进行实时通讯,以便进行交易处理和账户信息更新等操作。
综上所述,设计一个ATM机需要综合考虑用户需求、系统安全性、硬件设备、网络通讯等多个方面,以确保ATM机能够稳定可靠地运行,并为用户提供良好的交易体验。
在 Java 中,什么时候用重载,什么时候用重写?
在 Java 中,当需要在同一个类中为相同的方法提供不同的实现时,可以使用方法的重载(overloading)。方法的重载是指在同一个类中定义多个方法,它们具有相同的名称但参数列表不同的特性。通过方法的重载,可以根据方法的参数类型、个数或顺序来决定调用哪个方法。
而当需要子类继承父类的方法并提供不同的实现时,可以使用方法的重写(overriding)。方法的重写是指子类定义一个与父类方法签名相同的方法,从而覆盖父类的方法实现。在重写中,子类可以重新定义方法的实现逻辑,以满足自己的特定需求。在重写时,子类方法的访问修饰符不能比父类方法的更严格,并且不能抛出比父类方法更多的异常。
总的来说,重载是在同一个类中为同一个方法提供不同的版本,而重写是子类继承父类方法并提供新的实现。重载用于方法名相同但参数不同的情况,而重写用于继承和多态的实现。
举例说明什么情况下会更倾向于使用抽象类而不是接口?
以下情况下可能更倾向于使用抽象类而不是接口:
- 当你需要在类的继承层次结构中提供一组通用行为,并且这些行为具有某种默认实现时,可以使用抽象类。抽象类可以为一些方法提供默认实现,而子类可以选择性地覆盖这些方法以满足特定需求。这样可以避免在每个子类中重复编写相同的代码。
abstract class Shape {// 抽象方法abstract double area();abstract double perimeter();// 默认实现void display() {System.out.println("This is a shape.");}
}
- 当你希望将一些相关的类组织在一起,并且它们共享某些通用特征时,可以使用抽象类。通过将这些类放在同一个抽象类的继承层次结构中,可以更清晰地表达它们之间的关系。
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");}
}
- 当你需要在类中定义一些非抽象方法,并且希望子类共享这些方法时,可以使用抽象类。抽象类可以包含非抽象方法的实现,这些方法可以直接在子类中使用,而无需重新实现。
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");}
}
总的来说,抽象类适合于在类的继承层次结构中提供通用行为和共享特征,以及需要为子类提供一些默认实现的情况。