面试题答案
一键面试相似之处
- 结构相似:适配器模式、装饰器模式和代理模式都涉及到通过引入一个中间对象来对目标对象进行某种操作,它们在类图结构上有一定相似性,都涉及到对象的组合或继承关系。
- 都对对象功能进行改变:这三种模式都在一定程度上改变了原始对象的行为或功能。代理模式控制对对象的访问,装饰器模式增加对象功能,适配器模式让不兼容接口能协同工作,都是对目标对象功能的一种改变。
不同之处
- 目的不同
- 适配器模式:主要用于将一个类的接口转换成客户希望的另一个接口,使原本由于接口不兼容而不能一起工作的类可以协同工作。例如把方形插头适配到圆形插座。
- 装饰器模式:用于向一个现有的对象添加新的功能,同时又不改变其结构。比如给咖啡添加各种配料(牛奶、糖等)。
- 代理模式:控制对对象的访问,在访问对象时起到中介作用,通常用于延迟加载、访问控制等场景。如网络代理访问网站。
- 类关系不同
- 适配器模式:适配器通常继承自适配者类或者实现适配者接口,同时实现目标接口。
- 装饰器模式:装饰器类实现与被装饰对象相同的接口,并且持有被装饰对象的引用。
- 代理模式:代理类实现与真实对象相同的接口,并且持有真实对象的引用。
- 功能改变方式不同
- 适配器模式:改变接口,使得原本不兼容的接口能够协同工作。
- 装饰器模式:在运行时动态地添加功能,且不改变对象的接口。
- 代理模式:控制对对象的访问,不改变对象本身的功能。
代码示例
适配器模式
// 适配者接口
interface AdapteeInterface {
void specificRequest();
}
// 适配者类
class Adaptee implements AdapteeInterface {
@Override
public void specificRequest() {
System.out.println("执行适配者的特定请求");
}
}
// 目标接口
interface TargetInterface {
void request();
}
// 适配器类
class Adapter implements TargetInterface {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
使用:
public class AdapterPatternDemo {
public static void main(String[] args) {
Adaptee adaptee = new Adaptee();
TargetInterface target = new Adapter(adaptee);
target.request();
}
}
装饰器模式
// 抽象组件
abstract class Beverage {
String description = "未知饮料";
public String getDescription() {
return description;
}
public abstract double cost();
}
// 具体组件
class Coffee extends Beverage {
public Coffee() {
description = "咖啡";
}
@Override
public double cost() {
return 2.0;
}
}
// 抽象装饰器
abstract class CondimentDecorator extends Beverage {
protected Beverage beverage;
public CondimentDecorator(Beverage beverage) {
this.beverage = beverage;
}
}
// 具体装饰器
class Milk extends CondimentDecorator {
public Milk(Beverage beverage) {
super(beverage);
description = beverage.getDescription() + ", 牛奶";
}
@Override
public double cost() {
return beverage.cost() + 0.5;
}
}
使用:
public class DecoratorPatternDemo {
public static void main(String[] args) {
Beverage coffee = new Coffee();
coffee = new Milk(coffee);
System.out.println(coffee.getDescription() + " 价格: " + coffee.cost());
}
}
代理模式
// 主题接口
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("从磁盘加载: " + fileName);
}
@Override
public void display() {
System.out.println("显示图像: " + 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();
}
}
使用:
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test.jpg");
image.display();
}
}
选择适配器模式的场景
当需要使用一个现有的类,但它的接口与所需的接口不兼容时,应选择适配器模式。比如在一个系统中引入一个新的第三方库,该库的接口与现有系统的接口不匹配,通过适配器模式可以将新库的接口适配成系统能使用的接口,而不需要修改现有系统或第三方库的代码。如果只是想在运行时为对象添加功能则应选装饰器模式;如果是要控制对对象的访问,如权限控制、延迟加载等场景则选代理模式。