随着软件工程的不断发展,除了经典的二十几种设计模式,还有更多设计模式在不断涌现,其中一些设计模式也是值得学习的。
对象池模式 对象池(Object Pool)是一种创建和管理对象的设计模式,特别适用于需要频繁创建和销毁对象的场景。 它通过复用对象来减少对象的创建和销毁次数,从而提高性能和资源利用率,在资源密集型的应用情景中(如数据库连接、线程、Socket连接、内存分配等),池化的思想被广泛使用。
对象池的实现是非常简单直观的,直接看示例代码即可。
与之不同的是,使用C++实现一个实用的线程池或内存池的代码细节会更加复杂,需要深入考虑多线程和内存管理的细节,本文中不作讨论。
示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <memory> #include <vector> class PooledObject {public : PooledObject () { std::cout << "PooledObject created.\n" ; } ~PooledObject () { std::cout << "PooledObject destroyed.\n" ; } void reset () { std::cout << "PooledObject reset.\n" ; } void doSomething () { std::cout << "PooledObject is doing something.\n" ; } }; class ObjectPool {private : std::vector<std::shared_ptr<PooledObject>> m_pool; public : std::shared_ptr<PooledObject> getObject () { if (m_pool.empty ()) { return std::make_shared <PooledObject>(); } std::shared_ptr<PooledObject> obj = m_pool.back (); m_pool.pop_back (); obj->reset (); return obj; } void returnObject (std::shared_ptr<PooledObject> obj) { m_pool.push_back (obj); } }; int main () { ObjectPool pool; auto obj1 = pool.getObject (); obj1->doSomething (); pool.returnObject (obj1); auto obj2 = pool.getObject (); obj2->doSomething (); return 0 ; }
运行结果如下
1 2 3 4 5 PooledObject created. PooledObject is doing something. PooledObject reset. PooledObject is doing something. PooledObject destroyed.
订阅发布模式 订阅发布模式是一种消息传递模式,允许发送者(发布者)和接收者(订阅者)彼此解耦。 在逻辑上主要包括三部分:
发布者(Publisher):发布消息的对象。
订阅者(Subscriber):接收消息的对象。
事件总线(Event Bus):管理发布者和订阅者之间的通信桥梁。
发布者将特定主题的消息发布到一个事件总线上,而订阅者通过事件总线订阅一个或多个主题。 当有新的消息发布时,所有订阅了该主题的订阅者都会收到通知。
示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <iostream> #include <memory> #include <string> #include <unordered_map> #include <utility> #include <vector> class Subscriber {public : virtual ~Subscriber () = default ; virtual void onMessage (const std::string &message) = 0 ; }; class EventBus {public : void addsubscriber (const std::string &topic, std::shared_ptr<Subscriber> subscriber) { m_subscribers[topic].push_back (subscriber); } void publish (const std::string &topic, const std::string &message) { if (m_subscribers.find (topic) != m_subscribers.end ()) { for (auto &subscriber : m_subscribers[topic]) { subscriber->onMessage (message); } } } private : std::unordered_map<std::string, std::vector<std::shared_ptr<Subscriber>>> m_subscribers; }; class Publisher {public : explicit Publisher (EventBus &eventBus) : m_eventBus(eventBus) { } void publishMessage (const std::string &topic, const std::string &message) { m_eventBus.publish (topic, message); } private : EventBus &m_eventBus; }; class NewsSubscriber : public Subscriber {public : explicit NewsSubscriber (std::string name) : m_name(std::move(name)) { } void onMessage (const std::string &message) override { std::cout << m_name << " received news: " << message << '\n' ; } private : std::string m_name; }; class SportsSubscriber : public Subscriber {public : explicit SportsSubscriber (std::string name) : m_name(std::move(name)) { } void onMessage (const std::string &message) override { std::cout << m_name << " received sports news: " << message << '\n' ; } private : std::string m_name; }; int main () { EventBus eventBus; auto newsSubscriber1 = std::make_shared <NewsSubscriber>("News Subscriber 1" ); auto newsSubscriber2 = std::make_shared <NewsSubscriber>("News Subscriber 2" ); auto sportsSubscriber1 = std::make_shared <SportsSubscriber>("Sports Subscriber 1" ); eventBus.addsubscriber ("news" , newsSubscriber1); eventBus.addsubscriber ("news" , newsSubscriber2); eventBus.addsubscriber ("sports" , sportsSubscriber1); Publisher newsPublisher (eventBus) ; Publisher sportsPublisher (eventBus) ; newsPublisher.publishMessage ("news" , "Breaking news: C++20 released!" ); sportsPublisher.publishMessage ("sports" , "New world record set in marathon!" ); return 0 ; }
运行结果如下
1 2 3 News Subscriber 1 received news: Breaking news: C++20 released! News Subscriber 2 received news: Breaking news: C++20 released! Sports Subscriber 1 received sports news: New world record set in marathon!
订阅发布模式与观察者模式经常被用来对比,两者有很多相似之处,但是也有如下的不同点:
解耦程度:
观察者模式:观察者和被观察者之间存在直接的关联。被观察者需要维护着观察者的列表,并在状态变化时通知这些观察者。
订阅发布模式:发布者和订阅者完全解耦,彼此不知道对方的存在。
通信机制:
观察者模式:被观察者直接调用观察者的更新方法,通知它们状态变化。
订阅发布模式:所有的通信都基于事件总线进行,消息通过事件总线分发给订阅者。
使用场景:
观察者模式:适用于一个对象的状态变化需要通知多个依赖对象的场景,适合较简单的同步通信场景。
订阅发布模式:适用于需要解耦多个组件之间通信的场景,如事件驱动系统、消息队列系统等,适合更复杂的同步或异步通信场景。
事实上,我们可以将订阅发布模式视作广义的观察者模式,订阅发布模式还明显的吸收了中介模式中的部分做法。
补充 除了设计模式,还有几个编程概念与之相生相随,尤其在Java中被广泛的应用:面向切面编程、控制反转和依赖注入。 下面简单学习一下这几个概念,并在C++/Python中尝试应用。
面向切面编程 面向切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,它旨在提高软件模块化,通过将横切关注点(Cross-cutting Concerns)与业务逻辑分离来简化程序结构。 横切关注点是指那些分散在多个模块中的功能,如日志记录、事务管理、权限控制等,这些功能虽然对多个模块都重要,但与核心业务逻辑无关。
在Java中,Spring框架广泛支持AOP,例如我们可以使用Spring AOP来添加日志记录的切面,主要代码如下
1 2 3 4 5 6 7 @Aspect public class LoggingAspect { @Before("execution(* com.example.service.*.*(..))") public void logBefore (JoinPoint joinPoint) { System.out.println("Executing: " + joinPoint.getSignature()); } }
这里@Before注解定义了一个前置通知,它会在匹配的方法调用之前自动执行。execution(* com.example.service.*.*(..))是一个切点表达式,表示匹配所有在com.example.service包下的方法。
除了上面的定义部分,在Spring框架下还需要启用AOP并配置这个切面,最终达到效果:当服务层的任一方法被调用时,都会先执行日志记录逻辑,将调用信息输出到控制台。
Python虽然没有像Spring AOP这样的框架,但可以使用装饰器来实现类似的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from functools import wrapsdef log_before (func ): @wraps(func ) def wrapper (*args, **kwargs ): print (f"Calling {func.__name__} ..." ) return func(*args, **kwargs) return wrapper class UserService : @log_before def add_user (self, name ): print (f"Adding user: {name} " ) service = UserService() service.add_user("Alice" )
C++的语法和标准库并不直接支持AOP,但可以通过元编程、模板或者定制编译器(如AspectC++)来实现AOP的概念:
直接使用C++的lambda表达式、模板元编程等可以实现Python的装饰器,以此实现AOP类似的功能;
AspectC++是一个扩展的C++编译器,它通过扩展语法支持直接编写面向切面的代码,扩展语法类似Java的注解。
控制反转与依赖注入 控制反转(Inversion of Control, IoC)是一种编程思想,它将应用程序的控制权从应用代码转移到外部容器或框架。 在传统的编程模式中,对象负责创建或查找它所依赖的对象。 采用控制反转后,这种控制权被“反转”,即对象的创建、生命周期管理以及依赖关系的解决不再由对象本身负责,而是由外部环境(如框架)来控制。
控制反转的一种具体做法是依赖注入(Dependency Injection, DI), 它通过将依赖对象(即组件或服务)注入到需要它们的对象中,从而实现了控制反转。依赖注入可以通过构造函数注入、属性注入或方法注入来实现。
依赖注入的C++示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <iostream> #include <memory> class Service {public : virtual void execute () = 0 ; virtual ~Service () = default ; }; class ServiceImpl : public Service {public : void execute () override { std::cout << "Service is executed.\n" ; } }; class Client {private : std::shared_ptr<Service> m_service; public : explicit Client (std::shared_ptr<Service> service) : m_service(service) { } void doSomething () { m_service->execute (); } }; int main () { std::shared_ptr<Service> service = std::make_shared <ServiceImpl>(); Client client (service) ; client.doSomething (); return 0 ; }
控制反转并不等于依赖注入,还有其他形式的控制反转,如事件驱动编程中的控制反转,C++示例代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <functional> #include <iostream> #include <memory> #include <vector> class Event {public : virtual void notify () = 0 ; virtual ~Event () = default ; }; class ConcreteEvent : public Event {private : std::vector<std::function<void ()>> m_listeners; public : void addListener (std::function<void ()> listener) { m_listeners.push_back (listener); } void notify () override { for (const auto &listener : m_listeners) { listener (); } } }; class EventListener {public : void onEvent () { std::cout << "EventListener received the event.\n" ; } }; int main () { std::shared_ptr<ConcreteEvent> event = std::make_shared <ConcreteEvent>(); EventListener listener; event->addListener ([&listener]() { listener.onEvent (); }); event->notify (); return 0 ; }