C++ 设计模式 - 每日持续更新中

设计模式的核心 - 隔离程序的变化点和稳定点

零:面向对象设计八大原则

①:依赖倒置(Dependency Inversion Principle)

  1. 高层模块不应依赖于低层模块,二者都应依赖于抽象:这意味着程序的高层逻辑不应该直接依赖于具体实现,而是应该依赖于抽象(如接口或抽象类)。

  2. 抽象不应依赖于细节,细节应依赖于抽象:这个原则强调了抽象的独立性和重要性,具体实现应该围绕抽象进行,而不是相反。

通过遵循DIP,可以减少模块之间的耦合,提高代码的可重用性和可维护性,促进更灵活的系统架构。

②:开放封闭原则"(Open/Closed Principle, OCP)

  1. 软件实体(类、模块、函数等)应该对扩展开放,对修改封闭:这意味着你应该能够通过增加新代码来扩展现有的功能,而不是修改已有的代码。通过这样做,可以避免引入错误,并减少对现有功能的影响。

  2. 通过抽象和继承来实现扩展:开放封闭原则通常通过使用接口或抽象类来实现。通过定义通用的接口或基类,可以在不修改原有代码的情况下,创建新的实现类。

  3. 示例 :比如,在一个绘图程序中,假设有一个 Shape 接口和多个实现类如 CircleSquare。如果需要添加一个新的形状 Triangle,可以通过创建 Triangle 类来实现,而无需修改 Shape 接口或其他已存在的类。这种方式不仅符合开放封闭原则,还能提高代码的清晰度和可维护性。

实践中的好处

  • 减少缺陷:由于不需要修改现有代码,因此可以降低引入错误的风险。
  • 提高可维护性:新功能可以独立于现有代码进行开发和测试。
  • 促进重用:由于遵循抽象原则,可以更容易地重用现有的模块。

③:单一职责原则(Single Responsibility Principle, SRP)

单一职责原则的核心内容:

  1. 每个类应有且仅有一个原因引起变化:这意味着一个类应该只承担一个职责或功能。如果一个类承担了多个职责,当其中一个职责发生变化时,可能会影响到其他职责,导致代码的耦合性增加和维护难度加大。

  2. 减少耦合:将不同的功能分开,可以降低模块之间的依赖关系,使得每个模块都能独立开发和测试。

  3. 提高可维护性:遵循单一职责原则的代码更易于理解和维护。如果一个类只处理单一任务,开发者可以更快地理解其功能,并在需要修改或扩展时,更少地影响其他部分。

实践中的好处

  • 提高代码的可读性:每个类或模块的功能清晰明了,易于理解。
  • 简化测试:单一职责的类更易于单元测试,因为每个类都只包含特定的功能。
  • 增强灵活性:在添加新功能时,可以更方便地引入新类,而不需要修改现有的类。

④:里氏替换原则(Liskov Substitution Principle, LSP)

里氏替换原则的关键点:

  1. 子类应该可以替代父类:任何可以使用父类的地方,都应该能够使用其子类,而不会改变程序的预期行为。这要求子类在行为上要与父类一致。

  2. 方法的预期行为:子类应该遵循父类的约定,包括输入输出、异常处理等。子类在实现父类的方法时,应该保持父类方法的语义,不能改变方法的预期效果。

  3. 不应改变父类的行为:子类不能对父类的功能进行不必要的修改,比如改变方法的返回值类型或抛出不同的异常。

实践中的好处

  • 增强代码的可重用性:遵循 LSP 可以使子类和父类之间的关系更加清晰,从而提高代码的可重用性。
  • 提高系统的灵活性和可扩展性:当新功能需要添加时,可以通过添加新的子类来实现,而不需要修改已有的代码。
  • 降低错误风险:确保子类能够正确替代父类,降低了因不当替换而导致的错误风险。

⑤:接口隔离原则(Interface Segregation Principle, ISP)

