Я практикую объектно-ориентированный дизайн для предстоящего интервью. Мой вопрос касается дизайна системы бронирования отелей:
— Система должна иметь возможность вернуть открытую комнату определенного типа или вернуть все открытые комнаты в отеле.
— Есть много типов номеров в отеле, как обычные, роскошные, знаменитости и так далее.
До сих пор я придумал следующие классы:
Class Room{
//Information about room
virtual string getSpecifications(Room *room){};
}
Class regularRoom: public Room{
//get specifications for regular room
}
Class luxuryRoom: public Room{
//get specifications for regular room
}
//Similarly create as many specialized rooms as you want
Class hotel{
vector<Room *>openRooms; //These are all the open rooms (type casted to Room type pointer)
Public:
Room search(Room *aRoom){ //Search room of a specific type
for(int i=0;i<openRooms.size();i++){
if(typeid(*aRoom)==typeid(*openRooms[i])) return *openRooms[i];
}
}
vector<Room> allOpenRooms(){//Return all open rooms
...
}
}
Я запутался в реализации метода hotel.search (), где я проверяю тип (который, я считаю, должен быть каким-то образом обработан полиморфизмом). Есть ли лучший способ проектирования этой системы, чтобы методы search и allOpenRooms могли быть реализованы без явной проверки типа объектов?
Вы всегда можете позволить комнате нести свой реальный тип вместо сравнения типа объекта:
enum RoomType
{
RegularRoom,
luxuryRoom
};
class Room{
public:
explicit Room(RoomType room_type) : room_type_(room_type) { }
virtual ~Room(){}
RoomType getRoomType() const { return room_type_; }
private:
RoomType room_type_; // carries room type
};
class regularRoom: public Room{
public:
regularRoom() : Room(RegularRoom){ }
};Room search(Room *aRoom)
{
//Search room of a specific type
for(size_t i=0;i<openRooms.size();i++)
{
if (aRoom->getRoomType() == RegularRoom) // <<-- compare room type
{
// do something
}
}
};
Просмотр объектов подкласса, спрашивающих, какого они типа, на самом деле не является хорошей иллюстрацией о-о-дизайна. Вам действительно нужно что-то, что вы хотите сделать во всех комнатах, не зная, к какому типу относится каждый из них. Например, распечатайте ежедневное меню комнаты (которое может отличаться для разных типов).
Преднамеренный поиск типа объекта подкласса, хотя и не ошибочный, но не слишком удачный. Если вы просто хотите сделать это, как сказали другие респонденты, просто «комнаты» с набором свойств.
Разные типы комнат имеют разное поведение? От
описание, которое вы даете, это не случай, когда наследство
должен быть использован. Каждая комната просто имеет атрибут, тип, который
в простейшей форме просто перечисление.
Если вы действительно хотите проверить, что комната того же типа, что и другая комната, то typeid()
так же хорош, как и любой другой метод, и, безусловно, «лучше» (по крайней мере, с точки зрения производительности) вызывать виртуальный метод.
Другой вариант заключается в том, чтобы вообще не иметь отдельных классов и хранить тип комнаты как переменную-член (и это, конечно, то, как я бы это разработал, но это не очень хороший дизайн для изучения ориентации и наследования объектов — вы не получите наследовать, когда базовый класс выполняет все ваши потребности).
Самый простой способ — получить перечисление типа Room, как подсказывает @billz. Проблема с этим состоит в том, что вы не должны забывать добавлять значение в перечисление и использовать его один раз каждый раз, когда добавляете в систему новый тип Room. Вы должны быть уверены, что используете значения перечисления только один раз, один раз в классе.
Но с другой стороны, Основанные на наследовании дизайны имеют смысл только в том случае, если типы иерархии имеют общее поведение. Другими словами, вы хотите использовать их одинаково, независимо от его типа. IMPO, OO / наследование не лучший способ сделать это.
Странный и масштабируемый способ, которым я делаю такие вещи, заключается в списки типов.
Обычно у вас разные критерии поиска для каждого типа в вашей системе. И, во многих случаях, результаты этого поиска не одинаковы для разных типов вашей системы (это не то же самое, что искать роскошную комнату и искать обычную комнату, у вас могут быть разные критерии поиска и / или вы хотите другой поиск данные результатов).
Для этой цели в системе есть три списка типов: один, содержащий типы данных, один, содержащий типы критериев поиска, и один, содержащий типы результатов поиска:
using system_data_types = type_list<NormalRoom,LuxuryRoom>;
using search_criteria_types = type_list<NormalRoomsCriteria,LuxuryRoommsCriteria>;
using search_results_types = type_list<NormalRoomSearchResults,LuxuryRoomSearchResults>;
Обратите внимание, что списки типов сортируются одинаково. Это важно, как я покажу ниже.
Итак, реализация поисковой системы:
class SearchEngine
{
private:
std::vector<VectorWrapper*> _data_lists; //A vector containing each system data type in its own vector. (One vector for NormalRoom, one for LuxuryRoom, etc)
//This function returns the vector that stores the system data type passed.
template<typename T>
std::vector<T>& _get_vector() {...} //Implementation explained below.
public:
SearchEngine() {...}//Explanation below.
~SearchEngine() {...}//Explanation below.
//This function adds an instance of a system data type to the "database".
template<typename T>
void addData(const T& data) { _get_vector<T>().push_back( data ); }
//The magic starts here:
template<typename SEARCH_CRITERIA_TYPE>//This template parameter is deduced by the compiler through the function parameter, so you can ommit it.
typename search_results_types::type_at<search_criteria_types::index_of<SEARCH_CRITERIA_TYPE>> //Return value (The search result that corresponds to the passed criteria. THIS IS THE REASON BECAUSE THE TYPELISTS MUST BE SORTED IN THE SAME ORDER.
search( const SEARCH_CRITERIA_TYPE& criteria)
{
using system_data_type = system_data_types::type_at<search_criteria_types::index_of<SEARCH_CRITERIA_TYPE>>; //The type of the data to be searched.
std::vector<system_data_type>& data = _get_vector<system_data_type>(); //A reference to the vector where that type of data is stored.
//blah, blah, blah (Search along the vector using the criteria parameter....)
}
};
И поисковая система может быть использована следующим образом:
int main()
{
SearchEngine engine;
engine.addData(LuxuryRoom());
engine.addData(NormalRoom());
auto luxury_search_results = engine.search(LuxuryRoomCriteria()); //Search LuxuryRooms with the specific criteria and returns a LuxuryRoomSearchResults instance with the results of the search.
auto normal_search_results = engine.search(NormalRoomCriteria()); //Search NormalRooms with the specific criteria and returns a NormalRoomSearchResults instance with the results of the search.
}
Движок основан на хранении одного вектора для каждого типа данных системы. И движок использует вектор, который хранит эти векторы.
Мы не можем иметь полиморфную ссылку / указатель на векторы разных типов, поэтому мы используем оболочку std::vector
:
struct VectorWrapper
{
virtual ~VectorWrapper() = 0;
};
template<typename T>
struct GenericVectorWrapper : public VectorWrapper
{
std::vector<T> vector;
~GenericVectorWrapper() {};
};
//This template class "builds" the search engine set (vector) of system data types vectors:
template<int type_index>
struct VectorBuilder
{
static void append_data_type_vector(std::vector<VectorWrapper*>& data)
{
data.push_back( new GenericVectorWrapper< system_data_types::type_at<type_index> >() ); //Pushes back a vector that stores the indexth type of system data.
VectorBuilder<type_index+1>::append_data_type_vector(data); //Recursive call
}
};
//Base case (End of the list of system data types)
template<>
struct VectorBuilder<system_data_types::size>
{
static void append_data_type_vector(std::vector<VectorWrapper*>& data) {}
};
Таким образом, реализация SearchEngine::_get_vector<T>
как следует:
template<typename T>
std::vector<T>& get_vector()
{
GenericVectorWrapper<T>* data; //Pointer to the corresponing vector
data = dynamic_cast<GenericVectorWrapper<T>*>(_data_lists[system_data_types::index_of<T>]); //We try a cast from pointer of wrapper-base-class to the expected type of vector wrapper
if( data )//If cast success, return a reference to the std::vector<T>
return data->vector;
else
throw; //Cast only fails if T is not a system data type. Note that if T is not a system data type, the cast result in a buffer overflow (index_of<T> returns -1)
}
Конструктор SearchEngine
использует только VectorBuilder для построения списка векторов:
SearchEngine()
{
VectorBuilder<0>::append_data_type_vector(_data_list);
}
И деструктор перебирает только список, удаляя векторы:
~SearchEngine()
{
for(unsigned int i = 0 ; i < system_data_types::size ; ++i)
delete _data_list[i];
}
Преимущества этого дизайна:
Поисковая система использует точно такой же интерфейс для разных поисков (Поиск с различными типами системных данных в качестве цели). А процесс «привязки» типа данных к соответствующим критериям поиска и результатам выполняется во время компиляции.
Этот интерфейс типа сейф: Вызов SearchEngine::search()
возвращает тип результатов на основе только пройденных критериев поиска. Ошибки результатов назначения обнаруживаются во время компиляции. Например: NormalRoomResults = engine.search(LuxuryRoomCriteria())
генерирует ошибку компиляции (engine.search<LuxuryRoomCriteria>
возвращается LuxuryRoomResults
).
Поисковая система полностью масштабируемый: Чтобы добавить новый тип данных в систему, вам нужно только добавить типы в списки типов. Реализация поисковой системы не меняется.
Класс комнаты
class Room{
public:
enum Type {
Regular,
Luxury,
Celebrity
};
Room(Type rt):roomType(rt), isOpen(true) { }
Type getRoomType() { return roomType; }
bool getRoomStatus() { return isOpen; }
void setRoomStatus(bool isOpen) { this->isOpen = isOpen; }
private:
Type roomType;
bool isOpen;
};
Отель Класс
class Hotel{
std::map<Room::Type, std::vector<Room*>> openRooms;
//std::map<Room::Type, std::vector<Room*>> reservedRooms;
public:
void addRooms(Room &room) { openRooms[room.getRoomType()].push_back(&room); }
auto getOpenRooms() {
std::vector<Room*> allOpenRooms;
for(auto rt : openRooms)
for(auto r : rt.second)
allOpenRooms.push_back(r);
return allOpenRooms;
}
auto getOpenRoomsOfType(Room::Type rt) {
std::vector<Room*> OpenRooms;
for(auto r : openRooms[rt])
OpenRooms.push_back(r);
return OpenRooms;
}
int totalOpenRooms() {
int roomCount=0;
for(auto rt : openRooms)
roomCount += rt.second.size();
return roomCount;
}
};
Клиент UseCase:
Hotel Marigold;
Room RegularRoom1(Room::Regular);
Room RegularRoom2(Room::Regular);
Room LuxuryRoom(Room::Luxury);
Marigold.addRooms(RegularRoom1);
Marigold.addRooms(RegularRoom2);
Marigold.addRooms(LuxuryRoom);
auto allRooms = Marigold.getOpenRooms();
auto LRooms = Marigold.getOpenRoomsOfType(Room::Luxury);
auto RRooms = Marigold.getOpenRoomsOfType(Room::Regular);
auto CRooms = Marigold.getOpenRoomsOfType(Room::Celebrity);
cout << " TotalOpenRooms : " << allRooms.size()
<< "\n Luxury : " << LRooms.size()
<< "\n Regular : " << RRooms.size()
<< "\n Celebrity : " << CRooms.size()
<< endl;
TotalOpenRooms: 4
Люкс: 2
Регулярно: 2
Знаменитости: 0