Java經(jīng)典設(shè)計(jì)模式之裝飾器模式解析
裝飾器模式
裝飾器模式主要解決繼承關(guān)系過(guò)于復(fù)雜的問(wèn)題,通過(guò)組合來(lái)替代繼承。
指在不改變現(xiàn)有對(duì)象結(jié)構(gòu)的情況下,動(dòng)態(tài)地給該對(duì)象增加一些職責(zé)(即增加其額外功能)的模式,裝飾器模式提供了比繼承更有彈性的替代方案將功能附加到對(duì)象上。
因此,裝飾器模式的核心功能是功能擴(kuò)展,使用裝飾器模式可以透明且動(dòng)態(tài)的擴(kuò)展類(lèi)的功能。
裝飾器模式還有一個(gè)特點(diǎn),那就是可以對(duì)原始類(lèi)嵌套使用多個(gè)裝飾器。
為了滿(mǎn)足這個(gè)應(yīng)用場(chǎng)景,在設(shè)計(jì)的時(shí)候,裝飾器類(lèi)需要跟原始類(lèi)繼承相同的抽象類(lèi)或者接口。
應(yīng)用場(chǎng)景:
- 用于擴(kuò)展一個(gè)類(lèi)的功能,或者給一個(gè)類(lèi)添加附加職責(zé)
- 動(dòng)態(tài)的給一個(gè)對(duì)象添加功能,這些功能可以再動(dòng)態(tài)的被撤銷(xiāo)
- 需要為一批平行的兄弟類(lèi)進(jìn)行改裝或者加裝功能
UML類(lèi)圖

由上圖可知,裝飾器模式主要包含了4個(gè)角色;
- 抽象組建(Component):可以是一個(gè)接口或者抽象類(lèi),充當(dāng)被裝飾類(lèi)的原始對(duì)象,規(guī)定了被裝飾對(duì)象的行為
- 具體組件(ConcreteComponent):實(shí)現(xiàn)/繼承Component的一個(gè)具體對(duì)象,即被裝飾對(duì)象
- 抽象裝飾器(Decorator):通用的裝飾ConcreteComponent的裝飾器,其內(nèi)部必然有一個(gè)屬性指向Component,其實(shí)現(xiàn)一般是一個(gè)抽象類(lèi),主要為了讓其子類(lèi)按照其構(gòu)造形式傳入一個(gè)Component,這是強(qiáng)制的通用行為。如果系統(tǒng)中裝飾邏輯單一,則并不需要實(shí)現(xiàn)許多裝飾器,可以直接省略該類(lèi),而直接實(shí)現(xiàn)一個(gè)具體裝飾器即可
- 具體裝飾器(ConcreteDecorator):Decorator的具體實(shí)現(xiàn)類(lèi),理論上,每個(gè)ConcreteDecorator都擴(kuò)展了Component對(duì)象的一種功能
裝飾器模式的實(shí)現(xiàn)原理就是,讓裝飾器實(shí)現(xiàn)與被裝飾類(lèi)相同的接口,使得裝飾器與被擴(kuò)展類(lèi)類(lèi)型一致,并在構(gòu)造函數(shù)中傳入該接口對(duì)象,然后再實(shí)現(xiàn)這個(gè)接口的被 包裝類(lèi) 屬于同一類(lèi)型,且構(gòu)造函數(shù)的參數(shù)為其實(shí)現(xiàn)接口類(lèi),因此裝飾器模式具備嵌套擴(kuò)展功能,這樣就能使用裝飾器模式一層一層的對(duì)底層被包裝類(lèi)進(jìn)行功能擴(kuò)展了
通用寫(xiě)法
public class Client {
public static void main(String[] args) {
ConcreteComponent c1 = new ConcreteComponent();
ConcreteDecoratorA decoratorA = new ConcreteDecoratorA(c1);
decoratorA.operation();
System.out.println("---------------------------");
ConcreteDecoratorB decoratorB = new ConcreteDecoratorB(c1);
decoratorB.operation();
System.out.println("---------------------------");
ConcreteDecoratorB decoratorB1 = new ConcreteDecoratorB(decoratorA);
decoratorB1.operation();
}
static abstract class Component{
public abstract void operation();
}
static class ConcreteComponent extends Component{
@Override
public void operation() {
System.out.println("處理業(yè)務(wù)邏輯??!");
}
}
static abstract class Decorator extends Component{
protected Component component;
public Decorator(Component component){
this.component = component;
}
public void operation(){
//轉(zhuǎn)發(fā)請(qǐng)求給組建對(duì)象,可以在轉(zhuǎn)發(fā)前后執(zhí)行一些附加動(dòng)作
component.operation();
}
}
static class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component) {
super(component);
}
private void operationFirst(){
System.out.println("ConcreteDecoratorA裝飾operationFirst");
}
private void operationLast(){
System.out.println("ConcreteDecoratorA裝飾operationLast");
}
public void operation(){
operationFirst();
super.operation();
operationLast();
}
}
static class ConcreteDecoratorB extends Decorator{
public ConcreteDecoratorB(Component component) {
super(component);
}
private void operationFirst(){
System.out.println("ConcreteDecoratorB裝飾operationFirst");
}
private void operationLast(){
System.out.println("ConcreteDecoratorB裝飾operationLast");
}
public void operation(){
operationFirst();
super.operation();
operationLast();
}
}
}示例:使用裝飾器模式解決煎餅加碼問(wèn)題
下面用代碼來(lái)模擬給煎餅加碼的業(yè)務(wù)場(chǎng)景,先來(lái)看不用裝飾器模式的情況。
首先創(chuàng)建一個(gè)煎餅Battercake類(lèi)
public class Battercake {
protected String getMsg(){
return "煎餅";
}
public int getPrice(){
return 5;
}
}然后創(chuàng)建一個(gè)加雞蛋的煎餅BattercakeWithEgg類(lèi)
public class BattercakeWithEgg extends Battercake{
protected String getMsg(){
return super.getMsg() + "+ 1個(gè)雞蛋";
}
public int getPrice(){
return super.getPrice() + 1;
}
}在創(chuàng)建一個(gè)既加雞蛋又加香腸的BattercakeWithEggAndSausage類(lèi)
public class BattercakeWithEggAndSausage extends BattercakeWithEgg{
protected String getMsg(){
return super.getMsg() + "+ 1根香腸";
}
public int getPrice(){
return super.getPrice() + 2;
}
}最后編寫(xiě)客戶(hù)端測(cè)試代碼
public class ClientTest {
public static void main(String[] args) {
Battercake battercake = new Battercake();
System.out.println(battercake.getMsg() + ",總價(jià)格" + battercake.getPrice());
BattercakeWithEgg battercakeWithEgg = new BattercakeWithEgg();
System.out.println(battercakeWithEgg.getMsg() + ",總價(jià)格" + battercakeWithEgg.getPrice());
BattercakeWithEggAndSausage battercakeWithEggAndSausage = new BattercakeWithEggAndSausage();
System.out.println(battercakeWithEggAndSausage.getMsg() + ",總價(jià)格" + battercakeWithEggAndSausage.getPrice());
}
}運(yùn)行結(jié)果如下:
煎餅,總價(jià)格5
煎餅+ 1個(gè)雞蛋,總價(jià)格6
煎餅+ 1個(gè)雞蛋+ 1根香腸,總價(jià)格8
運(yùn)行結(jié)果沒(méi)有問(wèn)題。
但是,如果用戶(hù)需要一個(gè)加2個(gè)雞蛋和1根香腸的煎餅,則用現(xiàn)在的類(lèi)結(jié)構(gòu)是創(chuàng)建不出來(lái)的,也無(wú)法自動(dòng)計(jì)算出價(jià)格,除非再創(chuàng)建一個(gè)類(lèi)做定制。
如果需求在變,那么一直加定制顯然是不科學(xué)的
下面用裝飾器模式來(lái)解決上面的問(wèn)題。首先創(chuàng)建一個(gè)煎餅的抽象Battercake類(lèi)。
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int getPrice();
}創(chuàng)建一個(gè)基本的煎餅(或者叫基礎(chǔ)套餐)BaseBattercake.
public class BaseBattercake extends Battercake{
@Override
protected String getMsg() {
return "煎餅";
}
@Override
protected int getPrice() {
return 5;
}
}然后創(chuàng)建一個(gè)擴(kuò)張?zhí)撞偷某橄笱b飾器BattercakeDecotator類(lèi)
public abstract class BattercakeDecotator extends Battercake{
private Battercake battercake;
public BattercakeDecotator(Battercake battercake){
this.battercake = battercake;
}
protected abstract void doSomething();
protected String getMsg(){
return this.battercake.getMsg();
}
protected int getPrice(){
return this.battercake.getPrice();
}
}接著創(chuàng)建雞蛋裝飾器EggDecorator
public class EggDecorator extends BattercakeDecotator{
public EggDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected void doSomething() {
}
protected String getMsg(){
return super.getMsg() + "+1個(gè)雞蛋";
}
protected int getPrice(){
return super.getPrice() + 1;
}
}創(chuàng)建香腸裝飾器SausageDecorator類(lèi)
public class SausageDecorator extends BattercakeDecotator{
public SausageDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected void doSomething() {
}
protected String getMsg(){
return super.getMsg() + "+1根香腸";
}
protected int getPrice(){
return super.getPrice() + 2;
}
}在編寫(xiě)客戶(hù)端測(cè)試代碼
public class Client {
public static void main(String[] args) {
//買(mǎi)一個(gè)煎餅
Battercake battercake;
battercake = new BaseBattercake();
//煎餅有點(diǎn)小,想再加1個(gè)雞蛋
battercake = new EggDecorator(battercake);
battercake = new EggDecorator(battercake);
battercake = new SausageDecorator(battercake);
System.out.println(battercake.getMsg() + ",總價(jià): " + battercake.getPrice());
}
}運(yùn)行結(jié)果如下圖所示:
煎餅+1個(gè)雞蛋+1個(gè)雞蛋+1根香腸,總價(jià): 9
裝飾器模式與代理模式區(qū)別
從代理模式的UML類(lèi)圖和通用代碼實(shí)現(xiàn)上看,代理模式與裝飾器模式幾乎一摸一樣。
代理模式的Subject對(duì)應(yīng)裝飾器模式的Component,代理模式的RealSubject對(duì)應(yīng)裝飾器模式的Concrete Component,代理模式的Proxy對(duì)應(yīng)的裝飾器模式的Decorator。
確實(shí),從代碼實(shí)現(xiàn)上看,代理模式的確與裝飾器模式是一樣的,但是這兩種設(shè)計(jì)模式多面向的功能擴(kuò)展面是不一樣的。
裝飾器模式強(qiáng)調(diào)自身功能的擴(kuò)展。
Decorator所做的就是增強(qiáng)ConcreteComponent的功能,主體對(duì)象為ConcreteComponent,著重類(lèi)功能的變化。
代理模式強(qiáng)調(diào)對(duì)代理過(guò)程的控制。
Proxy完全掌握對(duì)RealSubject的訪(fǎng)問(wèn)控制,因此,Proxy可以決定對(duì)RealSubject進(jìn)行功能擴(kuò)展,功能縮減甚至功能散失,主體對(duì)象為Proxy。
裝飾器模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 裝飾器是繼承的有力補(bǔ)充,比繼承靈活,在不改變?cè)袑?duì)象的情況下,動(dòng)態(tài)地給一個(gè)對(duì)象擴(kuò)展功能,即插即用。
- 通過(guò)使用不同裝飾類(lèi)及這些裝飾類(lèi)的排列組合,可以實(shí)現(xiàn)不同效果
- 裝飾器模式完全遵守開(kāi)閉原則
缺點(diǎn):
- 會(huì)出現(xiàn)更多的代碼,更多的類(lèi),增加程序的復(fù)雜性
- 動(dòng)態(tài)裝飾在多層裝飾時(shí)會(huì)更復(fù)雜
到此這篇關(guān)于Java經(jīng)典設(shè)計(jì)模式之裝飾器模式解析的文章就介紹到這了,更多相關(guān)Java裝飾器模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
Java刪除二叉搜索樹(shù)最大元素和最小元素的方法詳解
使用Java實(shí)現(xiàn)一個(gè)能保留計(jì)算過(guò)程的計(jì)算器
SpringBoot如何訪(fǎng)問(wèn)html和js等靜態(tài)資源配置
SpringBoot實(shí)現(xiàn)異步事件驅(qū)動(dòng)的方法
JavaWeb表單注冊(cè)界面的實(shí)現(xiàn)方法
詳解簡(jiǎn)單基于spring的redis配置(單機(jī)和集群模式)
Mac使用Idea配置傳統(tǒng)SSM項(xiàng)目(非maven項(xiàng)目)
Java設(shè)計(jì)模式中代理模式應(yīng)用詳解