接口隔离原则的关键点:

  1. 细化接口:将大接口分解成多个小接口,使每个接口只包含客户端所需的方法。这有助于减少客户端对不必要功能的依赖。

  2. 降低耦合:通过引入多个小接口,可以降低模块之间的耦合度,使得系统的各个部分更加独立,有助于提升系统的灵活性和可维护性。

  3. 提高可替换性:小接口的实现可以更容易被替换或修改,因为客户端只依赖于自己所需的接口,不会影响到其他模块。

实践中的好处

  • 增强灵活性:通过接口隔离,系统中的每个部分可以独立修改和扩展,而不影响其他部分。
  • 提高可维护性:每个接口都只包含必要的方法,使得代码更易于理解和维护。
  • 减少不必要的依赖:客户端只需依赖于自己关心的接口,从而降低了不必要的耦合。

⑥:优先使用对象组合而不是类继承(Favor Composition Over Inheritance)

关键概念

  1. 对象组合:通过将不同的对象组合在一起以实现复杂的功能。每个对象可以独立地负责其特定的功能,多个对象可以协作完成更复杂的任务。

  2. 类继承:通过继承父类来获得其属性和方法。子类会直接获得父类的所有功能,同时也可能会因为父类的变化而受到影响。

为什么选择组合?

  1. 减少耦合:组合允许对象之间的关系更加灵活,改变或替换一个对象通常不会影响其他对象。相比之下,继承会导致较强的耦合关系,父类的变化可能会影响所有子类。

  2. 提高灵活性:组合可以在运行时动态地改变对象的行为,而继承在编译时就已经确定,灵活性较低。通过组合,系统可以在不改变现有代码的情况下添加新功能。

  3. 避免多重继承的问题:许多编程语言(如 Java 和 C#)不支持多重继承,而组合可以轻松实现类似的效果。通过组合不同的对象,可以获得多种功能而不产生复杂的继承层次结构。

  4. 增强可维护性:通过组合,代码变得更易于理解和维护。每个对象的职责明确,便于管理和修改。

实践中的好处

  • 模块化设计:组合使得系统可以由小模块构建,便于重用和测试。
  • 灵活性和扩展性:在需求变化时,可以通过增加新组件而不是修改现有类来快速响应变化。
  • 清晰的责任分离:组合能够更好地体现单一责任原则(Single Responsibility Principle),使得每个对象都聚焦于自己特定的功能。

⑦:封装变化点(Encapsulate What Varies)

关键概念

  1. 变化点的识别:在设计软件时,需要识别出那些会随时间、需求或技术演进而变化的部分。这些变化点可以是算法、策略、配置、外部服务或数据格式等。

  2. 封装的方式:通过使用接口、抽象类、策略模式、工厂模式等设计模式来封装变化点。这些技术允许在不影响系统其他部分的情况下替换或修改变化点的实现。

  3. 降低耦合:将变化点与系统其他部分分开,从而降低各部分之间的耦合度,使得系统更加灵活和易于修改。

示例

假设我们正在设计一个处理订单的系统,系统需要根据不同的支付方式(如信用卡、PayPal、银行转账等)处理支付。如果直接在订单类中实现支付逻辑,未来添加新的支付方式时就会导致订单类变得复杂并且难以维护。

不封装变化点的设计

cpp 复制代码
class Order {
public:
    void processPayment(const std::string& paymentType) {
        if (paymentType == "CreditCard") {
            // 处理信用卡支付
            handleCreditCardPayment();
        } else if (paymentType == "PayPal") {
            // 处理 PayPal 支付
            handlePayPalPayment();
        } else if (paymentType == "BankTransfer") {
            // 处理银行转账
            handleBankTransferPayment();
        } else {
            std::cout << "Unsupported payment type." << std::endl;
        }
    }
};

在这个设计中,Order 类对支付方式的变化非常敏感。如果要添加新的支付方式,必须修改 Order 类的代码,这样会增加出错的机会并且降低代码的可维护性。

封装变化点的设计

通过使用策略模式,我们可以将支付逻辑封装在独立的支付类中:

cpp 复制代码
#include <iostream>
#include <memory>

// 支付策略接口
class PaymentStrategy {
public:
    virtual void pay(double amount) = 0; // 纯虚函数,表示支付
    virtual ~PaymentStrategy() {} // 虚析构函数
};

// 信用卡支付实现
class CreditCardPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing credit card payment of $" << amount << std::endl;
    }
};

