# Blog 4|設計模式 - 結構型模式 - Reference : - https://shengyu7697.github.io/archives/ - https://www.jyt0532.com/ - https://refactoringguru.cn/design-patterns/catalog ## (1) 適配器模式 Adapter - ![image](https://hackmd.io/_uploads/Hk2XYcfJ1x.png) - 想要重用某個已經存在的類,但其接口與目前系統不匹配的情況下。`Adapter` 模式允許你創建一個中間層,使新系統和舊接口之間進行協作。 ```c++= // 已存在舊的實作 class OldRectangle { public: void draw(int x1, int y1, int x2, int y2) { std::cout << "OldRectangle: (" << x1 << ", " << y1 << "), (" << x2 << ", " << y2 << ")\n"; } }; // 新的接口 class NewRectangleInterface { public: virtual void drawRectangle(int x, int y, int width, int height) = 0; virtual ~NewRectangleInterface() = default; }; // Adapter class RectangleAdapter : public NewRectangleInterface { private: OldRectangle* oldRectangle; public: RectangleAdapter(OldRectangle* rectangle) : oldRectangle(rectangle) {} void drawRectangle(int x, int y, int width, int height) override { oldRectangle->draw(x, y, x + width, y + height); } }; // client int main() { OldRectangle* oldRectangle = new OldRectangle(); NewRectangleInterface* adapter = new RectangleAdapter(oldRectangle); adapter->drawRectangle(10, 20, 30, 40); delete oldRectangle; delete adapter; return 0; } ``` ```c++= OldRectangle: (10, 20), (40, 60) ``` ## (2) 橋接模式 Bridge - Bridge Pattern 的關鍵點: - 抽象部分(Abstraction):定義一個接口,並維護一個指向實現部分的引用。 - 實現部分(Implementor):定義具體的行為,並可以有不同的具體實現類別。 - ![image](https://hackmd.io/_uploads/B1QuwXNJye.png) ```c++= // Implementor 實現部分 class Color { public: virtual void applyColor() = 0; // 純虛函數,子類別需要具體實現 virtual ~Color() = default; // 虛擬析構函數 }; class RedColor : public Color { public: void applyColor() override { std::cout << "Applying Red color." << std::endl; } }; class BlueColor : public Color { public: void applyColor() override { std::cout << "Applying Blue color." << std::endl; } }; // Abstraction 抽象部分 class Shape { protected: std::shared_ptr<Color> color; // 持有 Implementor 的引用 public: Shape(std::shared_ptr<Color> c) : color(c) {} virtual void draw() = 0; virtual ~Shape() = default; }; class Circle : public Shape { public: Circle(std::shared_ptr<Color> c) : Shape(c) {} void draw() override { std::cout << "Drawing a Circle. "; color->applyColor(); // 使用實現部分 } }; class Square : public Shape { public: Square(std::shared_ptr<Color> c) : Shape(c) {} void draw() override { std::cout << "Drawing a Square. "; color->applyColor(); // 使用實現部分 } }; int main() { // 具體實現類別 std::shared_ptr<Color> red = std::make_shared<RedColor>(); std::shared_ptr<Color> blue = std::make_shared<BlueColor>(); // 使用不同的實現來創建形狀 std::shared_ptr<Shape> circle = std::make_shared<Circle>(red); std::shared_ptr<Shape> square = std::make_shared<Square>(blue); circle->draw(); // 繪製紅色的圓 square->draw(); // 繪製藍色的正方形 return 0; } ``` ```c++= Drawing a Circle. Applying Red color. Drawing a Square. Applying Blue color. ``` ## (3) 組合模式 Composite - Component:為所有具體元件和組合類定義共同介面。 - Leaf:表示組合中的葉節點對象,葉節點沒有子節點。 - Composite:表示複雜元件,包含其他元件(可以是葉節點或其他複雜元件)。 - 讓樹狀結構中的物件與物件集合能被同樣對待 - ![image](https://hackmd.io/_uploads/S1F2X9fJ1l.png) ```c++= class Graphic { public: virtual void draw() const = 0; virtual ~Graphic() = default; }; class Circle : public Graphic { public: void draw() const override { std::cout << "Drawing Circle\n"; } }; class Rectangle : public Graphic { public: void draw() const override { std::cout << "Drawing Rectangle\n"; } }; class CompositeGraphic : public Graphic { private: std::vector<Graphic*> graphics; public: void add(Graphic* graphic) { graphics.push_back(graphic); } void draw() const override { for (const auto& graphic : graphics) { graphic->draw(); } } }; // client int main() { // 單一圖形 Circle circle; Rectangle rectangle; // 組合圖形 CompositeGraphic composite, complexComposite; composite.add(&circle); composite.add(&rectangle); complexComposite.add(&composite); complexComposite.add(&circle); std::cout << "Drawing composite graphic:\n"; complexComposite.draw(); return 0; } ``` ```c++= Drawing composite graphic: Drawing Circle Drawing Rectangle Drawing Circle ``` ## (4) 裝飾器模型 Decorator - 裝飾者模式是一種結構型設計模式,它允許你動態地為物件添加功能,而不需要修改其原有的程式碼。 - 符合開放封閉原則,也就是添加新的裝飾者不需要修改原始類別 - 符合單一職責原則,即每個裝飾者只負責一項特定的功能增強。 - [理解Python 裝飾器看這篇就夠了](https://foofish.net/python-decorator.html) - ![image](https://hackmd.io/_uploads/BJofE9G11x.png) ```c++= class Pizza { public: virtual std::string getDescription() const { return "pizza"; } virtual int cost() const { return 100; } virtual ~Pizza() = default; }; class CheeseDecorator : public Pizza { private: std::unique_ptr<Pizza> pizza; public: CheeseDecorator(std::unique_ptr<Pizza> p) : pizza(std::move(p)) {} std::string getDescription() const override { return pizza->getDescription() + " + cheese"; } int cost() const override { return pizza->cost() + 10; } }; class BaconDecorator : public Pizza { private: std::unique_ptr<Pizza> pizza; public: BaconDecorator(std::unique_ptr<Pizza> p) : pizza(std::move(p)) {} std::string getDescription() const override { return pizza->getDescription() + " + bacon"; } int cost() const override { return pizza->cost() + 20; } }; // client code int main() { std::unique_ptr<Pizza> pizza = std::make_unique<Pizza>(); std::cout << pizza.getDescription() << ": " << pizza.cost() << std::endl; pizza = std::make_unique<CheeseDecorator>(std::move(pizza)); std::cout << pizza.getDescription() << ": " << pizza.cost() << std::endl; pizza = std::make_unique<BaconDecorator>(std::move(pizza)); std::cout << pizza.getDescription() << ": " << pizza.cost() << std::endl; pizza = std::make_unique<CheeseDecorator>(std::move(pizza)); std::cout << pizza.getDescription() << ": " << pizza.cost() << std::endl; pizza = std::make_unique<BaconDecorator>(std::move(pizza)); std::cout << pizza.getDescription() << ": " << pizza.cost() << std::endl; return 0; } ``` ```c++= pizza: 100 pizza + cheese: 110 pizza + cheese + bacon: 130 pizza + cheese + bacon + cheese: 140 pizza + cheese + bacon + cheese + bacon: 160 ``` ## (5) 門面模式 Facade - 為複雜系統提供一個簡單的介面,透過一個門面類別來隱藏系統的細節 - ![image](https://hackmd.io/_uploads/B1Wiocfk1x.png) ```c++= class TV { public: void on() { std::cout << "Turning on the TV" << std::endl; } void off() { std::cout << "Turning off the TV" << std::endl; } }; class Amplifier { public: void on() { std::cout << "Turning on the amplifier" << std::endl; } void off() { std::cout << "Turning off the amplifier" << std::endl; } void setVolume(int level) { std::cout << "Setting amplifier volume to " << level << std::endl; } }; class DVDPlayer { public: void on() { std::cout << "Turning on the DVD player" << std::endl; } void off() { std::cout << "Turning off the DVD player" << std::endl; } void play(const std::string& movie) { std::cout << "Playing movie: " << movie << std::endl; } }; class HomeTheaterFacade { public: HomeTheaterFacade(TV* tv, Amplifier* amp, DVDPlayer* dvd) : tv_(tv), amp_(amp), dvd_(dvd) {} void watchMovie(const std::string& movie) { std::cout << "Get ready to watch a movie..." << std::endl; tv_->on(); amp_->on(); amp_->setVolume(5); dvd_->on(); dvd_->play(movie); } void endMovie() { std::cout << "Shutting down the home theater..." << std::endl; dvd_->off(); amp_->off(); tv_->off(); } private: TV* tv_; Amplifier* amp_; DVDPlayer* dvd_; }; // client int main() { TV tv; Amplifier amp; DVDPlayer dvd; HomeTheaterFacade homeTheater(&tv, &amp, &dvd); homeTheater.watchMovie("Inception"); std::cout << std::endl; homeTheater.endMovie(); return 0; } ``` ```c++= Get ready to watch a movie... Turning on the TV Turning on the amplifier Setting amplifier volume to 5 Turning on the DVD player Playing movie: Inception Shutting down the home theater... Turning off the DVD player Turning off the amplifier Turning off the TV ``` ## (6) 享元模式 Flyweight - 享元(Flyweight)這個術語源自拳擊界,在拳擊比賽中,Flyweight 指的是一個較輕量級(57kg以下)的選手級別 - 目的在減少物件建立時佔用的記憶體空間 - `將可共享的部分抽出,放到 factory 或實體中進行管理共享記憶體,達到了減少記憶體佔用` - ![image](https://hackmd.io/_uploads/HJzIDX4ykx.png) ```c++= class Circle { public: Circle(std::string color) : color(color) {} void draw(int x, int y, int radius) { std::cout << "Drawing " << color << " circle at (" << x << ", " << y << ") with radius " << radius << std::endl; } private: std::string color; }; class ShapeFactory { public: Circle* getCircle(std::string color) { Circle* circle = circleMap[color]; if (circle == nullptr) { circle = new Circle(color); circleMap[color] = circle; std::cout << "Creating circle of color: " << color << std::endl; } return circle; } private: std::map<std::string, Circle*> circleMap; }; int main() { ShapeFactory factory; Circle* redCircle = factory.getCircle("red"); redCircle->draw(10, 20, 30); Circle* blueCircle = factory.getCircle("blue"); blueCircle->draw(50, 60, 70); Circle* anotherRedCircle = factory.getCircle("red"); anotherRedCircle->draw(30, 30, 30); return 0; } ``` ```c++= Creating circle of color: red Drawing red circle at (10, 20) with radius 30 Creating circle of color: blue Drawing blue circle at (50, 60) with radius 70 Drawing red circle at (30, 30) with radius 30 ``` ## (7) 代理模式 Proxy - 題外話:正向代理跟反向代理 - 正向代理: 服務多用戶,傳向伺服器前預處理,伺服器不知道用戶端是誰 - 反向代理: duplicate server,proxy 整合多伺服器,用戶端不知道伺服器 - ![image](https://hackmd.io/_uploads/SJk_-jzyye.png) - 避免 client 端繞過 proxy,通常可以搭配 factory pattern 一起使用 - 功用: 做為 cache、延遲載入資源、設定存取權限 ```c++= // 定義 Subject 介面,RealSubject 和 Proxy 都會實現此介面 class Subject { public: virtual void request() const = 0; virtual ~Subject() = default; }; // RealSubject 實現了真正的功能 class RealSubject : public Subject { public: void request() const override { std::cout << "RealSubject: Handling request." << std::endl; } }; // Proxy 會控制對 RealSubject 的訪問 class Proxy : public Subject { private: mutable std::unique_ptr<RealSubject> real_subject_; // 延遲加載 bool check_access() const { std::cout << "Proxy: Checking access prior to firing a real request.\n"; return true; } void log_access() const { std::cout << "Proxy: Logging the time of request.\n"; } public: void request() const override { if (check_access()) { if (!real_subject_) { // 延遲實例化 RealSubject real_subject_ = std::make_unique<RealSubject>(); } real_subject_->request(); log_access(); } } }; // client int main() { std::cout << "Client: Executing with a real subject:\n"; RealSubject real_subject; real_subject.request(); std::cout << "\nClient: Executing with a proxy:\n"; Proxy proxy; proxy.request(); return 0; } ``` ```c++= Client: Executing with a real subject: RealSubject: Handling request. Client: Executing with a proxy: Proxy: Checking access prior to firing a real request. RealSubject: Handling request. Proxy: Logging the time of request. ```