情景
假設,不久前你在餓了么開了一家面店浑玛,只賣一種面既原味面,但生意卻很火爆噩咪。昨天顾彰,你查看店鋪的留言時發(fā)現(xiàn):有很多客戶要求增加雞蛋和番茄等配菜。
為了不流失客戶胃碾,你決定通過繼承的方式新增雞蛋面和番茄面涨享,來滿足客戶需求。擴展后的菜單如下:
新品推出后仆百,得到了部份客戶的一致好評厕隧,但也有給差評的。差評的原因大多都是因為無法組合配菜,其中一條差評留言是:無法下單兩個雞蛋的番茄面吁讨。
為了不影響店鋪的口碑髓迎,你決定再次通過繼承新增番茄雞蛋面和兩個雞蛋的番茄面。菜單如下:
問題
顯然建丧,繼承將功能(配菜)和對象(面)靜態(tài)的綁定在了一起排龄,使原本可選的功能失去了動態(tài)組合的靈活性,導致無法動態(tài)地組合已有的功能來擴展對象的功能翎朱。
如果可選的功能(配菜)很多橄维,且可以相互組合;那么通過繼承的方式會導致類膨脹拴曲,代碼重復争舞、難以維護等問題。所以澈灼,這時我們應該考慮另一種更靈活的方式——裝飾器模式竞川。
方案
裝飾器模式通過一個獨立類即裝飾器包裝對象的方式,給對象擴展新功能叁熔。
裝飾器即對象的功能流译,它和對象之間是一種組合關系而非繼承關系,且裝飾器之間可嵌套者疤。
裝飾器實現(xiàn)了和原對象一致的接口福澡,所以它可以替原對象接收客戶請求,并在轉發(fā)請求前后對其功能進行擴展驹马。
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);
}
}
結構
抽象構件(Component):定義了具體構件和抽象裝飾器需要實現(xiàn)的接口革砸。
具體構件(ConcreteComponent):被包裝的原始對象,實現(xiàn)了Component接口糯累。
抽象裝飾器(Decorator):抽象類算利,持有一個指向被包裝對象的引用。
具體裝飾器(ConcreteDecorator):實現(xiàn)了具體的擴展功能泳姐,在被包裝的對象方法調(diào)用前后進行功能擴展效拭。
第一步:創(chuàng)建抽象構件。
public interface Component {
public void operation();
}
第二步:創(chuàng)建具體構件胖秒。
public class ConcreteComponent implements Component{
@Override
public void operation() {
System.out.println("the original operation");
}
}
第三步:創(chuàng)建抽象裝飾器缎患,并實現(xiàn)抽象構件接口。
public abstract class Decorator implements Component{
private final Component component;
public Decorator(Component component){
this.component = component;
}
@Override
public void operation() {
this.component.operation();
}
}
第四步:創(chuàng)建具體裝飾器并繼承抽象裝飾器阎肝。
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);
//執(zhí)行操作
b.operation();
}
}
應用
假設风题,我們有一個發(fā)送短信的服務類判导,它可以根據(jù)具體的短信通道來發(fā)送驗證碼和活動通知嫉父。短信通道我們接入了兩個,一個是騰訊短信通道眼刃,一個是阿里短信通道绕辖。短信服務類如下:
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";
}
}
現(xiàn)在,我們希望給短信服務擴展一個功能:當阿里或騰訊的短信服務因異常收不到我們發(fā)送的短信時擂红,及時使用另一個短信通道重試一次仪际。
第一步:創(chuàng)建抽象的消息服務裝飾器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);
}
}
第二步:創(chuàng)建具體裝飾類重試消息服務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;
}
}
第三步:客戶類使用帶重試功能的短信服務發(fā)送短信吩抓,使用沒有被裝飾的短信服務發(fā)送營銷活動短信涉茧。
public class Client {
public static void main(String[] args){
SMSService smsService = new SMSService();
//發(fā)送營銷短信
smsService.send(new Message(),Channel.ALI);
//用重試裝飾器包裝短信服務對象
RetryMessageService retryMessageService = new RetryMessageService(smsService);
retryMessageService.send(new Message(),Channel.ALI);
}
}
通過裝飾器我們就給短信服務添加了一個可選的擴展功能"切換通道重試",其它的功能如黑名單疹娶、頻次控制等都可以用同樣的方式進行擴展伴栓。