// PayPal支付实现
class PayPalPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing PayPal payment of $" << amount << std::endl;
    }
};

// 银行转账支付实现
class BankTransferPayment : public PaymentStrategy {
public:
    void pay(double amount) override {
        std::cout << "Processing bank transfer payment of $" << amount << std::endl;
    }
};

// 订单类
class Order {
private:
    std::shared_ptr<PaymentStrategy> paymentStrategy; // 使用 shared_ptr 管理支付策略

public:
    // 构造函数接收一个 shared_ptr
    Order(std::shared_ptr<PaymentStrategy> strategy) 
        : paymentStrategy(strategy) {}

    void processPayment(double amount) {
        paymentStrategy->pay(amount); // 使用支付策略进行支付
    }
};

优势

  1. 灵活性 :新的支付方式可以通过实现 PaymentStrategy 接口轻松添加,而无需修改 Order 类的代码。

  2. 可维护性:系统的不同部分之间的耦合度降低,使得每个部分可以独立修改和测试。

  3. 可读性:通过将变化的部分抽象出来,代码的结构更加清晰,易于理解。

实践中的应用

封装变化点的原则不仅适用于支付处理的例子,还广泛适用于其他场景,如:

  • 用户界面:不同的UI实现(如Web、移动端)可以通过界面抽象化来处理。
  • 数据存储:将数据存取的具体实现封装到仓储模式中,使得数据源的变化不会影响到业务逻辑。
  • 算法:通过策略模式封装算法实现,使得算法的变化不会影响到其他逻辑。

⑧:针对接口编程,而不是针对实现编程

定义

  1. 针对接口编程(Programming to an Interface):

    • 在代码设计中,程序员依赖于抽象接口(如接口或抽象类)来定义系统的行为。这意味着代码只需要知道如何使用接口,而不关心具体的实现细节。
    • 通过使用接口,程序可以在运行时根据需要选择不同的实现,这种方式提供了更大的灵活性。
  2. 针对实现编程(Programming to an Implementation):

    • 这种方法是指代码直接依赖于具体的类和实现,而不是抽象接口。这种方式会导致代码变得紧耦合,不易于修改和扩展。
    • 如果实现发生变化,可能需要修改依赖于该实现的所有代码,这会增加维护成本。

一:Template method模式

特点:晚绑定:"我来调用你,而不是你来调用我"

模板方法模式主要由以下几个角色组成:

  • 抽象类(Abstract Class)

    • 包含一个或多个模板方法。
    • 在模板方法中定义算法的基本步骤,部分步骤为抽象方法,子类需要实现这些方法。
  • 具体类(Concrete Class)

    • 继承抽象类并实现抽象方法。
    • 具体类可以修改某些步骤的实现,从而实现算法的具体细节。

2. 模式的优点

  • 代码复用:模板方法允许在多个子类中复用相同的算法结构。
  • 控制反转:子类可以选择实现部分算法步骤,从而灵活地变化算法行为。
  • 易于扩展:添加新算法只需创建新的子类,无需修改已有代码。

3. 模式的缺点

  • 灵活性下降:由于算法的步骤是固定的,灵活性可能不如策略模式。
  • 类数增加:每个不同的算法都需要一个新的子类,可能导致类的数量增加。

代码实践

【设计模式专题之模板方法模式】18-咖啡馆

cpp 复制代码
#include<iostream>
#include<vector>
#include<memory>
using namespace std;
#define endl '\n'
    
class Coffee {
public:
	void Run()
	{
		Name_coffee();
		Grind();
		Brewing();
		Add_condiments();
	}
protected:
	Coffee() {};
	void Grind()
	{
		cout << "Grinding coffee beans" << endl;
	}
	void Brewing()
	{
		cout << "Brewing coffee" << endl;
	}
	virtual void Name_coffee() = 0;
	virtual void Add_condiments() = 0;
	virtual ~Coffee() = default;
};


class Latte : public Coffee {
protected:
	void Name_coffee() override
	{
		cout << "Making Latte:" << endl;
	}
	void Add_condiments() override
	{
		cout << "Adding milk" << endl << "Adding condiments" << endl;
	}
};

class A_coffee : public Coffee {
protected:
	void Name_coffee() override
	{
		cout << "Making American Coffee:" << endl;
	}
	void Add_condiments() override
	{
		cout << "Adding condiments" << endl;
	}
};


signed main() {
    cin.tie(0) -> sync_with_stdio(false);
    int x = 0;
   	while (cin >> x) 
   	{
        shared_ptr<Coffee> coffeePtr; 
        switch (x) 
        {
            case 1:
                coffeePtr = make_shared<A_coffee>();
                break;
            case 2:
                coffeePtr = make_shared<Latte>();
                break;
            default:
                continue; 
        }
        coffeePtr -> Run();
        cout << endl;
    }

    return 0;
}

二:策略模式

1:关键组成部分

  1. 策略接口:定义所有支持的算法的公共接口。
  2. 具体策略类:实现策略接口,封装具体的算法。
  3. 上下文(Context):持有一个对策略接口的引用,可以在运行时切换策略。

2:工作原理

  • 上下文类使用策略接口来调用具体的策略算法。
  • 客户端可以根据需要选择并设置具体的策略,策略的实现细节对客户端是透明的。

3:适用场景

  • 当你有多个相关的类,仅仅是行为不同,而可以通过不同的算法实现。
  • 当你需要在运行时选择算法。
  • 当你希望避免使用大量的条件语句(如 if-elseswitch)。

代码实践:

【设计模式专题之策略模式】14. 超市打折

分析:该问题中,策略执行往往是稳定的,变化点在于用户策略的选择。

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<memory>
using namespace std;
#define endl '\n'
const int maxn = 1e6 + 10;
int n, m, k, d, T = 1, A, B;

//支付基类
class Pay {
public:
	Pay() {}
	virtual ~Pay() = default;	
	virtual int cost(int x) const = 0;
};

class Percentage_pay : public Pay {
public:
	int cost(int amount) const override
	{
		return amount * 0.9;
	}
};

class Full_pay : public Pay {
public:
	int cost(int amount) const override
	{
		if(amount >= 300)return amount - 40;
		if(amount >= 200)return amount - 25;
		if(amount >= 150)return amount - 15;
		if(amount >= 100)return amount - 5;
		return amount;
	}
};

//具体策略类
class Shopping {
private:
	std::shared_ptr<Pay> ptr;
public:
	Shopping(std::shared_ptr<Pay> p) : ptr(p) { } 
	void Run(int x) const
	{
		cout << ptr -> cost(x) << endl;
	}
};



signed main() 
{
    cin.tie(0) -> sync_with_stdio(false);
    int T = 0;
    cin >> T;
    std::shared_ptr<Pay> ptr;
    while(T--)
    {
    	cin >> n >> m;
    	if(1 == m)ptr = make_shared<Percentage_pay>();
    	else if(2 == m) ptr = make_shared<Full_pay>();
    	Shopping shop(ptr);
    	shop.Run(n);
    }
    return 0;
}

三:观察者模式

观察者模式是一种行为型模式。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象的状态变化。当主题对象的状态发生变化时,所有依赖于它的观察者都会收到通知并自动更新。

