C++数据分组类和常量访问

C++ data grouping class and const access

本文关键字:常量 访问 数据 C++      更新时间:2023-10-16

这个问题涉及类设计和连贯的接口(我想)。

假设你有一个小课堂来表示道路的"几何"。。。它可以包含许多类似这样的属性和方法。。。

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和nonconst方法。没什么大不了的:我们几乎可以写这样的客户端代码

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;

现在,让我们假设我们想在地图上添加"另一层"信息,这些信息不一定属于地图,而是。。。在客户端代码中对其进行了补充。。。比如,交通标志

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

同样,我们可以编写各种客户端代码,并混合道路和标志。在这一点上,请注意,我并不是真的在做这个应用程序,只是把它作为一个例子。。。

不管怎样,在写了更多的代码之后,我得到了第三层数据。。。这次是"吃的地方"。我不会在这里描述它们,但你会明白:它是独立存在的,但可以与道路或标志共享一定的空间(更像道路,但好吧…)。有了这第三层也是最后一层,我们可以找到一个地方,在那里我们可以获得包含道路、标志和用餐场所信息的文件。我们认为我们可以写一个类,我们将提供文件,它将为我们存储信息

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类作为代理(增加它在应用程序中的响应性),所以我会去:

MapData MD;
MD.add_stop_sign(10.0, 20.0);   //This, in time, proxies to the inner property.

或者我可以添加常量和非常量getter(增加我的头痛),比如:

MapData MD;
float area=MD.get_road_map().get_area();
MD.get_non_const_road_map().add_road(/*blah blah*/);

或者我可以把它搞砸,把这些财产公开:

public:
RoadMap roads;
TrafficSignsMap signs;
PlacesToEatMap places_to_eat;

或者,我可以让getter变得非常常量,因为我要修改数据并完成它(这里没有真正的缺点……我想,假设我得到了一个常量MapData对象,我无论如何都不能更改它):

    RoadMap& get_road_map() {return roads;} 
    TrafficSignsMap& get_signs_map() {return signs;}
    PlacesToEatMap& get_places_to_eat_map() {return places_to_eat;}

同样,请注意,这个场景是在这个问题被编辑时虚构的(否则为什么路线图会存储尺寸??)。考虑到这一点,你会如何应对这种情况?。我正在寻找一种方法,使MapData类尽可能具有可扩展性,以防我想要添加更多的层(应该放弃代理选项),并且尽可能正确。非常感谢。

当然有很多方法可以做到这一点。但从设计的角度来看,保持一致性很重要(请参见此处:"一致性与概念完整性相一致")。

您对三个容器类RoadMapTrafficSignsMapPlacesToEatMap的方法都有以下原则:

  • 容器中的对象是一个私有结构
  • 容器的用户不知道对象,只知道定义对象的数据(例如:路段的四个浮子)
  • 插入(以及检索?)是使用组成对象的数据在容器中进行的

如果您想保持一致,那么您应该对MapData采用相同的方法:使用代理方法(您的第一种选择)。

就我个人而言(但这里我们离开客观事实,进入主观意见),我认为这种设计没有利用面向对象的设计。我并不是说这很糟糕:这样做可能有充分的理由,但不是最佳的。为什么?类的用户无法操作类所围绕的应用程序对象:他不处理路段,只处理路段坐标。例如:如果稍后您决定float不够精确,而应该使用double,则必须对每一段代码进行审查。如果你意识到可能有调谐器,并且你的坐标需要是3D的,那将是一场真正的维护灾难。