上一篇地址:赶紧收藏!2024 年最常见 20道设计模式面试题(四)-CSDN博客
九、装饰器模式是如何在不修改对象结构的情况下增加额外功能的?
装饰器模式(Decorator Pattern)是一种结构设计模式,它允许用户在不修改对象结构的情况下动态地给一个对象添加额外的职责(功能)。这种模式通过创建一个包装对象,也就是装饰者,来包裹实际对象,然后在不修改实际对象代码的前提下,提供额外的功能。
装饰器模式的组成部分:
- 抽象组件(Component):定义了一个接口,描述了可以动态添加的行为。
- 具体组件(Concrete Component):实现抽象组件接口的具体类,也就是要被装饰的具体对象。
- 抽象装饰者(Decorator):抽象类,实现与具体组件相同的接口,持有一个组件的实例。
- 具体装饰者(Concrete Decorator):实现抽象装饰者,添加额外的职责。
装饰器模式如何增加额外功能:
-
定义接口:首先定义一个接口,这个接口描述了对象的基本行为。
-
创建具体组件:实现上述接口,创建具体的对象,这些对象是将要被装饰的。
-
创建抽象装饰者:创建一个抽象类,实现与具体组件相同的接口,并且持有一个组件的实例。
-
实现具体装饰者:创建具体装饰者类,继承自抽象装饰者,实现接口中的行为,并添加额外的功能。
-
装饰对象:通过创建具体装饰者的实例,并将具体组件作为参数传递给装饰者,来装饰具体组件。
-
使用装饰者:客户端代码通过装饰者接口与装饰后的对象交互,从而获得额外的功能。
装饰器模式的优点:
- 灵活性:可以在运行时动态地添加或修改对象的行为。
- 扩展性:可以很容易地添加新的装饰者,而不需要修改现有代码。
- 透明性:装饰者模式不会影响使用组件接口的客户。
- 复用性:可以重用已有的类。
应用场景:
- 当需要动态地给单个对象添加职责时。
- 当需要通过一种无须修改对象结构的方式来扩展功能时。
示例:
假设我们有一个咖啡店,我们想要为咖啡添加不同的调料,而不改变咖啡本身。使用装饰器模式,我们可以这样实现:
// 抽象组件
interface Coffee {double cost();
}// 具体组件
class SimpleCoffee implements Coffee {@Overridepublic double cost() {return 10.0;}
}// 抽象装饰者
abstract class CoffeeDecorator implements Coffee {protected Coffee coffee;public CoffeeDecorator(Coffee coffee) {this.coffee = coffee;}@Overridepublic double cost() {return coffee.cost();}
}// 具体装饰者
class MilkCoffee extends CoffeeDecorator {public MilkCoffee(Coffee coffee) {super(coffee);}@Overridepublic double cost() {return super.cost() + 2.0; // 增加牛奶的成本}
}// 客户端代码
Coffee coffee = new SimpleCoffee();
System.out.println("Cost: " + coffee.cost());Coffee milkCoffee = new MilkCoffee(coffee);
System.out.println("Cost with milk: " + milkCoffee.cost());
通过这种方式,装饰器模式允许我们透明地给对象添加新的职责,而不需要修改对象本身的代码,提供了一种灵活的扩展对象功能的方法。
十、请描述外观模式如何解决复杂系统的接口问题。
外观模式(Facade Pattern)是一种结构设计模式,它提供了一个统一的高层接口,用来访问子系统中的一群接口。外观模式定义了一个接口,这个接口使得子系统更容易使用。它隐藏了子系统内部的复杂性,让子系统与客户端之间的耦合度降低。
外观模式的组成部分:
- 外观(Facade):提供了一个高层的接口,使得客户端可以访问子系统中的一组功能。
- 子系统(Subsystem):一组类,每个类都代表了一个特定的功能,并且这些类与外观类交互。
外观模式如何解决复杂系统的接口问题:
-
简化接口:外观模式通过提供一个简单的接口,使得客户端不必了解子系统内部的复杂性。客户端只需要与外观类交互,而不需要直接与多个子系统类交互。
-
降低耦合度:通过外观类,客户端与子系统之间的耦合度降低。客户端不依赖于子系统的具体实现,只依赖于外观类。
-
易于扩展:当子系统需要扩展或修改时,只需要修改外观类或子系统内部的实现,而不需要修改客户端代码。
-
提供统一的访问点:外观类作为子系统的一个统一访问点,可以控制对子系统的操作顺序,确保操作的正确性。
-
隐藏实现细节:外观类隐藏了子系统的具体实现细节,客户端不需要了解子系统内部是如何工作的。
-
易于使用:客户端代码更加简洁和易于使用,因为它们只需要与外观类交互,而不是与多个子系统类交互。
外观模式的优点:
- 简化客户端代码:客户端不需要了解子系统的复杂性,只需要与外观类交互。
- 降低耦合度:客户端与子系统之间的耦合度降低,提高了系统的模块化。
- 易于维护和扩展:子系统的修改和扩展不会影响到客户端代码。
应用场景:
- 当需要向复杂系统的外部提供一个简化的接口时。
- 当需要客户端与多个子系统类交互,而这些子系统类的操作顺序很重要时。
示例:
假设我们有一个计算机系统,它由多个硬件组件组成,如CPU、内存和硬盘。使用外观模式,我们可以这样实现:
// 硬件组件接口
interface Component {void turnOn();void turnOff();
}// CPU组件
class CPU implements Component {public void turnOn() { /* ... */ }public void turnOff() { /* ... */ }
}// 内存组件
class Memory implements Component {public void turnOn() { /* ... */ }public void turnOff() { /* ... */ }
}// 硬盘组件
class HardDisk implements Component {public void turnOn() { /* ... */ }public void turnOff() { /* ... */ }
}// 外观类
class ComputerFacade {private CPU cpu;private Memory memory;private HardDisk hardDisk;public ComputerFacade() {cpu = new CPU();memory = new Memory();hardDisk = new HardDisk();}public void turnOn() {cpu.turnOn();memory.turnOn();hardDisk.turnOn();}public void turnOff() {cpu.turnOff();memory.turnOff();hardDisk.turnOff();}
}// 客户端代码
ComputerFacade computer = new ComputerFacade();
computer.turnOn();
// ... 使用计算机 ...
computer.turnOff();
通过这种方式,外观模式提供了一个简化的接口来控制计算机的启动和关闭过程,隐藏了内部组件的复杂性,使得客户端代码更加简洁和易于使用。