1:观察者模式的组成部分

  1. 主题(Subject)

    • 维护观察者的列表,提供添加、移除观察者的方法。
    • 通知所有已注册的观察者。
  2. 观察者(Observer)

    • 定义更新的接口,以便在主题状态变化时被通知。
  3. 具体主题(ConcreteSubject)

    • 具体实现主题,包含状态并在状态变化时通知观察者。
  4. 具体观察者(ConcreteObserver)

    • 实现观察者接口,更新自身状态以响应主题的变化。

2:流程步骤

  1. 注册观察者

    • 观察者通过主题的注册方法添加自己到观察者列表中。
  2. 更新状态

    • 当主题的状态发生变化时(例如数据更新),它会调用通知方法。
  3. 通知观察者

    • 主题遍历其观察者列表,调用每个观察者的更新方法,通知它们状态已改变。
  4. 更新观察者

    • 每个观察者在接收到通知后,执行自己的更新逻辑。

3: 代码实践

【设计模式专题之观察者模式】13. 时间观察者

cpp 复制代码
#include<iostream>
#include<string>
#include<vector>
#include <algorithm> 
#include<memory>
using namespace std;
/* C++ 不推荐多继承的使用, 但是在观察者模式(不局限于此)中对于具体观察者可以采用继承一个主基类和多个接口类的方式来解耦合降低依赖性(编译依赖) */

/*1. 确定需求: 识别问题:明确主题和观察者。*/
/* ... UML分析 ... */

/*2. 定义接口:观察者接口:定义一个接口(或抽象类),声明更新方法,所有观察者都需要实现这个方法。*/
class Observer {
public:
	virtual void update() = 0;
	virtual ~Observer() {}
};

/* 主题接口:定义一个接口,包含注册、注销和通知观察者的方法 */
class Subject {
public:
	virtual void register_Observer(Observer* observer) = 0;
	virtual void remove_Observer(const Observer* observer) = 0;
	virtual void notify_Observer() = 0;
	virtual ~Subject() {}
};

/* 其余接口类定义 */
class Count {
private:
	int cnt;
public:
Count() : cnt(0) {}
	virtual void Updated()
	{
		cout << ++cnt << endl;
	}
};

/* 3. 实现具体类 */
/*具体观察者:实现观察者接口,以便在接收到更新时执行相应的动作。*/

class Student : public Observer, public Count {
private:
    string m_name;
public:
	Student(const string name) : m_name(name) { } 
	void update() override
	{
		cout << m_name << ' ';
		Updated();
	}
};

/* 具体主题:实现主题接口,维护观察者列表,并在状态变化时通知所有观察者 */
class Clock : public Subject {
private: 
	vector<Observer*> observers;
	//std::vector< std::shared_ptr<Observer> > observers; //  或者自动管理内存注意函数传参
public:
	void register_Observer(Observer* observer) override
	{
		observers.emplace_back(observer);
	}
 	void remove_Observer(const Observer* observer) override 
 	{
        observers.erase(remove(observers.begin(), observers.end(), observer), observers.end());
    }
	void notify_Observer()
	{
		for(auto &x : observers)
		{
			x -> update();
		}
	}
	~Clock()
	{
		for(auto&x : observers)
		{
			delete x;
		}
	}
};

/* 4. 组合使用: 创建具体主题和观察者对象,并建立它们之间的关系。 */
signed main()
{
	int n = 0, m = 0; string S_name = " ";
	cin >> n;
	shared_ptr<Clock> C = make_shared<Clock>();
	for(int i = 1; i <= n; i += 1)
	{
		cin >> S_name;
		Student* stu = new Student(S_name); 
		C -> register_Observer(stu);
	}
	cin >> m;
	while(m--)
	{
		C -> notify_Observer();
	}
	return 0;
}

3:总结

优点

  • 促进了松耦合的设计。
  • 易于扩展,添加新的观察者非常简单。

