Skip to content

Instantly share code, notes, and snippets.

@Journeyman1337
Created September 29, 2021 20:27
Show Gist options
  • Save Journeyman1337/63735f9bd37f0cf14c6c4dc29840084c to your computer and use it in GitHub Desktop.
Save Journeyman1337/63735f9bd37f0cf14c6c4dc29840084c to your computer and use it in GitHub Desktop.
read the comment
/*
Copyright (c) 2021 Daniel Valcour
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include <list>
#include <string>
#include <iostream>
#include <memory>
// This is an advanced modern C++ 17 take on the observer pattern.
// Objects involved in this pattern must be in a static memory location. The best way of
// achieving this is to store the objects in unique_ptr on the heap.
// Both the Publisher and Subscriber keep track of the lifetimes of their attachments,
// and break cleanly when they are destroyed in either order on the same thread.
// Unlike in the traditional pattern, the publisher passes data to all subscribers in a single
// argument. Multiple arguments can be passed by passing a struct. Different arguments can be
// passed in different situations using a std::variant of structs.
// Publishers have a one-to-many relationship with Subscribers, meaning that a single Publisher
// can Publish to multiple Subscribers at once.
template<typename T>
class Subscriber;
template<typename T>
class Publisher;
//Data passing observer pattern subject.
template<typename T>
class Publisher
{
private:
std::list<Subscriber<T>*> subscribers;
public:
Publisher() : subscribers() {}
~Publisher()
{
for (const auto& s : this->subscribers)
{
s->NotifyPublisherDestroyed();
}
}
void RegisterSubscriber(Subscriber<T>* subscriber)
{
this->subscribers.push_back(subscriber);
}
void RemoveSubscriber(Subscriber<T>* subscriber)
{
this->subscribers.remove(subscriber);
}
void Publish(const T& data)
{
for (const auto& s : this->subscribers)
{
s->Recieve(data);
}
}
size_t GetSubscriberCount()
{
return this->subscribers.size();
}
};
//Data recieving observer pattern observer.
template<typename T>
class Subscriber
{
private:
Publisher<T>* publisher;
public:
Subscriber()
{
this->publisher = nullptr;
}
~Subscriber()
{
this->Unsubscribe();
}
void Subscribe(Publisher<T>* publisher)
{
this->Unsubscribe();
this->publisher = publisher;
this->publisher->RegisterSubscriber(this);
}
void Unsubscribe()
{
if (this->publisher != nullptr)
{
this->publisher->RemoveSubscriber(this);
this->publisher = nullptr;
}
}
virtual void Recieve(const T& data) = 0;
void NotifyPublisherDestroyed()
{
this->publisher = nullptr;
}
bool IsSubscribed()
{
return publisher != nullptr;
}
};
struct TestData
{
int a;
int b;
int c;
TestData(const int a, const int b, const int c) :a(a),b(b),c(c){}
};
class TestSubscriber : public Subscriber<TestData>
{
public:
std::string Name;
TestSubscriber(std::string_view name, Publisher<TestData>* publisher)
:
Name(name)
{
std::cout << name << " created!" << std::endl;
this->Subscribe(publisher);
}
~TestSubscriber()
{
std::cout << this->Name << " destroyed!" << std::endl;
Name.clear();
}
// Inherited via Subscriber
void Recieve(const TestData& data) override
{
std::cout << this->Name << "Recieveced a=" << data.a << " b=" << data.b << " c=" << data.c;
}
};
class TestPublisher : public Publisher<TestData>
{
public:
TestPublisher()
{
std::cout << "Publisher created!" << std::endl;
}
~TestPublisher()
{
std::cout << "Publisher destroyed!" << std::endl;
}
};
int main()
{
auto publisher = std::make_unique<TestPublisher>();
std::cout << "publisher subscriber count: " << publisher->GetSubscriberCount() << std::endl;
auto subscriber_1 = std::make_unique<TestSubscriber>("subscriber 1", publisher.get());
std::cout << "publisher subscriber count: " << publisher->GetSubscriberCount() << std::endl;
auto subscriber_2 = std::make_unique<TestSubscriber>("subscriber 2", publisher.get());
std::cout << "publisher subscriber count: " << publisher->GetSubscriberCount() << std::endl;
publisher->Publish(TestData(1, 2, 3));
publisher->Publish(TestData(4, 5, 6));
subscriber_1 = std::unique_ptr<TestSubscriber>(nullptr);
std::cout << "publisher subscriber count: " << publisher->GetSubscriberCount() << std::endl;
publisher->Publish(TestData(7, 8, 9));
publisher = std::unique_ptr<TestPublisher>(nullptr);
std::cout << "subscriber 2 is subscribed: " << subscriber_2->IsSubscribed() << std::endl;
subscriber_2.release();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment