情景
假设,不久前你在饿了么开了一家面店,只卖一种面既原味面,但生意却很火爆。昨天,你查看店铺的留言时发现:有很多客户要求增加鸡蛋和番茄等配菜。
为了不流失客户,你决定通过继承的方式新增鸡蛋面和番茄面,来满足客户需求。扩展后的菜单如下:
新品推出后,得到了部份客户的一致好评,但也有给差评的。差评的原因大多都是因为无法组合配菜,其中一条差评留言是:无法下单两个鸡蛋的番茄面。
为了不影响店铺的口碑,你决定再次通过继承新增番茄鸡蛋面和两个鸡蛋的番茄面。菜单如下:
问题
显然,继承将功能(配菜)和对象(面)静态的绑定在了一起,使原本可选的功能失去了动态组合的灵活性,导致无法动态地组合已有的功能来扩展对象的功能。
如果可选的功能(配菜)很多,且可以相互组合;那么通过继承的方式会导致类膨胀,代码重复、难以维护等问题。所以,这时我们应该考虑另一种更灵活的方式——装饰器模式。
方案
avatar装饰器模式通过一个独立类即装饰器包装对象的方式,给对象扩展新功能。
装饰器即对象的功能,它和对象之间是一种组合关系而非继承关系,且装饰器之间可嵌套。
装饰器实现了和原对象一致的接口,所以它可以替原对象接收客户请求,并在转发请求前后对其功能进行扩展。
public class Client {
public static void main(String[] args){
//制作二个鸡蛋的番茄鸡蛋面
//原味面
OriginalNoddles noddles = OriginalNoddles();
//第一个鸡蛋面装饰器
EggDecorator eggDecorator = new EggDecorator(noddles);
//第二个鸡蛋面装饰器
EggDecorator eggDecoratorTwo = new EggDecorator(eggDecorator);
//番茄装饰器
TomatoDecorator noddlesWithTwoEggAndTomato = new TomatoDecorator(eggDecoratorTwo);
}
}
结构
avatar抽象构件(Component):定义了具体构件和抽象装饰器需要实现的接口。
具体构件(ConcreteComponent):被包装的原始对象,实现了Component接口。
抽象装饰器(Decorator):抽象类,持有一个指向被包装对象的引用。
具体装饰器(ConcreteDecorator):实现了具体的扩展功能,在被包装的对象方法调用前后进行功能扩展。
第一步:创建抽象构件。
public interface Component {
public void operation();
}
第二步:创建具体构件。
public class ConcreteComponent implements Component{
@Override
public void operation() {
System.out.println("the original operation");
}
}
第三步:创建抽象装饰器,并实现抽象构件接口。
public abstract class Decorator implements Component{
private final Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
this.component.operation();
}
}
第四步:创建具体装饰器并继承抽象装饰器。
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
additionalFunction();
}
protected void additionalFunction(){
System.out.println("this is an additional functionA ");
}
}
第五步:使用具体装饰器包装具体构件。
public class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
additionalFunction();
}
protected void additionalFunction(){
System.out.println("this is an additional functionA ");
}
}
第六步:使用具体装饰器扩展对象的功能。
public class Client2 {
public static void main(String[] args) {
//被装饰者
Component p = new ConcreteComponent();
//使用装饰器A包装对象p
Component a = new ConcreteDecoratorA(p);
//使用装饰器B包装对象a
Component b = new ConcreteDecoratorB(a);
//执行操作
b.operation();
}
}
应用
假设,我们有一个发送短信的服务类,它可以根据具体的短信通道来发送验证码和活动通知。短信通道我们接入了两个,一个是腾讯短信通道,一个是阿里短信通道。短信服务类如下:
public enum Channel {
ALI,TENXUN
}
public interface MessageService {
public String send(Message message,Channel channel);
}
public class SMSService implements MessageService{
@Override
public String send(Message message, Channel channel) {
//......
if(Channel.ALI.equals(channel)){
aliChannel.send(message);
}else if(Channel.TENXUN.equals(channel)){
tenxunChannel.send(message);
}
//......
//0001表示对方服务异常
return "0001";
}
}
现在,我们希望给短信服务扩展一个功能:当阿里或腾讯的短信服务因异常收不到我们发送的短信时,及时使用另一个短信通道重试一次。
第一步:创建抽象的消息服务装饰器MessageServiceDecorator。
public abstract class MessageServiceDecorator implements MessageService{
private final MessageService messageService;
public MessageServiceDecorator(MessageService messageService){
this.messageService = messageService;
}
@Override
public String send(Message message, Channel channel) {
return messageService.send(message,channel);
}
}
第二步:创建具体装饰类重试消息服务RetryMessageService。
public class RetryMessageService extends MessageServiceDecorator{
public RetryMessageService(MessageService messageService) {
super(messageService);
}
@Override
public String send(Message message, Channel channel) {
String result = super.send(message, channel);
//当前短信通道异常,切换另一个通道
if("0001".equals(result)){
Channel anotherChannel = Channel.ALI;
if(anotherChannel==channel){
anotherChannel=Channel.TENXUN;
}
return super.send(message, anotherChannel);
}
return result;
}
}
第三步:客户类使用带重试功能的短信服务发送短信,使用没有被装饰的短信服务发送营销活动短信。
public class Client {
public static void main(String[] args){
SMSService smsService = new SMSService();
//发送营销短信
smsService.send(new Message(),Channel.ALI);
//用重试装饰器包装短信服务对象
RetryMessageService retryMessageService = new RetryMessageService(smsService);
retryMessageService.send(new Message(),Channel.ALI);
}
}
通过装饰器我们就给短信服务添加了一个可选的扩展功能"切换通道重试",其它的功能如黑名单、频次控制等都可以用同样的方式进行扩展。
网友评论