缺点

  • 当观察者数量过多时,通知性能可能受到影响。
  • 需要注意观察者和主题的状态一致性。

观察者模式非常适用于事件驱动的系统,例如 GUI 框架、消息传递系统等。

四:装饰模式

装饰模式(Decorator Pattern)是一种结构型设计模式,它允许在运行时动态地为对象添加新功能,而不影响其他对象的功能。这种模式通过将功能附加到现有对象的方式,提供了一种灵活的替代继承的方法。

1:主要组成部分

  1. 组件接口(Component):定义了一个接口,供具体组件和装饰器实现。

  2. 具体组件(ConcreteComponent):实现了组件接口,定义了被装饰的对象。

  3. 装饰器(Decorator):持有一个组件对象的引用,并实现了组件接口,用于给组件添加新功能。

  4. 具体装饰器(ConcreteDecorator):扩展了装饰器,增加了新的行为。

2:流程设计

1. 定义组件接口

首先,定义一个抽象的组件接口,规定所有具体组件和装饰器必须实现的方法。

2. 创建具体组件

实现组件接口的具体类,这些类是基础对象,提供基本的功能。

3. 定义装饰器基类

创建一个装饰器基类,它同样实现组件接口,并持有一个组件对象的引用。装饰器可以在基类中定义通用行为。

4. 创建具体装饰器

实现具体的装饰器类,继承自装饰器基类。每个具体装饰器可以增加新的行为或属性。

5. 动态组合对象和装饰器

在客户端代码中,首先创建具体的组件对象,然后通过装饰器动态地添加功能。

重点:下列是动态装填和静态装填类数目的对比图

++经过合理的动态装填重构,能很大程度上降低类数目,当然一个设计模式不可能适用于所有场景,++

++在某些简单场景中,使用装饰模式可能显得过于复杂,简单的继承或其他模式可能更合适。++

3:代码实践

【设计模式专题装饰模式】8-咖啡加糖

cpp 复制代码
#include <iostream>
#include <string>
#include<memory>

class Coffee {
public:
    virtual void Brew() = 0;
    virtual ~Coffee() { }
};

class BlackCoffee : public Coffee {
public:
    void Brew() override 
    {
        std::cout << "Brewing Black Coffee" << std::endl;
    }
};


class Latte : public Coffee {
public:
    void Brew() override 
    {
        std::cout << "Brewing Latte" << std::endl;
    }
};


// 装饰者抽象类
class Decorator : public Coffee {
private:
    std::unique_ptr<Coffee> coffee;
public:
    Decorator(std::unique_ptr<Coffee> c) : coffee(std::move(c)) { }
    
    void Brew() override 
    {
        coffee->Brew(); 
    }
};


// 具体的牛奶装饰者类
class MilkDecorator : public Decorator {
public:
    MilkDecorator(std::unique_ptr<Coffee> coffee) : Decorator(std::move(coffee)) {}
    void Brew() override {
        Decorator::Brew();
        std::cout << "Adding Milk" << std::endl;
    }
};

// 具体的糖装饰者类
class SugarDecorator : public Decorator {
public:
    SugarDecorator(std::unique_ptr<Coffee> coffee) : Decorator(std::move(coffee)) {}
    void Brew() override {
        Decorator::Brew();
        std::cout << "Adding Sugar" << std::endl;
    }
};

int main() 
{
	int n, m;
	while(std::cin >> n >> m)
	{
		std::unique_ptr<Coffee> coffee;
		if(1 == n)
		{
			coffee = std::make_unique<BlackCoffee>();
			if(1 == m)
			coffee = std::make_unique<MilkDecorator>(std::move(coffee));
			else if(2 == m)
			coffee = std::make_unique<SugarDecorator>(std::move(coffee));
		}
		else if(2 == n)
		{
			coffee = std::make_unique<Latte>();
			if(1 == m)
			coffee = std::make_unique<MilkDecorator>(std::move(coffee));
			else if(2 == m)
			coffee = std::make_unique<SugarDecorator>(std::move(coffee));
		}
		coffee -> Brew();
	}
	
	return 0;
}

