SOLID Principles
SOLID principles are the five design principles of object-oriented programming.
Today I will be solving SRP and DIP issues in my code.
SRP(Single Responsibility principle)
SRP is the principle that a class(object) should have only one responsibility(function).
When this principle is not followed, maintenance problems can easily arise, such as when one function is modified, other functions that seemed unrelated stop working.
In my code, I have several classes that handle various functions.
ServerPacketHandler performs both the role of routing packets and the actual business logic(login processing, room creation logic, etc.) at the same time.
Session has a mixed role of being responsible for socket communication (Receive/Send) and a data container that holds user status information (playerId, name, room).
PlayerManager handles all the 'player' related functions, including session management, ID generation, name mapping, token management, etc.
So, I will separate all of these functions.
DIP(Dependency Inversion 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 objects that use the Singleton pattern. I'll split some of these out to adhere to the Dependency Independent Programming (DIP) principle.
Let's follow the SRP principle
ServerPacketHandler.cpp
ServerPacketHandler is both receiving and processing packets.
I will change its role to only receive packets and forward them to other new service files.
I made AuthService(Login), RoomSerivce(JoinRoom, MakeRoom), GameService(In game operations to add later).
//Before
void ServerPacketHandler::Handle_MAKE_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::MakeRoomRequest& pkt) {
if (session->room.lock()) {
// Already in a room
blindspot::MakeRoomResponse res;
res.set_result(blindspot::MAKE_ALREADY_IN_ROOM);
session->Send(blindspot::PacketID::ID_MAKE_ROOM_RESPONSE, res);
return;
}
std::string title = pkt.room_name();
int32_t maxPlayers = pkt.max_players();
std::string password = pkt.password();
//...omission
}
//After
void ServerPacketHandler::Handle_JOIN_ROOM_REQUEST(std::shared_ptr<Session> session, blindspot::JoinRoomRequest& pkt) {
RoomService::JoinRoom(session, pkt);
}
Session.h
Session.h currently contains both socket communication and player information (ID, name, room). We'll separate this into a communication object (Session) and a game object (Player).
I made Player object, and insert id,name,room informations.
//Models/Player.h
class Player {
public:
int32_t id;
std::string name;
std::mutex _nameLock;
std::weak_ptr<GameRoom> room;
void SetName(const std::string& playerName) {
std::lock_guard<std::mutex> lock(_nameLock);
name = playerName;
}
std::string GetName() {
std::lock_guard<std::mutex> lock(_nameLock);
return name;
}
};
//Network/Session.h
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {};
std::shared_ptr<Player> player_ ;
std::string _sessionKey;
//...
}
PlayerManager.h
Currently, PlayerManager manages everything from sessions, authentication, to player data.
So I'm going to break this down into PlayerManager, AuthManager, and SessionManager.
//PlayerManager.h
class PlayerManager {
static std::atomic<int32_t> playerIdGenerator_;
static std::mutex name_mutex_;
static std::map<int32_t, std::string> playerIdToName_;
public:
static PlayerManager& Instance();
int32_t GeneratePlayerId();
void RegisterPlayerName(int32_t playerId, const std::string& name);
void EditPlayerName(int32_t playerId, const std::string& newName);
std::string GetPlayerNameById(int32_t playerId);
};
//AuthManager.h
class AuthManager {
static std::mutex token_mutex_;
static std::map<std::string, int32_t> sessionKeyToPlayerId_;
static std::mutex name_mutex_;
static std::map<int32_t, std::string> playerIdToName_;
public:
static AuthManager& Instance();
static int32_t GetPlayerIdBySessionKey(const std::string& token);
static std::string GenerateSessionKey();
static void RemoveSession(const std::string& token);
static void RegisterSession(const std::string& token, int32_t playerId);
};
//SessionManager.h
class SessionManager {
std::mutex sessions_mutex_;
std::set<std::shared_ptr<Session>> sessions_;
public:
static SessionManager& Instance();
void Add(std::shared_ptr<Session> session);
void Remove(std::shared_ptr<Session> session);
void Broadcast(uint16_t id, google::protobuf::Message& msg);
};
In Conclusion
Before the project grew any larger, I refactored the code to align with the SRP principle. Next time, I'll try refactoring to align with the DIP principle mentioned above.
Top comments (0)