这个问题涉及类设计和连贯的接口(我猜)。
假设你有一个小班来代表一条道路的“几何”......它可能包含许多这样的属性和方法......
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和非const方法。没什么大不了的:我们几乎可以写这样的客户端代码
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...
}
}
同样,我们可以编写各种客户端代码并混合道路和注册。在这一点上,请注意我并没有真正做这个应用程序,只是把它作为一个例子......
无论如何,在编写了更多代码后,我带来了第三层数据......这次是“PlacesToEat”。我不会在这里描述它们,但是你得到漂移:它本身存在但可以与道路或标志共享一定的空间(更像是道路,但是......)。通过这个第三层也是最后一层,我们找到了一个地方,我们可以获取包含道路,标志和吃饭地点信息的文件。我们认为我们可以写一个我们将提供文件的类,它会为我们存储信息。像这样:
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;}
};
这就是事情......一旦所有数据被分组到一个大容器中,我们应该提供const和非const访问,对吧?我想获得所有const数据,但我也应该能够添加新的吃饭地点,这是我当前界面无法做到的。
现在我知道我可以使用MapData类作为代理(增加它在应用程序中的责任)所以我会去:
MapData MD;
MD.add_stop_sign(10.0, 20.0); //This, in time, proxies to the inner property.
或者我可以添加const和非const 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非const,因为我打算修改数据并完成它(这里没有真正的缺点......我猜,我说得到一个const MapData对象,我不应该无论如何都能改变它:)
RoadMap& get_road_map() {return roads;}
TrafficSignsMap& get_signs_map() {return signs;}
PlacesToEatMap& get_places_to_eat_map() {return places_to_eat;}
请再次注意,该问题已经编制完成,因为这个问题已被编辑(为什么道路地图存储维度呢?)。考虑到这一点,你会如何处理这种情况?我正在寻找一种方法,我可以将MapData类尽可能地扩展,以防我想要添加更多层(应该丢弃代理选项)以及尽可能正确的层。非常感谢。
答案 0 :(得分:1)
当然有很多方法可以做到这一点。但从设计的角度来看,保持一致(see here: "consistency coincides with conceptual integrity")非常重要。
您对三个容器类RoadMap
,TrafficSignsMap
和PlacesToEatMap
的方法都有以下原则:
如果您想保持一致,那么您应该对MapData
采用相同的方法:使用代理方法(您的第一个选择)。
个人(但在这里我们要留下客观事实并输入主观意见)我认为这种设计并没有利用面向对象的设计。我不是说它很糟糕:这样做可能有充分的理由。但不是最佳的。为什么?您的课程的用户无法操纵您的课程所设计的应用程序对象:他不使用路段,而只使用段坐标。例如:如果稍后您决定float
不够精确且应使用double
,则必须审核每一段代码。如果您意识到可能存在tunel并且您的坐标需要为3D,则它将是真正的维护灾难