4

我正在为即将到来的面试练习面向对象设计。我的问题是关于酒店预订系统的设计: - 系统应该能够返回特定类型的开放房间或返回酒店中的所有开放房间。- 酒店有多种类型的房间,如普通、豪华、名人等。

到目前为止,我已经提出了以下课程:

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 方法?

4

6 回答 6

3

遍历子类对象询问它们是什么类型并不是OO设计的一个很好的说明。你真的需要对所有房间都做一些事情,而不知道每个房间是什么类型。例如打印房间的每日房间菜单(不同类型的菜单可能不同)。故意寻找子类对象的类型,虽然没有错,但不是很好的oo风格。如果您只是想这样做,正如其他受访者所说,只需拥有带有一组属性的“房间”。

于 2013-06-16T09:26:09.850 回答
2

您总是可以让房间携带它的真实类型,而不是比较对象类型:

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
      }
    }
};
于 2013-06-16T08:34:07.780 回答
1

不同类型的房间有不同的行为吗?根据您给出的描述,这不是应该使用继承的情况。每个房间都只有一个属性类型,最简单的形式就是一个枚举。

于 2013-06-16T09:13:25.923 回答
0

如果您真的想检查一个房间与其他房间的类型相同,那么typeid()它与任何其他方法一样好 - 调用虚拟方法肯定“更好”(至少从性能角度来看)。

另一种选择是根本没有单独的类,并将房间类型存储为成员变量(这当然是我设计它的方式,但这对于学习面向对象和继承来说不是一个很好的设计——你没有得到当基类满足您的所有需求时继承)。

于 2013-06-16T08:35:56.187 回答
0

最简单的方法是按照@billz 的建议进行 Room 类型枚举。这种方式的问题在于,每次向系统中添加新类型的 Room 时,一定不要忘记给枚举添加一个值并使用一次。您必须确保只使用一次枚举值,每堂课一次。

但是,另一方面,基于继承的设计只有在层次结构的类型具有共同行为时才有意义。换句话说,您希望以相同的方式使用它们,而不管其类型如何。IMPO,OO/继承设计并不是更好的方法。

我做这类事情的怪异且可扩展的方式是通过typelists

通常,您对系统中的每种类型都有不同的搜索条件。而且,在许多情况下,对于不同类型的系统,此搜索的结果是不同的(搜索豪华房间和搜索普通房间不同,您可能有不同的搜索条件和/或想要不同的搜索结果数据)。

为此,系统具有三个类型列表:一个包含数据类型,一个包含搜索条件类型,一个包含搜索结果类型:

using system_data_types     = type_list<NormalRoom,LuxuryRoom>;
using search_criteria_types = type_list<NormalRoomsCriteria,LuxuryRoommsCriteria>;
using search_results_types  = type_list<NormalRoomSearchResults,LuxuryRoomSearchResults>;

请注意,type_lists 以相同的方式排序。这很重要,如下所示。

所以搜索引擎的实现是:

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.
}

该引擎基于为每种系统数据类型存储一个向量。并且引擎使用存储该向量的向量。我们不能拥有指向不同类型向量的多态引用/指针,因此我们使用 a 的包装器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)。

  • 搜索引擎是完全可扩展的:要向系统添加新的数据类型,您只需将类型添加到类型列表中。搜索引擎的执行没有变化

于 2013-06-16T10:23:11.557 回答
0

房间等级

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;
    }

};

客户端用例:

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

于 2017-03-20T16:09:42.767 回答