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