本篇文章,來解讀《大話設(shè)計模式》的第6章——裝飾模式。并通過C++代碼實現(xiàn)實例代碼的功能。
注:第3~5章講的是設(shè)計模式中的一些原則(第3章:單一職責(zé)原則;第4章:開放-封閉原則;第5章:依賴倒轉(zhuǎn)原則和里氏替換原則),這些原則在設(shè)計模式中用到時會提及,暫不做專門解讀。
1 裝飾器模式
裝飾模式,或稱裝飾器模式(Decorator),動態(tài)地給一個對象添加一些額外的職責(zé),就增加功能來說,裝飾模式比生成子類更加靈活
我們在給對象增加功能時,一種做法是再設(shè)計一個子類來繼續(xù)父類,然后給子類添加額外的功能,另一種做法就是通過裝飾模式,設(shè)計單獨用于裝飾功能的類,通過“包裝”的形式動態(tài)給某個對象增加功能。
2 穿搭衣服實例
題目:用控制臺程序,寫可以給人搭配衣服的代碼
2.1 版本一
版本一的代碼,僅定義了一個Person類,提供6種不同服飾的裝扮接口,以及1個名字展示接口。
2.1.1 Person類
Person類的代碼如下,維護一個人的名字,然后是6種服飾的穿衣接口,就是加一句打印,最后Show接口顯示人的名字。
class Person
{
public:
Person(std::string name)
{
m_name = name;
}
void WearTShirts()
{
printf("大T恤 ");
}
void WearBigTrouser()
{
printf("垮褲 ");
}
void WearSneakrs()
{
printf("破球鞋 ");
}
void WearSuit()
{
printf("西裝 ");
}
void WearTie()
{
printf("領(lǐng)帶 ");
}
void WearLeatherShoes()
{
printf("皮鞋 ");
}
void Show()
{
printf("裝扮的%s", m_name.c_str());
}
private:
std::string m_name;
};
2.1.2 主函數(shù)
主函數(shù)的邏輯如下,先實例化一個名為"小菜"的Person對象,然后依次調(diào)用穿衣接口,最后調(diào)用展示接口:
#include <iostream>
int main()
{
Person xc = Person("小菜");
printf("n第一種裝扮:");
xc.WearTShirts();
xc.WearBigTrouser();
xc.WearSneakrs();
xc.Show();
printf("n第二種裝扮:");
xc.WearSuit();
xc.WearTie();
xc.WearLeatherShoes();
xc.Show();
printf("n");
return 0;
}
代碼運行效果如下:
版本一這種方式,雖然功能實現(xiàn)了,但如果想要增加裝扮,就需要修改Person類了,這違反了面向?qū)ο笤O(shè)計中的開放-封閉原則。
開放-封閉原則:是指軟件實體(類、模塊、函數(shù)等等)應(yīng)該可以擴展,但是不可修改。
換句話說:
開放:對擴展開放,當需要增加新功能時,通過代碼擴展的方式(如增加新的類)實現(xiàn)封閉:對修改封閉,當需要增加新功能時,盡量避免對原有代碼的修改
下面來看版本二如何實現(xiàn)。
2.2 版本二
版本二是將各種服飾單獨封裝了起來,并繼承自服飾抽象類,將服飾類與人類進行了分離,類圖如下:
這樣,后續(xù)需要增加服飾時,只需要增加對應(yīng)的具體服飾類,而不會影響其它已有的服飾類的代碼。
2.2.1 Person類與服飾類
Person類與服飾類的代碼如下,Person類只維護一個人的名字,并通過Show接口顯示人的名字。服飾類的Show接口用于顯示服飾的名稱,具體顯示的內(nèi)容由具體服飾類的Show接口實現(xiàn),也是打印出服飾的名字。
// Person類
class Person
{
public:
Person(std::string name)
{
m_name = name;
}
void Show()
{
printf("裝扮的%s", m_name.c_str());
}
private:
std::string m_name;
};
// 服飾類
class Finery
{
public:
virtual void Show(){};
};
// 各種服飾子類
class TShirts : public Finery
{
public:
void Show()
{
printf("大T恤 ");
}
};
class BigTrouser : public Finery
{
public:
void Show()
{
printf("垮褲 ");
}
};
class Sneakrs : public Finery
{
public:
void Show()
{
printf("破球鞋 ");
}
};
class Suit : public Finery
{
public:
void Show()
{
printf("西裝 ");
}
};
class Tie : public Finery
{
public:
void Show()
{
printf("領(lǐng)帶 ");
}
};
class LeatherShoes : public Finery
{
public:
void Show()
{
printf("皮鞋 ");
}
};
2.2.2 主函數(shù)
主函數(shù)的邏輯如下,先實例化一個名為"小菜"的Person對象,
然后依次實例化具體要裝扮的服飾類并調(diào)用對應(yīng)的展示接口,
最后調(diào)用Person的展示接口:
int main()
{
Person xc = Person("小菜"); //先實例化一個名為"小菜"的Person對象
printf("n第一種裝扮:");
TShirts dtx; //依次實例化具體要裝扮的服飾類
BigTrouser kk;
Sneakrs pqx;
dtx.Show(); //調(diào)用對應(yīng)的展示接口
kk.Show();
pqx.Show();
xc.Show(); //最后調(diào)用Person的展示接口
printf("n第二種裝扮:");
Suit xz;
Tie ld;
LeatherShoes px;
xz.Show();
ld.Show();
px.Show();
xc.Show();
printf("n");
return 0;
}
代碼運行效果如下:
版本二中,Rerson類和服飾類是完全獨立的,搭配衣服的過程也只是一個一個將對應(yīng)詞打印出來。下面來看版本三。
2.3 版本三
版本三用到了本篇要講的裝飾器模式。
在本例中,服飾就是裝飾類,具體的服飾,T恤、球鞋這些是具體的裝飾類,而人就是要被裝飾的組件,那是組件Component還是具體組件ConcreateComponet呢?
本例中,只有一個ConcreateComponet具體組件類,就沒必要單獨建立一個Component組件類,可以把兩者職責(zé)合并成一個類,也就是只使用ConcreateComponet類表示Person類。
2.3.1 Person類與服飾類
Person類與服飾類的代碼如下:
// Person類
class Person
{
public:
Person(){};
Person(std::string name)
{
m_name = name;
}
// 父類的函數(shù)
virtual void Show()
{
printf("裝扮的%s", m_name.c_str());
}
private:
std::string m_name;
};
// 服飾類(Decorator)
class Finery : public Person
{
protected:
Person *m_pComponent = nullptr;
public:
void Decorate(Person *pComponent)
{
m_pComponent = pComponent;
}
// 子類的函數(shù)
void Show()
{
if (m_pComponent != nullptr)
{
m_pComponent->Show();
}
}
};
// 具體服飾類(ConcreateDecorator)
class TShirts : public Finery
{
public:
void Show()
{
printf("大T恤 ");
Finery::Show();
}
};
class BigTrouser : public Finery
{
public:
void Show()
{
printf("垮褲 ");
Finery::Show();
}
};
class Sneakrs : public Finery
{
public:
void Show()
{
printf("破球鞋 ");
Finery::Show();
}
};
class Suit : public Finery
{
public:
void Show()
{
printf("西裝 ");
Finery::Show();
}
};
class Tie : public Finery
{
public:
void Show()
{
printf("領(lǐng)帶 ");
Finery::Show();
}
};
class LeatherShoes : public Finery
{
public:
void Show()
{
printf("皮鞋 ");
Finery::Show();
}
};
2.3.2 主函數(shù)
主函數(shù)的邏輯如下,先實例化一個名為"小菜"的Person對象,
然后再依次實例化要裝扮的服飾類對象,接著通過“包裝”的方式,后一個對象對前一個對象進行包裝,
最后調(diào)用最終包裝后對象的展示接口:
int main()
{
Person *xc = new Person("小菜");
printf("n第一種裝扮:");
TShirts *dtx = new TShirts();
BigTrouser *kk = new BigTrouser();
Sneakrs *pqx = new Sneakrs();
dtx->Decorate(xc); // 裝飾過程:先用大褲衩裝飾小菜
kk->Decorate(dtx); // 再用垮褲裝飾穿了大褲衩的小菜
pqx->Decorate(kk); // 再用破球鞋裝飾穿了大褲衩和垮褲的小菜
pqx->Show(); // 最后調(diào)用最外層的裝飾對象的展示接口
printf("n第二種裝扮:");
Suit *xz = new Suit();
Tie *ld = new Tie();
LeatherShoes *px = new LeatherShoes();
xz->Decorate(xc); // 裝飾過程:先用西裝裝飾小菜
ld->Decorate(xz); // 再用領(lǐng)帶裝飾穿了西裝的小菜
px->Decorate(ld); // 再皮鞋裝飾穿了西裝和領(lǐng)帶的小菜
px->Show(); // 最后調(diào)用最外層的裝飾對象的展示接口
printf("n");
return 0;
}
代碼運行效果如下:
裝飾模式的優(yōu)缺點與適用場景:
3 總結(jié)
本篇介紹了設(shè)計模式中的裝飾模式,并通過給人裝扮的實例,使用C++編程,來演示裝飾模式的使用。