文章目录
前言
本篇是关于设计模式中装饰者模式、组合模式、以及外观模式的学习笔记。
一、装饰者模式
装饰者模式是一种结构型设计模式
,在不改变对象接口的情况下,动态地添加额外的功能到对象中。通过创建一个装饰类
来包裹原始类
的实例,并在保持原有接口不变的基础上,扩展其行为。
通常包含了以下角色:
- 组件接口:定义一个对象的接口,可以是抽象类或接口,通常是被装饰者和具体装饰器实现的基础。
- 具体组件:实现了组件接口的具体类,是被装饰的对象,提供了核心功能。
- 装饰器类:实现了组件接口并持有一个组件对象的引用(通常是组件接口类型)。装饰器类的职责是扩展 或修改核心对象的功能。
- 具体装饰器:继承自装饰器类,添加具体的功能或行为。
举一个在生活中的案例,假设咖啡有不同的品种,比如美式,拿铁等,每个品种又有不同的做法,可以加冰块,糖,牛奶等。如果需要下订单,则组合的方式非常多,无法一一列举。
利用装饰者模式,即可以创建一个组件接口,然后编写具体的实现:
java
/**
* 组件接口
*/
public interface Coffee {
String getDescription();
double cost();
}
java
/**
* 某个品种的咖啡
*/
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple Coffee";
}
@Override
public double cost() {
return 5.0; // 咖啡的价格
}
}
如果这些咖啡有不同的做法,则可以编写一个装饰器类:
java
/**
* 装饰器,持有咖啡的对象,也实现咖啡接口
*/
public class CoffeeDecorator implements Coffee{
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee){
this.coffee = coffee;
}
@Override
public String getDescription() {
return coffee.getDescription();
}
@Override
public double cost() {
return coffee.cost();
}
具体的做法,实现装饰器类:
java
public class IceDecorator extends CoffeeDecorator{
public IceDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + "冰块";
}
@Override
public double cost() {
return coffee.cost() + 1.0; // 咖啡费用 + 冰块费用
}
}
java
public class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee decoratedCoffee) {
super(decoratedCoffee);
}
@Override
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
@Override
public double cost() {
return coffee.cost() + 1.5; // 咖啡费用 + 牛奶的附加费用
}
}
用户在下单时即可进行组合:
java
public class Client {
public static void main(String[] args) {
Coffee coffee = new SimpleCoffee();
System.out.println("coffee.getDescription() = " + coffee.getDescription());
System.out.println("coffee.cost() = " + coffee.cost());
coffee = new IceDecorator(coffee);//相当于为coffee包装了一种做法
System.out.println("coffee.getDescription() = " + coffee.getDescription());
System.out.println("coffee.cost() = " + coffee.cost());
}
}
coffee.getDescription() = Simple Coffee
coffee.cost() = 5.0
coffee.getDescription() = Simple Coffee冰块
coffee.cost() = 6.0
虽然装饰器模式和桥接模式都将某个维度抽象的接口组合进了另一个维度的类中,但是还是有区别的,装饰者模式着重于扩展功能
,通过包装已有对象来增强功能。而桥接模式着重于解耦抽象与实现
,通过引入桥接类来避免抽象和实现的紧耦合。
二、组合模式
组合模式是一种结构型设计模式
,将对象组合成树形结构以表示"部分-整体"
的层次结构,它的主要角色有:
- 组件接口:定义了叶子对象和容器对象的共同接口。通常是一个抽象类或接口,声明所有子类必须实现的方法,例如 add()、remove() 和 getChild() 等。
- 叶子对象:是树的最基本单元,不再包含其他子对象。叶子对象实现了组件接口,但没有子节点。
- 容器对象:继承了组件接口,并可以包含多个子组件(可以是叶子对象或其他容器对象)。容器对象实现了对子组件的操作。
例如用一个案例表示操作系统中的文件体系。一个盘中可以有多个文件夹,一个文件夹中有多个文件,总体是一个树形结构。定义一个组件接口
,其中showDetails
作为抽象方法强制所有子类重写,而最底层的文件是没有增加或删除功能的,所以可以不用强制重写。
java
public abstract class FileSystemComponent {
abstract void showDetails();
void add() {
throw new UnsupportedOperationException();
}
void remove() {
throw new UnsupportedOperationException();
}
}
再定义一个叶子对象
,即文件:
java
public class File extends FileSystemComponent{
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void showDetails() {
System.out.println(name + "文件");
}
}
然后是容器对象
,即文件夹
java
public class Folder extends FileSystemComponent{
protected List<FileSystemComponent> fileSystemComponents = new ArrayList<>();
protected String name;
@Override
void add(FileSystemComponent fileSystemComponent) {
fileSystemComponents.add(fileSystemComponent);
}
@Override
void remove(FileSystemComponent fileSystemComponent) {
fileSystemComponents.remove(fileSystemComponent);
}
@Override
public void showDetails() {
System.out.println("文件夹"+name+"下有:");
for (FileSystemComponent fileSystemComponent : fileSystemComponents) {
System.out.println(fileSystemComponent+"文件");
}
}
}
创建测试对象:
java
public class Client {
public static void main(String[] args) {
//创建文件夹1
Folder folder = new Folder();
folder.name = "文件夹1";
//创建文件夹1中的a文件
File a = new File();
a.setName("a.txt");
//创建文件夹1中的b文件
File b = new File();
b.setName("b.txt");
//将a和b放入文件夹1
folder.add(a);
folder.add(b);
//创建文件夹2
Folder folder2 = new Folder();
folder2.name = "文件夹2";
//创建文件夹2中的c文件
File c = new File();
c.setName("c.txt");
//将c放入文件夹2
folder2.add(c);
//还可以将文件夹2放入文件夹1中
folder.add(folder2);
folder.showDetails();
}
}
Folder: 文件夹1
a.txt文件
b.txt文件
Folder: 文件夹2
c.txt文件
通过上面的案例,可以看出,组合模式尤其适用于树形结构
的表示,以及对单个对象和对象集合(组合)进行相同的操作。
三、外观模式
外观模式是一种结构型设计模式
,目的是通过为复杂系统提供一个统一的接口
,来简化客户端的操作和理解,将系统的内部复杂性隐藏在外部接口后面,使客户端与复杂子系统的交互变得简单。
例如页面上有一个下单
按钮,点击后用户跳转到支付页面,服务器处理下单请求,参数校验,创建订单等操作对于用户是无感知的,用户只需要点击下单按钮,即可一步完成这些操作。
举一个生活中的案例,假设有一个家庭影院系统,由多个组件组成:音响、投影仪、灯光、DVD播放机等。每个组件开启,以及使用的步骤不尽相同。用户不希望分别操作各个组件,而是希望直接一键启动整个系统并播放电影。
用下面几个类模拟一下音响,投影仪,灯光的操作:
java
package com.light.facade;
// 音响系统
public class Amplifier {
public void on() {
System.out.println("Amplifier is on");
}
public void off() {
System.out.println("Amplifier is off");
}
public void setVolume(int level) {
System.out.println("Setting amplifier volume to " + level);
}
}
// DVD 播放机
class DVDPlayer {
public void on() {
System.out.println("DVD Player is on");
}
public void off() {
System.out.println("DVD Player is off");
}
public void play() {
System.out.println("DVD Player is playing the movie");
}
public void stop() {
System.out.println("DVD Player has stopped the movie");
}
}
// 投影仪
class Projector {
public void on() {
System.out.println("Projector is on");
}
public void off() {
System.out.println("Projector is off");
}
public void wideScreenMode() {
System.out.println("Projector is in widescreen mode");
}
}
// 屏幕
class Screen {
public void down() {
System.out.println("Screen is lowered");
}
public void up() {
System.out.println("Screen is raised");
}
}
// 灯光系统
class Lights {
public void on() {
System.out.println("Lights are on");
}
public void off() {
System.out.println("Lights are off");
}
public void dim(int level) {
System.out.println("Lights are dimmed to " + level + "%");
}
}
外观类,用于组合对开启,关闭电影系统的各项操作,对外提供统一的方法一键操作。
java
/**
* 外观类
*/
public class HomeTheaterFacade {
protected Amplifier amplifier;
protected DVDPlayer dvdPlayer;
protected Lights lights;
protected Projector projector;
protected Screen screen;
public HomeTheaterFacade() {
}
public HomeTheaterFacade(Amplifier amplifier, DVDPlayer dvdPlayer, Lights lights, Projector projector, Screen screen) {
this.amplifier = amplifier;
this.dvdPlayer = dvdPlayer;
this.lights = lights;
this.projector = projector;
this.screen = screen;
}
/**
* 一键启动影院系统
*/
public void oneStepStart(){
System.out.println("Get ready to watch a movie...");
lights.dim(10); // 灯光调暗
screen.down(); // 拉下屏幕
projector.on(); // 打开投影仪
projector.wideScreenMode(); // 设置宽屏模式
amplifier.on(); // 打开音响
amplifier.setVolume(5); // 设置音响音量
dvdPlayer.on(); // 打开DVD播放器
dvdPlayer.play(); // 播放电影
}
/**
* 一键关闭影院系统
*/
public void oneStepEnd() {
System.out.println("Shutting down movie theater...");
lights.on(); // 打开灯光
screen.up(); // 升起屏幕
projector.off(); // 关闭投影仪
amplifier.off(); // 关闭音响
dvdPlayer.stop(); // 停止播放
dvdPlayer.off(); // 关闭DVD播放器
}
}
用户一键启动,关闭:
java
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade(
new Amplifier(),
new DVDPlayer(),
new Lights(),
new Projector(),
new Screen()
);
homeTheaterFacade.oneStepStart();
System.out.println("*********************");
homeTheaterFacade.oneStepEnd();
}
}
Get ready to watch a movie...
Lights are dimmed to 10%
Screen is lowered
Projector is on
Projector is in widescreen mode
Amplifier is on
Setting amplifier volume to 5
DVD Player is on
DVD Player is playing the movie
Shutting down movie theater...
Lights are on
Screen is raised
Projector is off
Amplifier is off
DVD Player has stopped the movie
DVD Player is off
通过上面的案例,可以看出,外观类
负责将各个底层组件所需要用到的功能进行组合,对于用户屏蔽了具体的细节,用户无需对每个组件单独进行操作,简化了客户端与多个子系统的交互。
外观模式非常适合以下几种情况:
- 当系统中的子系统过于复杂,且需要简化客户端与子系统的交互。
- 当希望将客户端与子系统解耦,使得客户端只与一个简单的接口交互。
- 当需要协调多个子系统的工作,并提供一个统一的访问点。
- 当使用第三方库或框架时,需要为其提供一个简化的接口。
- 当系统中有跨层次、跨模块的功能交互时,需要一个简化的协调接口。