本文参考:
基本原理:装饰器模式 | 菜鸟教程 (runoob.com)
基本原理:适配器模式 | 菜鸟教程 (runoob.com)
优缺点和区别,装饰模式:适配器模式和装饰模式 - 掘金 (juejin.cn)
装饰模式和责任链模式区别:【设计模式】——装饰模式VS职责链模式_Mandy_i的博客-CSDN博客
装饰者模式demo:设计模式-装饰者模式 (Decorator Pattern)-保姆级攻略,实战项目_哔哩哔哩_bilibili
这两种模式经常被拿出来进行考察分别,在这里我就尝试从一个初学者的角度做一个学习和记录。
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
对象:
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
何时使用: 1、系统需要使用现有的类,而此类的接口不符合系统的需要。 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
如何解决:继承或依赖(在构造函数中处理或者 get
、set
方法处理)。
关键代码:适配器继承或依赖已有的对象,实现想要的目标接口。
应用实例: 1、美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2、JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3、在 LINUX 上运行 WINDOWS 程序。 4、JAVA 中的 jdbc。
优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。
缺点: 1、过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2.由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。
使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
我们要实现一个聋哑人手语翻译的场景,这里聋哑人的手语是被适配的类,翻译器是适配器类,目标对象是通过翻译器掌握聋哑人的手语表达的一个对象。
实现要点:这里选择继承以及依赖两种方式,适配器类继承被适配的类,从而能够使用到被继承类的方法,同时实现目标类的接口,达成向目标类接口的转换。后面的调用只需要使用适配器类,就可以完成被适配基础类的行为。
定义被适配基础类接口
/**
* @Author jiangxuzhao
* @Description 被适配类的接口
* @Date 2023/5/22
*/
public interface Adaptee {
void handLanguage();
}
实现具体的被适配基础类
/**
* @Author jiangxuzhao
* @Description 具体的被适配基础类
* @Date 2023/5/22
*/
public class ConcreteAdaptee implements Adaptee{
@Override
public void handLanguage() {
System.out.println("打手语...");
}
}
定义目标类接口
/**
* @Author jiangxuzhao
* @Description 目标对象的接口
* @Date 2023/5/22
*/
public interface Target {
void targetTranslate();
}
实现具体的目标类
/**
* @Author jiangxuzhao
* @Description 具体的目标对象
* @Date 2023/5/22
*/
public class ConcreteTarget implements Target{
@Override
public void targetTranslate() {
System.out.println("人能搞懂的东西...");
}
}
实现适配器类
/**
* @Author jiangxuzhao
* @Description 适配器类,继承实现
* @Date 2023/5/22
*/
public class Adapter1 extends ConcreteAdaptee implements Target{
@Override
public void targetTranslate() {
super.handLanguage();
System.out.println("Adapter1 把手语翻译成人能搞懂的东西...");
}
}
/**
* @Author jiangxuzhao
* @Description 适配器类,构造函数依赖实现
* @Date 2023/5/22
*/
public class Adapter2 extends ConcreteAdaptee implements Target{
Adaptee adaptee;
// 构造函数依赖实现
public Adapter2(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void targetTranslate() {
adaptee.handLanguage();
System.out.println("Adapter2 把手语翻译成人能搞懂的东西...");
}
}
测试
import org.junit.Test;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/5/22
*/
public class testAdapter {
@Test
public void testAdapter1(){
Target target = new Adapter1();
target.targetTranslate();
}
@Test
public void testAdapter2(){
Target target = new Adapter2(new ConcreteAdaptee());
target.targetTranslate();
}
}
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
对象:被装饰基础类和装饰类。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
如何解决:将具体功能职责划分,继承或者依赖(关联使用)。
关键代码: 1、Component 类充当抽象角色,不应该具体实现。 2、修饰类继承 Component 类,具体扩展类重写父类方法。
应用实例: 1、孙悟空有 72 变,当他变成"庙宇"后,他的根本还是一只猴子,但是他又有了庙宇的功能。 2、不论一幅画有没有画框都可以挂在墙上,但是通常都是有画框的,并且实际上是画框被挂在墙上。在挂在墙上之前,画可以被蒙上玻璃,装到框子里;这时画、玻璃和画框形成了一个物体。
优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
缺点:多层装饰比较复杂。
使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。
注意事项:可代替继承。
我们要模拟一个制作饮料的场景,在基础的茶底中可以加入不同的配料进行装饰,使其成为一款新的子饮料,计算价格。
实现要点在于:基础类和装饰类都要继承于同一个父类,装饰类中可以通过构造方法传入前一次制作出的半成品(因为都是继承于同一个父类的),这也称之为关联使用,是一种很常见的复用技巧。
定义饮料抽象类,这是基础类和装饰类的共同类,这里我采用了抽象类定义,理论上来说接口定义也行
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/5/22
*/
public abstract class Beverage {
public abstract void cost();
}
实现具体的饮料基础类作为茶底
/**
* @Author jiangxuzhao
* @Description 奶茶茶底类
* @Date 2023/5/22
*/
public class MilkTea extends Beverage{
private static final double COST=5.5;
@Override
public double cost() {
System.out.println("奶茶茶底售价:"+COST);
return COST;
}
}
/**
* @Author jiangxuzhao
* @Description 果茶茶底类
* @Date 2023/5/22
*/
public class FruitTea extends Beverage{
private static final double COST=5.5;
@Override
public double cost() {
System.out.println("果茶茶底售价:"+COST);
return COST;
}
}
定义装饰者抽象类,继承于饮料父类
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/5/22
*/
public abstract class BeverageDecorator extends Beverage{
public abstract double cost();
}
实现具体的装饰者作为配料
/**
* @Author jiangxuzhao
* @Description 珍珠配料类
* @Date 2023/5/22
*/
public class Boba extends BeverageDecorator{
// 珍珠的价格
private static final double COST=1.0;
private final Beverage beverage;
public Boba(Beverage beverage) {
this.beverage=beverage;
}
@Override
public double cost() {
System.out.println("加入珍珠的单价为:"+COST);
return beverage.cost()+COST;
}
}
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/5/22
*/
public class Pudding extends BeverageDecorator{
private static final double COST=1.0;
private final Beverage beverage;
public Pudding(Beverage beverage) {
this.beverage = beverage;
}
@Override
public double cost() {
System.out.println("加入布丁的单价为:"+COST);
return beverage.cost()+COST;
}
}
测试
import org.junit.Test;
/**
* @Author jiangxuzhao
* @Description
* @Date 2023/5/22
*/
public class testDecorator {
@Test
public void testBeverage(){
// 奶茶底
Beverage myTea= new MilkTea();
// 加入两粒珍珠,一杯布丁
myTea = new Boba(myTea);
myTea = new Boba(myTea);
myTea = new Pudding(myTea);
// 输出最后的价格
double cost = myTea.cost();
System.out.println(cost);
}
}
输出:
加入布丁的单价为:1.0
加入珍珠的单价为:1.0
加入珍珠的单价为:1.0
奶茶茶底售价:5.5
8.5
适配器模式和装饰者模式都属于结构型模式,功能都类似,都是包装作用。
装饰模式强调的是动态扩展原有接口功能,所有装饰类都有一个共同的父类,而适配器模式侧重于转换接口,通过继承和依赖的方式,将源角色转换成目标角色。