C++ data grouping class and const access
这个问题涉及类设计和一致的接口(我猜)。
假设您有一个小类来表示道路的"几何体"…它可以包含许多这样的属性和方法…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | class RoadMap { private: struct RoadPiece { float x1, y1, x2, y2; }; std::string name; float area_width; float area_height; std::vector<RoadPiece> pieces; public: const std::string& get_name() const {return name;} float get_width() const {return area_width;} float get_height() const {return area_height;} float get_area() const {return area_width * area_height;} void set_width(float v) {area_width=v;} void set_height(float v) {area_height=v;} void set_name(const std::string v) {name=v;} void add_road_piece(float x1, float y1, float x2, float y2) { //... } } |
如您所见,我们混合了const和non-const方法。没什么大不了的:我们可以像这样编写客户机代码
1 2 3 4 5 6 7 8 9 | RoadMap m; m.set_width(100.0); m.set_height(150.0); m.set_name("Northern Hills"); //Tedious code here... std::cout<<"The area of the map is"<<m.get_area()<<std::endl; |
现在,让我们设想一下,我们要向地图中添加"另一层"信息,而这些信息不一定属于地图,而是……在客户端代码中补充它…比如说,交通标志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class TrafficSignsMap { private: struct Sign { enum class types {STOP, YIELD, STEP_ON_IT}; types type; float x; float y; } std::vector<Sign> signs; public: void add_stop_sign(float x, float y) {/*Blah blah*/} void add_yield_sign(float x, float y) {/*Blah blah*/} void add_step_on_it_sign(float x, float y) {/*Blah blah*/} const std::vector<Sign>& get_all_signs() {return signs;} const std::vector<const Sign const *> get_signs_in_area(float x1, float y1, float x2, float y2) { //Do some calculations, populate a vector with pointers to signs, return it... } } |
同样,我们可以编写各种客户代码,混合道路和注册。在这一点上,请注意,我并不是真的在做这个应用程序,只是以它为例…
总之,在写了更多的代码之后,我得到了第三层数据…这次是"吃的地方"。我不会在这里描述它们,但你会明白:它本身就存在,但可以与道路或标志共享一定的空间(更像道路,但很好…)。通过这第三层和最后一层,我们可以找到一个地方,在那里我们可以获得有关道路、标志和吃饭地点的信息文件。我们认为我们可以编写一个类来提供文件,它将为我们存储信息。这样地:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class MapData { private: RoadMap roads; TrafficSignsMap signs; PlacesToEatMap places_to_eat; public: MapData(const std::string& filename) { std::ifstream(filename); //Read the file... populate our properties... } const RoadMap& get_road_map() const {return roads;} const TrafficSignsMap& get_signs_map() const {return signs;} const PlacesToEatMap& get_places_to_eat_map() const {return places_to_eat;} }; |
事情是这样的……一旦所有数据都分组到一个大容器中,我们就应该同时提供常量和非常量访问,对吗?.我想获取所有的常量数据,但我也应该能够添加新的吃的地方,这是我不能用当前接口做的。
现在我知道我可以使用mapdata类作为代理(增加它在应用程序中的响应能力),所以我要:
1 2 | MapData MD; MD.add_stop_sign(10.0, 20.0); //This, in time, proxies to the inner property. |
或者我可以添加const和non-const getters(增加我的头痛),比如:
1 2 3 | MapData MD; float area=MD.get_road_map().get_area(); MD.get_non_const_road_map().add_road(/*blah blah*/); |
或者我可以把它搞砸,把这些财产公之于众:
1 2 3 4 5 | public: RoadMap roads; TrafficSignsMap signs; PlacesToEatMap places_to_eat; |
或者我可以把getters设为非常量,因为我要修改数据并完成它(这里没有真正的缺点…我想,假设我得到了一个const-mapdata对象,我无论如何都不能更改它):
1 2 3 | RoadMap& get_road_map() {return roads;} TrafficSignsMap& get_signs_map() {return signs;} PlacesToEatMap& get_places_to_eat_map() {return places_to_eat;} |
请再次注意,由于这个问题被修改,场景已经被编好了(为什么地图商店的尺寸会是其他的尺寸呢??)考虑到这一点,你会怎么处理这种情况?.I正在寻找一种方法,以便在需要添加更多层(应放弃代理选项)以及尽可能正确的层时,尽可能使mapdata类具有可扩展性。谢谢。
当然有很多方法可以做到这一点。但是从设计的角度来看,保持一致性是很重要的(参见这里的"一致性与概念完整性一致")。
您对三个容器类
- 容器中的对象是私有结构
- 容器的用户不知道对象,但只知道定义对象的数据(例如:道路段的四个浮动)。
- 插入(以及检索?)使用构成对象的数据在容器中生成。
如果您想保持一致,那么您应该对
我个人(但这里我们要离开客观事实,进入主观观点)认为这个设计没有利用面向对象的设计。我不认为这很糟糕:这样做可能有很好的理由。但不是最佳的。为什么?你的类的用户不能操纵你的类所设计的应用程序对象:他不处理路段,只处理路段坐标。例如:如果以后你认为