装饰者模式- 2020-12-07 00:13- 设计模式设计模式,装饰者模式
装饰者模式(Decorator Pattern)是指在不改变原有对象的基础之上,将功能附加到对 象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于结构型模式。
适用场景
1、用于扩展一个类的功能或给一个类添加附加职责。
2、动态的给一个对象添加功能,这些功能可以再动态的撤销。
优点
1、装饰者是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象
扩展功能,即插即用。
2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
3、装饰者完全遵守开闭原则。
缺点
1、会出现更多的代码,更多的类,增加程序复杂性。
2、动态装饰时,多层装饰时会更复杂。
demo
煎饼原生场景
来看一个这样的场景,上班族白领其实大多有睡懒觉的习惯,每天早上上班都是踩点, 于是很多小伙伴为了多赖一会儿床都不吃早餐。那么,也有些小伙伴可能在上班路上碰 到卖煎饼的路边摊,都会顺带一个到公司茶水间吃早餐。卖煎饼的大姐可以给你的煎饼加鸡蛋,也可以加香肠。
首先创建一个基础套餐煎饼 Battercake 类
/**
* 基础煎饼套餐
*
* @author Bai
* @date 2020/12/5 23:32
*/
public class BaseBattercake {
public String cook() {
return "基础套餐:一个鸡蛋";
}
public int getPrice() {
return 5;
}
}
创建一个加鸡蛋的煎饼 EggBattercake类
/**
* 再加一个蛋煎饼
*
* @author Bai
* @date 2020/12/6 0:24
*/
public class EggBattercake extends BaseBattercake {
@Override
public String cook() {
return super.cook() + "再加一个鸡蛋";
}
@Override
public int getPrice() {
return super.getPrice() + 1;
}
}
再创建一个既加鸡蛋又加香肠的 SausageBattercake类
/**
* 煎饼加香肠
*
* @author Bai
* @date 2020/12/6 0:25
*/
public class SausageBattercake extends BaseBattercake {
@Override
public String cook() {
return super.cook() + "再加一根香肠";
}
@Override
public int getPrice() {
return super.getPrice() + 2;
}
}
@Test
public void demo() {
/**
* 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
*/
Battercake battercake = new BaseBattercake();
EggBattercake eggBattercake = new EggBattercake();
SausageBattercake sausageBattercake = new SausageBattercake();
System.out.println(battercake.cook() + " 价格:" + battercake.getPrice());
System.out.println(eggBattercake.cook() + " 价格:" + eggBattercake.getPrice());
System.out.println(sausageBattercake.cook() + " 价格:" + sausageBattercake.getPrice());
}
基础套餐:一个鸡蛋 价格:5
基础套餐:一个鸡蛋再加一个鸡蛋 价格:6
基础套餐:一个鸡蛋再加一根香肠 价格:7
运行结果没有问题。但是,如果用户需要一个加 2 个鸡蛋加 1 根香肠的煎饼,那么用我 们现在的类结构是创建不出来的,也无法自动计算出价格,除非再创建一个类做定制。 如果需求再变,一直加定制显然是不科学的。那么下面我们就用装饰者模式来解决上面的问题。
煎饼+装饰者模式
先创建一个建煎饼的抽象 Battercake类
/**
* 煎饼 因煎饼经常变换,所以抽取出来
*
* @author Bai
* @date 2020/12/5 23:31
*/
public interface Battercake {
/**
* 摊煎饼
*
* @return
*/
String cook();
/**
* 价格
*
* @return
*/
int getPrice();
}
创建一个基本的煎饼(或者叫基础套餐)BaseBattercake,也就是被装饰者
/**
* 基础煎饼套餐
*
* @author Bai
* @date 2020/12/5 23:32
*/
public class BaseBattercake implements Battercake {
@Override
public String cook() {
return "基础套餐:一个鸡蛋";
}
@Override
public int getPrice() {
return 5;
}
}
然后,再创建一个扩展套餐的抽象装饰者 BattercakeDecorator 类
/**
* 煎饼装饰器, 继承被装饰接口
*
* @author Bai
* @date 2020/12/5 23:34
*/
public interface BattercakeDecorator extends Battercake {
}
然后,创建鸡蛋装饰者 EggDecorator 类
/**
* 鸡蛋 装饰
*
* @author Bai
* @date 2020/12/5 23:38
*/
public class EggDecorator implements BattercakeDecorator {
/**
* 被装饰者引用
*/
private Battercake battercake;
public EggDecorator(Battercake battercake) {
this.battercake = battercake;
}
@Override
public String cook() {
return battercake.cook() + ",再加一个鸡蛋";
}
@Override
public int getPrice() {
return battercake.getPrice() + 1;
}
}
创建香肠装饰者 SausageDecorator类
/**
* 香肠装饰者
*
* @author Bai
* @date 2020/12/5 23:42
*/
public class SausageDecorator implements BattercakeDecorator {
/**
* 持有被装饰者的引用
*/
private Battercake battercake;
public SausageDecorator(Battercake battercake) {
this.battercake = battercake;
}
@Override
public String cook() {
return battercake.cook() + ",再加一根香肠";
}
@Override
public int getPrice() {
return battercake.getPrice() + 2;
}
}
测试
@Test
public void demo() {
/**
* 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
*/
Battercake battercake = new BaseBattercake();
//加一个鸡蛋
battercake = new EggDecorator(battercake);
//再加一个香肠
battercake = new SausageDecorator(battercake);
System.out.println(battercake.cook());
System.out.println(battercake.getPrice());
}
基础套餐:一个鸡蛋,再加一个鸡蛋,再加一根香肠
8
使用了装饰者模式之后,对原有逻辑做了增强,原有逻辑不做变动,如果煎饼中还需要加入生菜,则需要再增加一个生菜装饰者即可,LettuceDecorator
/**
* 生菜装饰
*
* @author Bai
* @date 2020/12/5 23:56
*/
public class LettuceDecorator implements BattercakeDecorator {
/**
* 持有被装饰者者
*/
private Battercake battercake;
public LettuceDecorator(Battercake battercake) {
this.battercake = battercake;
}
@Override
public String cook() {
return battercake.cook();
}
@Override
public int getPrice() {
return battercake.getPrice();
}
}
@Test
public void demo() {
/**
* 初始化一个基础套餐,原有套餐是不变的(不改变被装饰者原有逻辑,只是做增强&扩展)
*/
Battercake battercake = new BaseBattercake();
//加一个鸡蛋
battercake = new EggDecorator(battercake);
//再加一个香肠
battercake = new SausageDecorator(battercake);
//再加免费生菜
battercake = new LettuceDecorator(battercake);
System.out.println(battercake.cook());
System.out.println(battercake.getPrice());
}
用户登录 重构
原有登录方式为 用户名+密码,现在需要增加第三方登录方式,微信 qq 手机号,在不改动原有逻辑的情况下,遵循开闭原则完成此需求。
原有登录服务LoginService
/**
* 原有登录方式:根据用户名登录
*
* @author Bai
* @date 2020/12/5 23:59
*/
public interface LoginService {
/**
* 根据用户名登录
*
* @return
*/
boolean loginByName();
}
登录服务实现
/**
* 模拟用户根据用户名登录
*
* @author Bai
* @date 2020/12/5 16:23
*/
public class LoginServiceImpl implements LoginService {
@Override
public boolean loginByName() {
System.out.println("根据用户名登录");
return true;
}
}
增加装饰者LoginDecoratorService,继承LoginService,持有被装饰对象的所有方法
/**
* 装饰者类
*
* @author Bai
* @date 2020/12/6 0:05
*/
public interface LoginDecoratorService extends LoginService {
/**
* QQ登录
*
* @return
*/
boolean qqLogin();
/**
* 微信登录
*
* @return
*/
boolean weixinLogin();
/**
* 手机号登录
*
* @return
*/
boolean mobileLogin();
}
装饰者实现
/**
* 装饰者实现
*
* @author Bai
* @date 2020/12/6 0:08
*/
public class LoginDecoratorServiceImpl implements LoginDecoratorService {
/**
* 持有被装饰者引用
*/
private LoginService loginService;
public LoginDecoratorServiceImpl(LoginService loginService) {
this.loginService = loginService;
}
@Override
public boolean qqLogin() {
System.out.println("QQ登录");
return true;
}
@Override
public boolean weixinLogin() {
System.out.println("微信登录");
return true;
}
@Override
public boolean mobileLogin() {
System.out.println("手机号登录");
return true;
}
/**
* 使用被装饰者原有逻辑
*/
@Override
public boolean loginByName() {
return loginService.loginByName();
}
}
测试
@Test
public void loginTest() {
//原有登录方式:根据用户名登录,不改变原有逻辑上增加 开闭原则
LoginService loginService = new LoginServiceImpl();
//新增第三方登录方式
LoginDecoratorService decoratorService = new LoginDecoratorServiceImpl(loginService);
decoratorService.loginByName();
decoratorService.qqLogin();
}
装饰者模式最本质的特征是讲原有类的附加功能抽离出来,简化原有类的逻辑。通过这 样两个案例,我们可以总结出来,其实抽象的装饰者是可有可无的,具体可以根据业务 模型来选择。
源码TODO
装饰器模式在源码中也应用得非常多,在 JDK 中体现最明显的类就是 IO 相关的类,如 BufferedReader、InputStream、OutputStream
参考资料
咕泡VIP架构师课程