Tools: [BlindSpot] Log 04. Let's follow the SOLID principles : DIP

Tools: [BlindSpot] Log 04. Let's follow the SOLID principles : DIP

Source: Dev.to

SOLID Principles ## DIP(Dependency Inversion Principle) ## Why only Managers? ## Let's follow the DIP principle ## Making interfaces for managers ## Inheriting an interface ## Injecting dependencies ## Making Composition Root ## In Conclusion Last time, we learned about SRP, one of the SOLID principles, and refactored our code based on it. This time, we'll refactor our code based on the DIP principle. DIP is the principle that higher-level modules should depend on abstractions (interfaces) rather than on the concrete implementations of lower-level modules. Failure to adhere to this principle can lead to increased dependencies between classes, such that modifying one class can lead to subsequent modifications to other classes affected by the change. Furthermore, if a test class lacks an interface, a new class must be created to perform the same functionality. In my code, there are currently a large number of Manager/Service objects that use the Singleton pattern. If I were applying DIP, it might be easier to use something like TestAuthManager to load dummy data for testing before applying the DB later. I'll split Manager classes out to adhere to the Dependency Independent Programming (DIP) principle. Currently, both the Manager and Service classes are singletons, using no interfaces. I'll create an interface for the Manager class here. This is because, if we later implement sign-up/login functionality, Managers will likely need to interact with the database, requiring a complete overhaul of the entire logic. If we were to use the Manager object directly instead of an interface, other code would also need to be completely overhauled. Since the Service object is responsible for the actual running logic, I decided that creating an interface was a low priority. However, dependency injection will also be applied to service and handler classes without interface. First, I made interfaces for managers(Auth,Player,Room,Session). It contains functions from existing classes. I inherited the interface from the manager. At this point, copy constructors and copy assignment operators are prevented. This is because the mutex used by AuthManager cannot be copied, and even if there is no mutex, the manager object must remain intact. Now I should inject dependencies to Service. This should apply to all services, but I'll just use AuthService as an example. First, I created a manager interface pointer required for private. And, And I created a constructor that includes those managers. As you can see from the code, the Service does not use static variables. The singleton pattern using the Instance() function is no longer used. Therefore, it can be used by calling the manager from within the internal function. The service has been injected with a manager, and that service will now be injected with a handler. Finally, we'll create a Composition Root, which is where we'll create and inject the managers, services, and handlers we'll actually use. In this project, the Server class will fulfill this role. When the Server class's constructor is executed, it creates managers, and then creates services with those managers as constructor arguments. Finally, it creates handlers that inject those services. When the main function is executed, the server constructor takes action, and the server constructor creates all managers, services, and handlers. This time, I tried refactoring the code to follow the DIP. It doesn't seem to fully adhere to the SOLID pattern yet, but I've prioritized addressing the SRP and DIP issues, which would require significant time and effort to fix later. Next, I'll tackle the actual in-game implementation. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK: //IAuthManager.h class IAuthManager { public: virtual ~IAuthManager() = default; virtual int32_t GetPlayerIdByToken(const std::string& token) = 0; virtual std::string GenerateToken() = 0; virtual void RemoveToken(const std::string& token) = 0; virtual void RegisterToken(const std::string& token, int32_t playerId) = 0; }; //... Make other interfaces the same Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: //IAuthManager.h class IAuthManager { public: virtual ~IAuthManager() = default; virtual int32_t GetPlayerIdByToken(const std::string& token) = 0; virtual std::string GenerateToken() = 0; virtual void RemoveToken(const std::string& token) = 0; virtual void RegisterToken(const std::string& token, int32_t playerId) = 0; }; //... Make other interfaces the same CODE_BLOCK: //IAuthManager.h class IAuthManager { public: virtual ~IAuthManager() = default; virtual int32_t GetPlayerIdByToken(const std::string& token) = 0; virtual std::string GenerateToken() = 0; virtual void RemoveToken(const std::string& token) = 0; virtual void RegisterToken(const std::string& token, int32_t playerId) = 0; }; //... Make other interfaces the same CODE_BLOCK: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; CODE_BLOCK: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; COMMAND_BLOCK: //AuthManager.h #include "../Core/Interfaces/IAuthManager.h" class AuthManager : public IAuthManager{ public: AuthManager() = default; virtual ~AuthManager() = default; int32_t GetPlayerIdByToken(const std::string& token) override; std::string GenerateToken() override; void RemoveToken(const std::string& token) override; void RegisterToken(const std::string& token, int32_t playerId) override; private: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; std::mutex token_mutex_; std::map<std::string, int32_t> tokenToPlayerId_; std::mutex name_mutex_; std::map<int32_t, std::string> playerIdToName_; }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: //AuthManager.h #include "../Core/Interfaces/IAuthManager.h" class AuthManager : public IAuthManager{ public: AuthManager() = default; virtual ~AuthManager() = default; int32_t GetPlayerIdByToken(const std::string& token) override; std::string GenerateToken() override; void RemoveToken(const std::string& token) override; void RegisterToken(const std::string& token, int32_t playerId) override; private: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; std::mutex token_mutex_; std::map<std::string, int32_t> tokenToPlayerId_; std::mutex name_mutex_; std::map<int32_t, std::string> playerIdToName_; }; COMMAND_BLOCK: //AuthManager.h #include "../Core/Interfaces/IAuthManager.h" class AuthManager : public IAuthManager{ public: AuthManager() = default; virtual ~AuthManager() = default; int32_t GetPlayerIdByToken(const std::string& token) override; std::string GenerateToken() override; void RemoveToken(const std::string& token) override; void RegisterToken(const std::string& token, int32_t playerId) override; private: AuthManager(const AuthManager&) = delete; AuthManager& operator=(const AuthManager&) = delete; std::mutex token_mutex_; std::map<std::string, int32_t> tokenToPlayerId_; std::mutex name_mutex_; std::map<int32_t, std::string> playerIdToName_; }; COMMAND_BLOCK: class AuthService { public: private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: class AuthService { public: private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; COMMAND_BLOCK: class AuthService { public: private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; COMMAND_BLOCK: //AuthService.h class AuthService { public: AuthService(std::shared_ptr<IAuthManager> authMgr, std::shared_ptr<IPlayerManager> playerMgr, std::shared_ptr<ISessionManager> sessionMgr) : authMgr_(authMgr), playerMgr_(playerMgr), sessionMgr_(sessionMgr) { } void Login(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: //AuthService.h class AuthService { public: AuthService(std::shared_ptr<IAuthManager> authMgr, std::shared_ptr<IPlayerManager> playerMgr, std::shared_ptr<ISessionManager> sessionMgr) : authMgr_(authMgr), playerMgr_(playerMgr), sessionMgr_(sessionMgr) { } void Login(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; COMMAND_BLOCK: //AuthService.h class AuthService { public: AuthService(std::shared_ptr<IAuthManager> authMgr, std::shared_ptr<IPlayerManager> playerMgr, std::shared_ptr<ISessionManager> sessionMgr) : authMgr_(authMgr), playerMgr_(playerMgr), sessionMgr_(sessionMgr) { } void Login(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); private: std::shared_ptr<IAuthManager> authMgr_; std::shared_ptr<ISessionManager> sessionMgr_; std::shared_ptr<IPlayerManager> playerMgr_; }; CODE_BLOCK: //Before newPlayer->name = PlayerManager.Instance().GetPlayerNameById(playerId); //After newPlayer->name = playerMgr_->GetPlayerNameById(playerId); Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: //Before newPlayer->name = PlayerManager.Instance().GetPlayerNameById(playerId); //After newPlayer->name = playerMgr_->GetPlayerNameById(playerId); CODE_BLOCK: //Before newPlayer->name = PlayerManager.Instance().GetPlayerNameById(playerId); //After newPlayer->name = playerMgr_->GetPlayerNameById(playerId); COMMAND_BLOCK: using PacketFunc = std::function<void(std::shared_ptr<Session>, uint8_t*, uint16_t)>; class ServerPacketHandler { public: ServerPacketHandler(std::shared_ptr<AuthService> authService, std::shared_ptr<RoomService> roomService); void Init(); void HandlePacket(std::shared_ptr<Session> session, uint16_t id, uint8_t* payload, uint16_t size); private: void Handle_LOGIN_REQUEST(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); void Handle_JOIN_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::JoinRoomRequest& pkt); void Handle_MAKE_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::MakeRoomRequest& pkt); private: std::shared_ptr<AuthService> authService_; std::shared_ptr<RoomService> roomService_; PacketFunc packet_handlers_[UINT16_MAX]; }; Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK: using PacketFunc = std::function<void(std::shared_ptr<Session>, uint8_t*, uint16_t)>; class ServerPacketHandler { public: ServerPacketHandler(std::shared_ptr<AuthService> authService, std::shared_ptr<RoomService> roomService); void Init(); void HandlePacket(std::shared_ptr<Session> session, uint16_t id, uint8_t* payload, uint16_t size); private: void Handle_LOGIN_REQUEST(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); void Handle_JOIN_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::JoinRoomRequest& pkt); void Handle_MAKE_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::MakeRoomRequest& pkt); private: std::shared_ptr<AuthService> authService_; std::shared_ptr<RoomService> roomService_; PacketFunc packet_handlers_[UINT16_MAX]; }; COMMAND_BLOCK: using PacketFunc = std::function<void(std::shared_ptr<Session>, uint8_t*, uint16_t)>; class ServerPacketHandler { public: ServerPacketHandler(std::shared_ptr<AuthService> authService, std::shared_ptr<RoomService> roomService); void Init(); void HandlePacket(std::shared_ptr<Session> session, uint16_t id, uint8_t* payload, uint16_t size); private: void Handle_LOGIN_REQUEST(std::shared_ptr<Session> session, blindspot::LoginRequest& pkt); void Handle_JOIN_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::JoinRoomRequest& pkt); void Handle_MAKE_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::MakeRoomRequest& pkt); private: std::shared_ptr<AuthService> authService_; std::shared_ptr<RoomService> roomService_; PacketFunc packet_handlers_[UINT16_MAX]; }; CODE_BLOCK: //main.cpp int main() { try { boost::asio::io_context io_context; std::cout << "Server starting on port "<< PORT << "..." << std::endl; Server s(io_context, PORT); //Managers, services, and handlers are created and injected here. io_context.run(); // Start the server event loop } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } //Server.cpp Server::Server(boost::asio::io_context& io_context, short port) : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { sessionMgr_ = std::make_shared<SessionManager>(); roomMgr_ = std::make_shared<RoomManager>(); authMgr_ = std::make_shared<AuthManager>(); playerMgr_ = std::make_shared<PlayerManager>(); authService_ = std::make_shared<AuthService>(authMgr_, playerMgr_, sessionMgr_); roomService_ = std::make_shared<RoomService>(roomMgr_); packetHandler_ = std::make_shared<ServerPacketHandler>(authService_, roomService_); packetHandler_->Init(); std::cout << "Server initialized on port " << port << std::endl; DoAccept(); } Enter fullscreen mode Exit fullscreen mode CODE_BLOCK: //main.cpp int main() { try { boost::asio::io_context io_context; std::cout << "Server starting on port "<< PORT << "..." << std::endl; Server s(io_context, PORT); //Managers, services, and handlers are created and injected here. io_context.run(); // Start the server event loop } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } //Server.cpp Server::Server(boost::asio::io_context& io_context, short port) : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { sessionMgr_ = std::make_shared<SessionManager>(); roomMgr_ = std::make_shared<RoomManager>(); authMgr_ = std::make_shared<AuthManager>(); playerMgr_ = std::make_shared<PlayerManager>(); authService_ = std::make_shared<AuthService>(authMgr_, playerMgr_, sessionMgr_); roomService_ = std::make_shared<RoomService>(roomMgr_); packetHandler_ = std::make_shared<ServerPacketHandler>(authService_, roomService_); packetHandler_->Init(); std::cout << "Server initialized on port " << port << std::endl; DoAccept(); } CODE_BLOCK: //main.cpp int main() { try { boost::asio::io_context io_context; std::cout << "Server starting on port "<< PORT << "..." << std::endl; Server s(io_context, PORT); //Managers, services, and handlers are created and injected here. io_context.run(); // Start the server event loop } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } //Server.cpp Server::Server(boost::asio::io_context& io_context, short port) : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) { sessionMgr_ = std::make_shared<SessionManager>(); roomMgr_ = std::make_shared<RoomManager>(); authMgr_ = std::make_shared<AuthManager>(); playerMgr_ = std::make_shared<PlayerManager>(); authService_ = std::make_shared<AuthService>(authMgr_, playerMgr_, sessionMgr_); roomService_ = std::make_shared<RoomService>(roomMgr_); packetHandler_ = std::make_shared<ServerPacketHandler>(authService_, roomService_); packetHandler_->Init(); std::cout << "Server initialized on port " << port << std::endl; DoAccept(); }