裝飾者模式
概念
動態地給一個對象添加一些額外的職責。與繼承的模式對比,裝飾者模式更為靈活。
類圖
以上共有四個角色:
抽象構件(Component)角色:給出一個抽象接口,以規范准備接收附加責任的對象。
具體構件(ConcreteComponent)角色:定義一個將要接收附加責任的類。
裝飾(Decorator)角色:持有一個構件(Component)對象的實例,並定義一個與抽象構件接口一致的接口。
具體裝飾(ConcreteDecorator)角色:負責給構件對象“貼上”附加的責任。
實例介紹
火鍋包括鍋底與配菜,有很多的組合方式,適合用裝飾者模式實現。
1.定義被裝飾對象基類GuoDi(可以是抽象類也可以是接口) 相當於Component
package com.example.demo;
public interface GuoDi {
/**
* 鍋底的價格
* @return
*/
public float cost();
/**
* 鍋底的名字
* @return
*/
public String name();
}
2.定義具體被裝飾對象(也就是各種鍋底,這里有兩種) 相當於ConcreteComponent
package com.example.demo;
public class YuanYang implements GuoDi {
/**
* 鍋底的價格
*
* @return
*/
@Override
public float cost() {
return 50.0f;
}
/**
* 鍋底的名字
*
* @return
*/
@Override
public String name() {
return "鴛鴦鍋底";
}
}
package com.example.demo;
public class XiangLa implements GuoDi {
/**
* 鍋底的價格
*
* @return
*/
@Override
public float cost() {
return 40.0f;
}
/**
* 鍋底的名字
*
* @return
*/
@Override
public String name() {
return "香辣鍋底";
}
}
3.定義裝飾者抽象類PeiCai,相當於Decorator
package com.example.demo;
public class PeiCai implements GuoDi {
private GuoDi guodi;
public PeiCai(GuoDi guodi) {
super();
this.guodi = guodi;
}
/**
* 鍋底的價格
*
* @return
*/
@Override
public float cost() {
return guodi.cost();
}
/**
* 鍋底的名字
*
* @return
*/
@Override
public String name() {
return guodi.name();
}
}
4.定義具體的裝飾者對象(這里也是定義兩個),相當於ConcreteDecorator
package com.example.demo;
public class MaLaNiuRou extends PeiCai {
public MaLaNiuRou(GuoDi guodi) {
super(guodi);
}
@Override
public float cost() {
return super.cost()+60f;
}
@Override
public String name() {
return super.name()+"+麻辣牛肉";
}
}
package com.example.demo;
public class DouFuPi extends PeiCai{
public DouFuPi(GuoDi guodi) {
super(guodi);
}
@Override
public float cost() {
return super.cost()+20.0f;
}
@Override
public String name() {
return super.name()+"+豆腐皮";
}
}
5.創建一個測試類Test
package com.example.demo;
public class Test {
public static void main(String[] args) {
//點個香辣鍋底
GuoDi guodi = new XiangLa ();
//來個麻辣牛肉
MaLaNiuRou niurou = new MaLaNiuRou(guodi);
//再來一個豆腐皮
DouFuPi douFuPi = new DouFuPi(niurou);
System.out.println("您點了"+douFuPi.name());
System.out.println("共消費"+douFuPi.cost());
}
}
基本思想
1.編寫一個類,實現與被裝飾類相同的接口,目的是他們有相同的行為。
我們做了MaLaNiuRou這個類,實現與被裝飾類XiangLa 相同的接口GuoDi,目的是他們有相同的行為:cost(),name()。
2.定義一個實例變量,引用被裝飾的對象,目的與原來的老對象進行交接。
也就是
//點個香辣鍋底
GuoDi guodi = new XiangLa ();
//來個麻辣牛肉
MaLaNiuRou niurou = new MaLaNiuRou(guodi);
3,定義構造方法。把被裝飾的對象注入進來。
public MaLaNiuRou(GuoDi guodi) {
super(guodi);
}
4.對於不需要改寫的方法,調用被裝飾對象的方法。
5.對於要改寫的方法,改寫即可。
@Override
public float cost() {
return super.cost()+60f;
}
@Override
public String name() {
return super.name()+"+麻辣牛肉";
}
優點
(1)裝飾模式與繼承關系的目的都是要擴展對象的功能,但是裝飾模式可以提供比繼承更多的靈活性。裝飾模式允許系統動態決定“貼上”一個需要的“裝飾”,或者除掉一個不需要的“裝飾”。繼承關系則不同,繼承關系是靜態的,它在系統運行前就決定了。
(2)通過使用不同的具體裝飾類以及這些裝飾類的排列組合,設計師可以創造出很多不同行為的組合。
缺點
被裝飾者一旦身份增加,作為裝飾類,也需要相應的擴展,這必然造成編碼的負擔。
這個個人覺得其實不算是缺點,與繼承相比,裝飾功能的細化必然會導致產生很多的裝飾對象。魚與熊掌不可兼得。
混淆點
與代理模式的區別
對於裝飾者模式來說,裝飾者與被裝飾者都實現同一個接口,對於代理模式來說,代理類與真實處理的類都實現同一個接口。而且都要把后者注入到前者。實現方式差別不大。
但是其中的關注點是不一樣的。
代理模式 | 偏重因自己無法完成或自己無需關心,需要他人干涉事件流程,更多的是對對象的控制。 |
---|---|
裝飾者模式 | 偏重對原對象功能的擴展,擴展后的對象仍是是對象本身。 |
也就是思想的側重點不一樣。
在我們實際業務中裝飾模式使用的案例就是IO類,經常可以看到一個流包裝之后再包裝。而代理模式的使用就有Spring AOP和Spring 事務。不過它們使用的是動態代理。因為普通的代理例如上面的例子稱為靜態代理,只能代理某一類對象,但在我們實際業務中不可能為每個類都去寫一個代理類,所以這個時候就出現了動態代理。動態代理是使用反射的方法實現的,只用傳入類的類型就可以達到增強類的方法。
應用場景
IO類(上文提到),或者遇到需要動態添加職能的場景。
組合模式
概念
組合模式對多個對象形成樹形結構以表示具有“整體部分”關系的層次結構。組合模式對葉子對象和組合對象使用具有一致性。
類圖
說明:
Component:為葉子與容器的抽象。定義操作葉子的方法,比如增刪改,獲取葉子等另外還要定義葉子的行為。
Leaf:葉子結點,沒有子節點。實現葉子具體的行為。
Composite:容器節點對象,也就是非葉子節點。內部有集合存儲子節點。可以實現訪問子節點的方法。在組合模式中,由於容器仍然包含容器容器,所以容器需要使用遞歸方法對子元素進行處理。
示例介紹
文件夾殺毒業務:
常規實現
package demo1;//為了突出核心框架代碼,我們對殺毒過程的實現進行了大量簡化
import java.util.ArrayList;
//圖像文件類
class ImageFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
public void killVirus() {
//簡化代碼,模擬殺毒
System.out.println("----對圖像文件'" + name + "'進行殺毒");
}
}
//文本文件類
class TextFile {
private String name;
public TextFile(String name) {
this.name = name;
}
public void killVirus() {
//簡化代碼,模擬殺毒
System.out.println("----對文本文件'" + name + "'進行殺毒");
}
}
//文件夾類
class Folder {
private String name;
//定義集合folderList,用於存儲Folder類型的成員
private ArrayList<Folder> folderList = new ArrayList<Folder>();
//定義集合imageList,用於存儲ImageFile類型的成員
private ArrayList<ImageFile> imageList = new ArrayList<ImageFile>();
//定義集合textList,用於存儲TextFile類型的成員
private ArrayList<TextFile> textList = new ArrayList<TextFile>();
public Folder(String name) {
this.name = name;
}
//增加新的Folder類型的成員
public void addFolder(Folder f) {
folderList.add(f);
}
//增加新的ImageFile類型的成員
public void addImageFile(ImageFile image) {
imageList.add(image);
}
//增加新的TextFile類型的成員
public void addTextFile(TextFile text) {
textList.add(text);
}
public void killVirus() {
System.out.println("****對文件夾'" + name + "'進行殺毒"); //模擬殺毒
//如果是Folder類型的成員,遞歸調用Folder的killVirus()方法
for(Object obj : folderList) {
((Folder)obj).killVirus();
}
//如果是ImageFile類型的成員,調用ImageFile的killVirus()方法
for(Object obj : imageList) {
((ImageFile)obj).killVirus();
}
//如果是TextFile類型的成員,調用TextFile的killVirus()方法
for(Object obj : textList) {
((TextFile)obj).killVirus();
}
}
}
測試類
class Client {
public static void main(String args[]) {
Folder folder1,folder2,folder3;
folder1 = new Folder("Sunny的資料");
folder2 = new Folder("圖像文件");
folder3 = new Folder("文本文件");
ImageFile image1,image2;
image1 = new ImageFile("小龍女.jpg");
image2 = new ImageFile("張無忌.gif");
TextFile text1,text2;
text1 = new TextFile("九陰真經.txt");
text2 = new TextFile("葵花寶典.doc");
folder2.addImageFile(image1);
folder2.addImageFile(image2);
folder3.addTextFile(text1);
folder3.addTextFile(text2);
folder1.addFolder(folder2);
folder1.addFolder(folder3);
folder1.killVirus();
}
}
該實現的弊端:
1.文件夾類Folder在進行多個集合存儲需要針對不同的成員進行增刪改方法,比較冗余。
2.客戶端無法對容器和葉子進行統一的處理,因為沒有進行抽象。
3.如果增加新的葉子類型,比如視頻文件,需要修改Folder類。
用組合模式進行解決:
import java.util.*;
//抽象文件類:抽象構件
abstract class AbstractFile {
public abstract void add(AbstractFile file);
public abstract void remove(AbstractFile file);
public abstract AbstractFile getChild(int i);
public abstract void killVirus();
}
//圖像文件類:葉子構件
class ImageFile extends AbstractFile {
private String name;
public ImageFile(String name) {
this.name = name;
}
public void add(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public void remove(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public AbstractFile getChild(int i) {
System.out.println("對不起,不支持該方法!");
return null;
}
public void killVirus() {
//模擬殺毒
System.out.println("----對圖像文件'" + name + "'進行殺毒");
}
}
//文本文件類:葉子構件
class TextFile extends AbstractFile {
private String name;
public TextFile(String name) {
this.name = name;
}
public void add(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public void remove(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public AbstractFile getChild(int i) {
System.out.println("對不起,不支持該方法!");
return null;
}
public void killVirus() {
//模擬殺毒
System.out.println("----對文本文件'" + name + "'進行殺毒");
}
}
//視頻文件類:葉子構件
class VideoFile extends AbstractFile {
private String name;
public VideoFile(String name) {
this.name = name;
}
public void add(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public void remove(AbstractFile file) {
System.out.println("對不起,不支持該方法!");
}
public AbstractFile getChild(int i) {
System.out.println("對不起,不支持該方法!");
return null;
}
public void killVirus() {
//模擬殺毒
System.out.println("----對視頻文件'" + name + "'進行殺毒");
}
}
//文件夾類:容器構件
class Folder extends AbstractFile {
//定義集合fileList,用於存儲AbstractFile類型的成員
private ArrayList<AbstractFile> fileList=new ArrayList<AbstractFile>();
private String name;
public Folder(String name) {
this.name = name;
}
public void add(AbstractFile file) {
fileList.add(file);
}
public void remove(AbstractFile file) {
fileList.remove(file);
}
public AbstractFile getChild(int i) {
return (AbstractFile)fileList.get(i);
}
public void killVirus() {
System.out.println("****對文件夾'" + name + "'進行殺毒"); //模擬殺毒
//遞歸調用成員構件的killVirus()方法
for(Object obj : fileList) {
((AbstractFile)obj).killVirus();
}
}
}
客戶端測試方法:
class Client {
public static void main(String args[]) {
//針對抽象構件編程
AbstractFile file1,file2,file3,file4,file5,folder1,folder2,folder3,folder4;
folder1 = new Folder("Sunny的資料");
folder2 = new Folder("圖像文件");
folder3 = new Folder("文本文件");
folder4 = new Folder("視頻文件");
file1 = new ImageFile("小龍女.jpg");
file2 = new ImageFile("張無忌.gif");
file3 = new TextFile("九陰真經.txt");
file4 = new TextFile("葵花寶典.doc");
file5 = new VideoFile("笑傲江湖.rmvb");
folder2.add(file1);
folder2.add(file2);
folder3.add(file3);
folder3.add(file4);
folder4.add(file5);
folder1.add(folder2);
folder1.add(folder3);
folder1.add(folder4);
//從“Sunny的資料”節點開始進行殺毒操作
folder1.killVirus();
}
}
改進的效果
由於
進行了抽象,所以能夠針對不同的成員進行增刪改方法。
而且如果增加新的葉子類型,比如視頻文件,不需要修改Folder類,只需要寫一個實現AbstractFile接口的視頻文件類就行了。而且各個容器相對獨立,符合開閉原則,也方便管理。
實現的基本思想
1.定義抽象類或者接口。定義操作葉子的方法,比如增刪改,獲取葉子等另外還要定義具體的業務行為。
2.分別實現葉子類與容器類,葉子類主要實現業務的行為。容器類內部維護一個抽象類的集合,並且通過這個集合實現增刪改葉子。然后通過遞歸的方式便利集合實現業務行為。
優點
1.組合模式可以無差別的使用容器對象與葉子對象。
2.新增容器與葉子都很友好。
3.與常規的操作樹的方式比,組合模式通過遞歸的方式,可以形成復雜的樹形結構,但對樹形結構的控制卻非常簡單。
缺點
新增構件只要實現抽象就能進入父構件中,不能進行約束,如果非要約束的話,需要在運行中進行類型檢查來實現。
應用場景
業務場景中具有樹形結構或者層次結構中,比如文件夾,企業的組織構成業務。