五:桥模式

1:主要组成部分

  1. 抽象类(Abstraction):定义了高层的接口,可以包含对实现部分的引用。

  2. 扩展抽象类(Refined Abstraction):对抽象类的具体实现,通常会增加一些新功能。

  3. 实现接口(Implementor):定义了实现类的接口,但并不要求与抽象类的接口一致。

  4. 具体实现类(Concrete Implementor):实现了实现接口,定义了具体的实现方式。

2:工作原理

桥模式通过将抽象部分与具体实现部分分开,使得二者可以独立变化。举个例子:

  • 假设你在开发一个图形应用程序,其中有不同的形状(如圆形、矩形)和不同的颜色(如红色、蓝色)。你可以使用桥模式将形状和颜色分离,形成两个独立的层次结构。

3:优点

  • 提高了可扩展性:增加新实现或新抽象类不需要修改现有代码。
  • 减少了类的数量:通过组合的方式可以避免产生大量的子类。

4:适用场景

  • 当需要将抽象部分与具体实现部分分离时。
  • 当系统可能在不同的实现之间切换时。
  • 当你希望避免在每个实现与抽象之间建立固定的关联时。

5:代码实践

【设计模式专题之桥接模式】10-万能遥控器

cpp 复制代码
#include <iostream>
#include <memory>

// 实现部分的接口
class TVImplementor {
public:
    virtual void turnOn() = 0;
    virtual void turnOff() = 0;
    virtual void changeChannel() = 0;
    virtual ~TVImplementor() = default;
};

// 具体实现部分
class SonyTV : public TVImplementor {
public:
    void turnOn() override {
        std::cout << "Sony TV is ON" << std::endl;
    }
    void turnOff() override {
        std::cout << "Sony TV is OFF" << std::endl;
    }
    void changeChannel() override {
        std::cout << "Switching Sony TV channel" << std::endl;
    }
};

class TCLTV : public TVImplementor {
public:
    void turnOn() override {
        std::cout << "TCL TV is ON" << std::endl;
    }
    void turnOff() override {
        std::cout << "TCL TV is OFF" << std::endl;
    }
    void changeChannel() override {
        std::cout << "Switching TCL TV channel" << std::endl;
    }
};

// 抽象部分
class TVControl {
protected:
    std::shared_ptr<TVImplementor> tv; // 持有实现部分的引用

public:
    TVControl(std::shared_ptr<TVImplementor> tvImpl) : tv(tvImpl) {}
    
    virtual void turnOn() {
        tv -> turnOn();
    }

    virtual void turnOff() {
        tv -> turnOff();
    }

    virtual void changeChannel() {
        tv -> changeChannel();
    }

    virtual ~TVControl() = default;
};

// 具体抽象部分
class RemoteControl : public TVControl {
public:
    RemoteControl(std::shared_ptr<TVImplementor> tvImpl) : TVControl(tvImpl) {}

    void turnOn() override {
        TVControl::turnOn();
    }

    void turnOff() override {
        TVControl::turnOff();
    }

    void changeChannel() override {
        TVControl::changeChannel();
    }
};

