來源:《圖說設(shè)計(jì)模式》
結(jié)構(gòu)型模式(Structural Pattern)描述如何將類或者對象結(jié)合在一起形成更大的結(jié)構(gòu)仁锯,就像搭積木业崖,可以通過 簡單積木的組合形成復(fù)雜的蓄愁、功能更為強(qiáng)大的結(jié)構(gòu)腻要。
結(jié)構(gòu)型模式可以分為類結(jié)構(gòu)型模式和對象結(jié)構(gòu)型模式:
類結(jié)構(gòu)型模式關(guān)心類的組合,一般只存在繼承關(guān)系和實(shí)現(xiàn)關(guān)系涝登。
對象結(jié)構(gòu)型模式關(guān)心類與對象的組合雄家,一般關(guān)系為關(guān)聯(lián)關(guān)系。 根據(jù)“合成復(fù)用原則”胀滚,在系統(tǒng)中盡量使用關(guān)聯(lián)關(guān)系來替代繼承關(guān)系趟济,因此大部分類結(jié)構(gòu)型模式都是對象結(jié)構(gòu)型模式咽笼。
包含模式
適配器模式(Adapter)
重要程度:4橋接模式(Bridge)
重要程度:3組合模式(Composite)
重要程度:4裝飾模式(Decorator)
重要程度:3外觀模式(Facade)
重要程度:5享元模式(Flyweight)
重要程度:1代理模式(Proxy)
重要程度:4
一顷编、適配器模式(Adapter)
模式動(dòng)機(jī)
適配器可以使由于接口不兼容而不能交互的類可以一起工作。類似于電源適配器的設(shè)計(jì)剑刑。
模式結(jié)構(gòu)
適配器模式包含如下角色:
Target:目標(biāo)抽象類
Adapter:適配器類
Adaptee:適配者類
Client:客戶類
適配器模式有對象適配器和類適配器兩種實(shí)現(xiàn):
-
類適配器:
類適配器媳纬,Adapter 類既繼承了 Adaptee (被適配類),也實(shí)現(xiàn)了 Target 接口
-
對象適配器:
對象適配器施掏,它不是使用多繼承或繼承再實(shí)現(xiàn)的方式钮惠,而是使用直接關(guān)聯(lián),或者稱為委托的方式七芭。
從類圖中我們也可以看到 對象適配器比起類適配器 需要修改的只不過就是 Adapter 類的內(nèi)部結(jié)構(gòu)素挽,即 Adapter 自身必須先擁有一個(gè)被適配類的對象,再把具體的特殊功能委托給這個(gè)對象來實(shí)現(xiàn)狸驳。使用對象適配器模式预明,可以使得 Adapter 類(適配類)根據(jù)傳入的 Adaptee 對象達(dá)到適配多個(gè)不同被適配類的功能,當(dāng)然耙箍,此時(shí)我們可以為多個(gè)被適配類提取出一個(gè)接口或抽象類撰糠。這樣看起來的話,似乎對象適配器模式更加靈活一點(diǎn)辩昆。
代碼分析
典型的類適配器代碼
// 已存在的阅酪、具有特殊功能、但不符合我們既有的標(biāo)準(zhǔn)接口的類
class Adaptee {
public void specificRequest() {
System.out.println("被適配類具有 特殊功能...");
}
}
// 目標(biāo)接口,或稱為標(biāo)準(zhǔn)接口
interface Target {
public void request();
}
// 具體目標(biāo)類遮斥,只提供普通功能
class ConcreteTarget implements Target {
public void request() {
System.out.println("普通類 具有 普通功能...");
}
}
// 適配器類,繼承了被適配類扇丛,同時(shí)實(shí)現(xiàn)標(biāo)準(zhǔn)接口
class Adapter extends Adaptee implements Target{
public void request() {
super.specificRequest();
}
}
典型的對象適配器代碼
// 適配器類术吗,直接關(guān)聯(lián)被適配類,同時(shí)實(shí)現(xiàn)標(biāo)準(zhǔn)接口
class Adapter implements Target{
// 直接關(guān)聯(lián)被適配類
private Adaptee adaptee;
// 可以通過構(gòu)造函數(shù)傳入具體需要適配的被適配類對象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
// 這里是使用委托的方式完成特殊功能
this.adaptee.specificRequest();
}
}
兩種方式的區(qū)別 :
- 對象適配器模式可以直接使用一個(gè)已有的Adaptee的實(shí)例來轉(zhuǎn)換接口帆精。
- 類適配器繼承了Adaptee,所以可以通過覆寫來擴(kuò)展SpecificRequest()
- 類適配器模式因?yàn)槭抢^承所以相對靜態(tài)较屿,而對象適配器模式是包含是組合相對靈活(可以通過寫adaptee子類擴(kuò)展功能)
優(yōu)點(diǎn)
將目標(biāo)類和適配者類解耦,增加了類的透明性和復(fù)用性卓练,靈活性和擴(kuò)展性都非常好隘蝎,完全符合“開閉原則”。
缺點(diǎn)
類適配器一次最多只能適配一個(gè)適配者類襟企,使用有一定的局限性嘱么。(不能多繼承)
而對象適配器雖然可以把適配者類和它的子類都適配到目標(biāo)接口,但是更改適配者的方法十分麻煩顽悼,既需要更改適配器曼振,也需要更改適配者
應(yīng)用
JDBC驅(qū)動(dòng)軟件就是一個(gè)介于JDBC接口和數(shù)據(jù)庫引擎接口之間的適配器軟件。
擴(kuò)展
當(dāng)不需要全部實(shí)現(xiàn)接口提供的方法時(shí)蔚龙,可先設(shè)計(jì)一個(gè)抽象類實(shí)現(xiàn)接口冰评,并為該接口中每個(gè)方法提供一個(gè)默認(rèn)實(shí)現(xiàn)(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實(shí)現(xiàn)需求木羹,它適用于一個(gè)接口不想使用其所有的方法的情況甲雅。因此也稱為單接口適配器模式。
二坑填、橋接模式(Bridge)
模式動(dòng)機(jī)
設(shè)想如果要繪制矩形抛人、圓形、橢圓脐瑰、正方形函匕,我們至少需要4個(gè)形狀類,但是如果繪制的圖形需要具有不同的顏色蚪黑,如紅色盅惜、綠色、藍(lán)色等忌穿,此時(shí)至少有如下兩種設(shè)計(jì)方案:
第一種設(shè)計(jì)方案是為每一種形狀都提供一套各種顏色的版本抒寂。
第二種設(shè)計(jì)方案是根據(jù)實(shí)際需要對形狀和顏色進(jìn)行組合
對于有兩個(gè)變化維度的系統(tǒng),采用方案二來進(jìn)行設(shè)計(jì)系統(tǒng)中類的個(gè)數(shù)更少掠剑,且系統(tǒng)擴(kuò)展更為方便屈芜。設(shè)計(jì)方案二即是橋接模式的應(yīng)用。橋接模式將繼承關(guān)系轉(zhuǎn)換為關(guān)聯(lián)關(guān)系,從而降低了類與類之間的耦合井佑,減少了代碼編寫量属铁。
模式定義
橋接模式(Bridge Pattern):將抽象部分與它的實(shí)現(xiàn)部分分離,使它們都可以獨(dú)立地變化躬翁。它是一種對象結(jié)構(gòu)型模式焦蘑。
模式結(jié)構(gòu)
橋接模式包含如下角色:
Abstraction:抽象類
RefinedAbstraction:擴(kuò)充抽象類
Implementor:實(shí)現(xiàn)類接口
ConcreteImplementor:具體實(shí)現(xiàn)類
代碼分析
interface Implementor{
void operationImpl();
}
abstract class Abstraction{
protected Implementor implementor;
public Abstraction(Implementor implementor){
this.implementor = implementor;
}
public void operation(){
implementor.operationImpl();
}
}
class ConcreteImplementorA implements Implementor {
@Override
public void operationImpl() {
System.out.println("具體實(shí)現(xiàn)A");
}
}
class ConcreteImplementorB implements Implementor {
@Override
public void operationImpl() {
System.out.println("具體實(shí)現(xiàn)B");
}
}
class RefinedAbstraction extends Abstraction{
public RefinedAbstraction(Implementor implementor) {
super(implementor);
}
public void otherOperation(){
System.out.println("其他操作");
}
}
public class MainTest {
public static void main(String arg[]) {
Implementor implementor = new ConcreteImplementorA();
RefinedAbstraction abstraction = new RefinedAbstraction(implementor);
abstraction.operation();
abstraction.otherOperation();
}
}
擴(kuò)展
- 橋接模式和適配器模式的區(qū)別
很多時(shí)候經(jīng)常容易把橋接模式和適配器模式弄混。那什么時(shí)候用橋接盒发,什么時(shí)候用適配器呢 例嘱?
共同點(diǎn):
橋接和適配器都是讓兩個(gè)東西配合工作
不同點(diǎn):
適配器:改變已有的兩個(gè)接口,讓他們相容宁舰。
橋接模式:分離抽象化和實(shí)現(xiàn)拼卵,使兩者的接口可以不同,目的是分離蛮艰。
所以說腋腮,如果你拿到兩個(gè)已有模塊,想讓他們同時(shí)工作壤蚜,那么你使用的適配器低葫。 如果你還什么都沒有,但是想分開實(shí)現(xiàn)仍律,那么橋接是一個(gè)選擇嘿悬。
橋接是先有橋,才有兩端的東西 適配是先有兩邊的東西水泉,才有適配器 善涨。
橋模式并不同于適配器模式,適配器模式其實(shí)是一個(gè)事后諸葛亮草则,當(dāng)發(fā)現(xiàn)以前的東西不適用了才去做一個(gè)彌補(bǔ)的措施钢拧。橋模式相對來說所做的改變比適配器模式早,它可以適用于有兩個(gè)甚至兩個(gè)以上維度的變化炕横。
橋接模式將繼承關(guān)系轉(zhuǎn)換為關(guān)聯(lián)關(guān)系源内,從而降低了類與類之間的耦合,減少了代碼編寫量份殿。
三膜钓、組合模式(Composite)
模式動(dòng)機(jī)
將對象組合成樹形結(jié)構(gòu)以表示‘部分-整體’的層次結(jié)構(gòu)。組合模式使得用戶對耽擱對象和組合對象的使用具有一致性卿嘲。
模式結(jié)構(gòu)
代碼分析
public abstract class Company {
private String name;
public Company(String name) {
this.name = name;
}
public Company() {
}
protected abstract void add(Company company);
protected abstract void remove(Company company);
protected abstract void display(int depth);
}
public class ConcreteCompany extends Company {
private List<Company> cList;
public ConcreteCompany() {
cList = new ArrayList<Company>();
}
public ConcreteCompany(String name) {
super(name);
cList = new ArrayList<Company>() ;
}
@Override
protected void add(Company company) {
cList.add(company);
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName());
for (Company c : cList) {
c.display(depth + 2);
}
}
@Override
protected void remove(Company company) {
cList.remove(company);
}
}
public class FinanceDepartment extends Company {
public FinanceDepartment(){
}
public FinanceDepartment(String name){
super(name);
}
@Override
protected void add(Company company) {
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName() ) ;
}
@Override
protected void remove(Company company) {
}
}
public class HRDepartment extends Company {
public HRDepartment(){
}
public HRDepartment(String name){
super(name);
}
@Override
protected void add(Company company) {
}
@Override
protected void display(int depth) {
StringBuilder sb = new StringBuilder("");
for (int i = 0; i < depth; i++) {
sb.append("-");
}
System.out.println(new String(sb) + this.getName() ) ;
}
@Override
protected void remove(Company company) {
}
}
public class Client {
public static void main(String[] args) {
Company root = new ConcreteCompany();
root.setName("北京總公司");
root.add(new HRDepartment("總公司人力資源部"));
root.add(new FinanceDepartment("總公司財(cái)務(wù)部"));
Company shandongCom = new ConcreteCompany("山東分公司");
shandongCom.add(new HRDepartment("山東分公司人力資源部"));
shandongCom.add(new FinanceDepartment("山東分公司賬務(wù)部"));
Company jinanCom = new ConcreteCompany("濟(jì)南辦事處");
jinanCom.add(new FinanceDepartment("濟(jì)南辦事處財(cái)務(wù)部"));
jinanCom.add(new HRDepartment("濟(jì)南辦事處人力資源部"));
shandongCom.add(jinanCom);
root.add(shandongCom);
root.display(0);
}
}
類似于樹
四颂斜、裝飾模式(Decorator)
模式動(dòng)機(jī)
一般有兩種方式可以實(shí)現(xiàn)給一個(gè)類或?qū)ο笤黾有袨椋?/p>
- 繼承機(jī)制
- 關(guān)聯(lián)機(jī)制,即將一個(gè)類的對象嵌入另一個(gè)對象中拾枣,由另一個(gè)對象來決定是否調(diào)用嵌入對象的行為以便擴(kuò)展自己的行為沃疮,我們稱這個(gè)嵌入的對象為裝飾器(Decorator)
裝飾模式可以在不需要?jiǎng)?chuàng)造更多子類的情況下盒让,將對象的功能加以擴(kuò)展。這就是裝飾模式的模式動(dòng)機(jī)司蔬。
模式定義
裝飾模式(Decorator Pattern) :動(dòng)態(tài)地給一個(gè)對象增加一些額外的職責(zé)(Responsibility)邑茄,它是一種對象結(jié)構(gòu)型模式。
模式結(jié)構(gòu)
裝飾模式包含如下角色:
Component: 抽象構(gòu)件
ConcreteComponent: 具體構(gòu)件
Decorator: 抽象裝飾類
ConcreteDecorator: 具體裝飾類
代碼分析
現(xiàn)在需要一個(gè)漢堡俊啼,主體是雞腿堡肺缕,可以選擇添加生菜、醬吨些、辣椒等等許多其他的配料搓谆,這種情況下就可以使用裝飾者模式炒辉。
//漢堡基類(被裝飾者)
public abstract class Humburger {
protected String name ;
public String getName(){
return name;
}
public abstract double getPrice();
}
//雞腿堡類(被裝飾者的初始狀態(tài)豪墅,有些自己的簡單裝飾)
public class ChickenBurger extends Humburger {
public ChickenBurger(){
name = "雞腿堡";
}
@Override
public double getPrice() {
return 10;
}
}
//配料的基類(裝飾者,用來對漢堡進(jìn)行多層裝飾黔寇,每層裝飾增加一些配料)
public abstract class Condiment extends Humburger {
public abstract String getName();
}
//生菜(裝飾者的第一層)
public class Lettuce extends Condiment {
Humburger humburger;
public Lettuce(Humburger humburger){
this.humburger = humburger;
}
@Override
public String getName() {
return humburger.getName()+" 加生菜";
}
@Override
public double getPrice() {
return humburger.getPrice()+1.5;
}
}
//辣椒(裝飾者的第二層)
public class Chilli extends Condiment {
Humburger humburger;
public Chilli(Humburger humburger){
this.humburger = humburger;
}
@Override
public String getName() {
return humburger.getName()+" 加辣椒";
}
@Override
public double getPrice() {
return humburger.getPrice(); //辣椒是免費(fèi)的哦
}
}
//測試類
public static void main(String[] args) {
Humburger humburger = new ChickenBurger();
System.out.println(humburger.getName()+" 價(jià)錢:"+humburger.getPrice());
Lettuce lettuce = new Lettuce(humburger);
System.out.println(lettuce.getName()+" 價(jià)錢:"+lettuce.getPrice());
Chilli chilli = new Chilli(humburger);
System.out.println(chilli.getName()+" 價(jià)錢:"+chilli.getPrice());
Chilli chilli2 = new Chilli(lettuce);
System.out.println(chilli2.getName()+" 價(jià)錢:"+chilli2.getPrice());
}
應(yīng)用
Java 的I/O API就是使用裝飾模式實(shí)現(xiàn)的
五偶器、外觀模式(Facade)
模式定義
外部與一個(gè)子系統(tǒng)的通信必須通過一個(gè)統(tǒng)一的外觀對象進(jìn)行,為子系統(tǒng)中的一組接口提供一個(gè)一致的界面缝裤,外觀模式定義了一個(gè)高層接口屏轰,這個(gè)接口使得子系統(tǒng)更加容易使用。外觀模式又稱為門面模式憋飞,它是一種對象結(jié)構(gòu)型模式霎苗。
模式結(jié)構(gòu)
外觀模式包含如下角色:
Facade: 外觀角色
SubSystem:子系統(tǒng)角色
代碼分析
int main()
{
Facade fa;
fa.wrapOpration();
return 0;
}
class Facade
{
public:
Facade();
virtual ~Facade();
void wrapOpration();
private:
SystemC *m_SystemC;
SystemA *m_SystemA;
SystemB *m_SystemB;
};
Facade::Facade(){
m_SystemA = new SystemA();
m_SystemB = new SystemB();
m_SystemC = new SystemC();
}
void Facade::wrapOpration(){
m_SystemA->operationA();
m_SystemB->operationB();
m_SystemC->opeartionC();
}
模式分析
據(jù)“單一職責(zé)原則”,在軟件中將一個(gè)系統(tǒng)劃分為若干個(gè)子系統(tǒng)有利于降低整個(gè)系統(tǒng)的復(fù)雜性榛做,一個(gè)常見的設(shè)計(jì)目標(biāo)是使子系統(tǒng)間的通信和相互依賴關(guān)系達(dá)到最小唁盏,而達(dá)到該目標(biāo)的途徑之一就是引入一個(gè)外觀對象,它為子系統(tǒng)的訪問提供了一個(gè)簡單而單一的入口检眯。
外觀模式也是“迪米特法則”的體現(xiàn)厘擂,通過引入一個(gè)新的外觀類可以降低原有系統(tǒng)的復(fù)雜度,同時(shí)降低客戶類與子系統(tǒng)類的耦合度锰瘸。
外觀類將客戶端與子系統(tǒng)的內(nèi)部復(fù)雜性分隔開刽严,使得客戶端只需要與外觀對象打交道,而不需要與子系統(tǒng)內(nèi)部的很多對象打交道避凝。
優(yōu)點(diǎn)
對客戶屏蔽子系統(tǒng)組件舞萄,實(shí)現(xiàn)了子系統(tǒng)與客戶之間的松耦合關(guān)系
缺點(diǎn)
在不引入抽象外觀類的情況下,增加新的子系統(tǒng)可能需要修改外觀類的源代碼管削,違背了“開閉原則”鹏氧。
擴(kuò)展
一個(gè)系統(tǒng)有多個(gè)外觀類
在一個(gè)系統(tǒng)中可以設(shè)計(jì)多個(gè)外觀類,每個(gè)外觀類都負(fù)責(zé)和一些特定的子系統(tǒng)交互佩谣,向用戶提供相應(yīng)的業(yè)務(wù)功能把还。不要通過繼承一個(gè)外觀類在子系統(tǒng)中加入新的行為
外觀模式的用意是為子系統(tǒng)提供一個(gè)集中化和簡化的溝通渠道,而不是向子系統(tǒng)加入新的行為,新的行為的增加應(yīng)該通過修改原有子系統(tǒng)類或增加新的子系統(tǒng)類來實(shí)現(xiàn)吊履,不能通過外觀類來實(shí)現(xiàn)安皱。外觀模式與迪米特法則(最少知道原則)
外觀類充當(dāng)了客戶類與子系統(tǒng)類之間的“第三者”,降低了客戶類與子系統(tǒng)類之間的耦合度艇炎,外觀模式就是實(shí)現(xiàn)代碼重構(gòu)以便達(dá)到“迪米特法則”要求的一個(gè)強(qiáng)有力的武器酌伊。抽象外觀類的引入
外觀模式最大的缺點(diǎn)在于違背了“開閉原則”,當(dāng)增加新的子系統(tǒng)或者移除子系統(tǒng)時(shí)需要修改外觀類缀踪,可以通過引入抽象外觀類在一定程度上解決該問題居砖,客戶端針對抽象外觀類進(jìn)行編程。對于新的業(yè)務(wù)需求驴娃,不修改原有外觀類奏候,而對應(yīng)增加一個(gè)新的具體外觀類,由新的具體外觀類來關(guān)聯(lián)新的子系統(tǒng)對象唇敞,同時(shí)可以通過修改配置文件來達(dá)到不修改源代碼并更換外觀類的目的蔗草。
六间聊、享元模式(Flyweight)
模式定義
對象的多次復(fù)用
模式結(jié)構(gòu)
享元模式包含如下角色:
Flyweight: 抽象享元類
ConcreteFlyweight: 具體享元類
UnsharedConcreteFlyweight: 非共享具體享元類
FlyweightFactory: 享元工廠類
優(yōu)點(diǎn)
可以極大減少內(nèi)存中對象的數(shù)量蜗搔,使得相同對象或相似對象在內(nèi)存中只保存一份。
缺點(diǎn)
享元模式需要將享元對象的狀態(tài)外部化握爷,而讀取外部狀態(tài)使得運(yùn)行時(shí)間變長旷档。
應(yīng)用
享元模式在編輯器軟件中大量使用模叙,如在一個(gè)文檔中多次出現(xiàn)相同的圖片,則只需要?jiǎng)?chuàng)建一個(gè)圖片對象鞋屈,通過在應(yīng)用程序中設(shè)置該圖片出現(xiàn)的位置范咨,可以實(shí)現(xiàn)該圖片在不同地方多次重復(fù)顯示。
七谐区、代理模式(Proxy)
模式動(dòng)機(jī)
在某些情況下湖蜕,客戶不想或者不能直接引用一個(gè)對 象,此時(shí)可以通過一個(gè)稱之為“代理”的第三者來實(shí)現(xiàn) 間接引用宋列。代理對象可以在客戶端和目標(biāo)對象之間起到 中介的作用昭抒,并且可以通過代理對象去掉客戶不能看到 的內(nèi)容和服務(wù)或者添加客戶需要的額外服務(wù)。
模式定義
給某一個(gè)對象提供一個(gè)代 理炼杖,并由代理對象控制對原對象的引用灭返。它是一種對象結(jié)構(gòu)型模式。
模式結(jié)構(gòu)
代理模式包含如下角色:
Subject: 抽象主題角色
Proxy: 代理主題角色
RealSubject: 真實(shí)主題角色
擴(kuò)展
幾種常用的代理模式
- 圖片代理
一個(gè)很常見的代理模式的應(yīng)用實(shí)例就是對大圖瀏覽的控制坤邪。
用戶通過瀏覽器訪問網(wǎng)頁時(shí)先不加載真實(shí)的大圖熙含,而是通過代理對象的方法來進(jìn)行處理,在代理對象的方法中艇纺,先使用一個(gè)線程向客戶端瀏覽器加載一個(gè)小圖片怎静,然后在后臺使用另一個(gè)線程來調(diào)用大圖片的加載方法將大圖片加載到客戶端邮弹。當(dāng)需要瀏覽大圖片時(shí),再將大圖片在新網(wǎng)頁中顯示蚓聘。如果用戶在瀏覽大圖時(shí)加載工作還沒有完成腌乡,可以再啟動(dòng)一個(gè)線程來顯示相應(yīng)的提示信息。通過代理技術(shù)結(jié)合多線程編程將真實(shí)圖片的加載放到后臺來操作夜牡,不影響前臺圖片的瀏覽与纽。
遠(yuǎn)程代理:遠(yuǎn)程代理可以將網(wǎng)絡(luò)的細(xì)節(jié)隱藏起來,使得客戶端不必考慮網(wǎng)絡(luò)的存在塘装〖庇兀客戶完全可以認(rèn)為被代理的遠(yuǎn)程業(yè)務(wù)對象是局域的而不是遠(yuǎn)程的,而遠(yuǎn)程代理對象承擔(dān)了大部分的網(wǎng)絡(luò)通信工作蹦肴。
虛擬代理:當(dāng)一個(gè)對象的加載十分耗費(fèi)資源的時(shí)候僚碎,虛擬代理的優(yōu)勢就非常明顯地體現(xiàn)出來了。虛擬代理模式是一種內(nèi)存節(jié)省技術(shù)冗尤,那些占用大量內(nèi)存或處理復(fù)雜的對象將推遲到使用它的時(shí)候才創(chuàng)建听盖。
動(dòng)態(tài)代理
較為高級的代理模式胀溺,它的典型應(yīng)用就是Spring AOP裂七。