int main() 
{
	std::cin.tie(0) -> sync_with_stdio(false);
    int n = 0, id = 0, motion = 0;
    std::shared_ptr<TVImplementor> sony = std::make_shared<SonyTV>();
    std::shared_ptr<TVImplementor> tcl = std::make_shared<TCLTV>();
	std::shared_ptr<RemoteControl> sonyRemote = std::make_shared<RemoteControl>(sony);
	std::shared_ptr<RemoteControl> tclRemote = std::make_shared<RemoteControl>(tcl);
	std::cin >> n;
    for(int i = 1; i <= n; i += 1)
    {
    	std::cin >> id >> motion;
    	if(0 == id)
    	{
    		if(2 == motion) { sonyRemote -> turnOn(); }
    		else if(3 == motion) { sonyRemote -> turnOff(); }
    		else if(4 == motion) { sonyRemote -> changeChannel(); }
    	}
    	else if(1 == id)
    	{
    		if(2 == motion) { tclRemote -> turnOn(); }
    		else if(3 == motion) { tclRemote -> turnOff(); }
    		else if(4 == motion) { tclRemote -> changeChannel(); }
    	}
    }
    return 0;
}

因为该例题在实现部分功能较少,且都是通用功能,因此具体类完全可以直接使用父类已有成员函数

优化改进:

cpp 复制代码
#include <iostream>
#include <memory>

// 实现部分的接口
class TVImplementor {
public:
    virtual void turnOn() = 0;
    virtual void turnOff() = 0;
    virtual void changeChannel() = 0;
    virtual ~TVImplementor() = default;
};

// 具体实现部分
class SonyTV : public TVImplementor {
public:
    void turnOn() override {
        std::cout << "Sony TV is ON" << std::endl;
    }
    void turnOff() override {
        std::cout << "Sony TV is OFF" << std::endl;
    }
    void changeChannel() override {
        std::cout << "Switching Sony TV channel" << std::endl;
    }
};

class TCLTV : public TVImplementor {
public:
    void turnOn() override {
        std::cout << "TCL TV is ON" << std::endl;
    }
    void turnOff() override {
        std::cout << "TCL TV is OFF" << std::endl;
    }
    void changeChannel() override {
        std::cout << "Switching TCL TV channel" << std::endl;
    }
};

// 抽象部分
class TVControl {
protected:
    std::shared_ptr<TVImplementor> tv;

public:
    TVControl(std::shared_ptr<TVImplementor> tvImpl) : tv(tvImpl) {}
    
    virtual void turnOn() {
        tv->turnOn();
    }

    virtual void turnOff() {
        tv->turnOff();
    }

    virtual void changeChannel() {
        tv->changeChannel();
    }

    virtual ~TVControl() = default;
};

// 具体抽象部分
class RemoteControl : public TVControl {
public:
    RemoteControl(std::shared_ptr<TVImplementor> tvImpl) : TVControl(tvImpl) {}
};

void handleMotion(std::shared_ptr<RemoteControl> remote, int motion) {
    switch (motion) {
        case 2: remote->turnOn(); break;
        case 3: remote->turnOff(); break;
        case 4: remote->changeChannel(); break;
        default: std::cout << "Invalid motion!" << std::endl; break;
    }
}

int main() 
{
    std::cin.tie(0) -> sync_with_stdio(false);
    int n = 0, id = 0, motion = 0;
    auto sony = std::make_shared<SonyTV>();
    auto tcl = std::make_shared<TCLTV>();
    auto sonyRemote = std::make_shared<RemoteControl>(sony);
    auto tclRemote = std::make_shared<RemoteControl>(tcl);

    std::cin >> n;
    for (int i = 1; i <= n; i += 1) 
    {
        std::cin >> id >> motion;
        if (id == 0) {
            handleMotion(sonyRemote, motion);
        } 
        else if (id == 1) {
            handleMotion(tclRemote, motion);
        } 
    }
    return 0;
}

六:工厂模式

七:抽象工厂

八:原型模式

九:构建器

十:单件模式

十一:享元模式

十二: 门面模式

十三:代理模式

十四:适配器

十五:中介者

十六:状态模式

十七:备忘录

十八:组合模式

十九:迭代器

二十:职责链

二十一:命令模式

二十二:访问器

二十三:解析器

相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室6 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0016 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我587 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
fpcc7 小时前
跟我学c++中级篇——C++中的缓存利用
c++·缓存
呆萌很7 小时前
C++ 集合 list 使用
c++