我理解为什么我们应该避免使用getter / setter,但我不知道如何避免使用它们。
例如,我有三个类,如下所示,
A
(private: point_B
)B
(private: point_C
)C
(private: val_C
) A有一个私有成员point_B
,它是一个指向B
的指针,而B
也有一个私有成员point_C
,它是一个指向{的指针{1}}。 C
具有私有C
值int
。
如何访问val_C
中的val_C
?
在这种情况下,
A
是一个名为A
的类,其地址为state
。point_B
是一个名为B
的类,其指针调用node
。pointer_C
是一个名为C
的类,它有两个名为base_file
和file
的派生类。请参阅参考here 正确的做法是让实现指定类。
答案是不要在每个类中访问私有值,而是实现使用它们的函数。这就解释了为什么首先将它们设为私有。
答案 0 :(得分:8)
也许有点澄清 - 吸气剂和制定者并不是要不惜一切代价避免;他们有自己的位置。人们说应该避免的原因是因为良好的面向对象程序设计的一个目标是封装 - 也就是说,每个类应该将自己实现的细节尽可能保密,因此,该类用户不需要知道(或关心)该课程的实施方式。随着程序变得越来越大,越来越复杂,这变得越来越重要,因为人类程序员只能立刻在他/她的头脑中保留这么多细节,如果程序员必须记住C类工作的一切,同时编写/调试类A,这是一个额外/不必要的认知负担,在某些时候会导致程序员的大脑爆炸。
所以,回到主要问题 - 如何避免吸气者和制定者 - 这样做的方法是定义你的课程'接口处于更高的抽象级别,而不是状态变量的简单存储库。 (毕竟,如果你想要一个简单的状态变量集合,根本就没有理由使用C ++类,你可以简单地声明一个C风格的结构)
例如,如果您的C类旨在表示一个老虎机,那么C类的不良接口可能包含许多getter和setter,如下所示:
int getNumCoins() const {return numCoins;}
void setNumCoins(int newCoinCount) {numCounts = newCoinCount;}
void setDisplayedResult(const string & displayStr) {result = displayStr;}
int getDisplayedResult() const {return result;}
......而被迫使用C类的可怜程序员必须编写如下代码:
playersWallet--; // take a coin out of the player's wallet
c.setNumCoins(c.getNumCoins()+1); // insert the coin into the machine
string newResult = "10 J A"; // somehow figure out what the machine should display
c.setDisplayedResult(newResult); // and make the machine display it
if (c.getDisplayedResult() == "7 7 7")
{
cout << "YOU ARE WINNER!" << endl;
int numCoinsWon = 5000; // jackpot!
c.setNumCoins(c.getNumCoins()-numCoinsWon); // deduct from machine's balance
playersWallet += numCoinsWon; // add to player's balance
}
[... and so on...]
请注意,在上面的代码中,程序员必须考虑老虎机的所有内部机制,并编写自己的代码来处理其操作的每一步。另一方面,通过良好的封装,老虎机的公共界面将更简单,更不透明,如下所示:
// returns the number of coins the player won on this round
int pullTheBigLever();
...使用此API的程序员可能会编写如下代码:
playersWallet += (c.pullTheBigLever() - 1); // -1 for the coin the player put in
请注意,只有一行代码,并且程序员根本不必考虑老虎机内部的工作原理。这避免了爆炸 - 程序员 - 脑 - 综合症,同样重要的是,它意味着您(或其他人)可以稍后返回并更改老虎机如何工作的私有实现,而不会破坏与老虎机交互的代码。
那么获取者和制定者何时可以接受?答:当真的没有任何更高级别的抽象时。如果您正在编写代表灯光开关的类,那么只需检查开关的当前位置,或为其指定新位置,可能就是您需要的所有功能。但是在许多(大多数?)情况下,你正在实现比这更复杂的功能,而且你可以隐藏在公共界面后面的复杂性越多,那个类(包括你)的更快乐的用户就会。
答案 1 :(得分:2)
简短的答案,在OOP中,课程应具有&#34;属性&#34;作为其公共API的一部分。属性可以包含getter,setter和change notifications等内容。另一个getter直接返回一个私有成员变量,这是一个实现细节,可以根据需要进行更改。区分属性的概念和成员变量的概念。
在考虑这个问题时,直接回答你的问题是,除了拥有不必要的可读属性外,你应该尽量避免&#34;除了具有不必要的可读属性。
请注意,面向对象语言中的属性通常没有明确的语法或支持(流行的反例:C#),所以很容易认为它们与成员变量是一样的有一个二传手和一个吸气剂。但重叠有点重合,而不是在使用课时你应该关心的事情。在某种程度上,成员变量没有getter,只有属性的getter,即使它恰好用成员变量映射1:1。
答案 2 :(得分:1)
如何避免在C ++中使用getter / setter。
为了避免setter / getter,访问C类数据属性的所有代码都必须是C类方法的一部分。
替代措辞:在类中引入使用data属性的代码。
更新2016/01/25
一个例子会有帮助吗?我发现避免吸气者和闯入者(以及公共数据和朋友等)是微不足道的。我想我已经习惯了。
我最近完成了生命游戏的另一种实现。整个游戏是观看细胞变化模式的娱乐价值。来自一小组规则的令人印象深刻的复杂行为。
我的班级Cell_t只有私人数据,没有getter,没有setter,也没有朋友。没有其他类可以访问任何单元格数据。
以下是我游戏中那一部分的片段,说明如果没有吸气者,制定者和朋友创造不良的耦合和凝聚力,那么生活是多么容易:
// somewhere in GameOfLife exists
std::vector<Cell_t> m_ptCellVec; // a vector of cell ptrs
GameOfLife::exec(...)
{
// ... preliminary stuff
do {
// ... some preliminary stuff
// NOTE 1
for ( auto it : m_ptCellVec ) it->countNeighbor();
// NOTE 2
for ( auto it : m_ptCellVec ) { it->updateDisplay();}
// .... more stuff
if(timeElapsed > timeLimit) break;
if(m_generation > genLimit) break;
}while(1);
}
注1 - GameOfLife类不计算neigbors ...每个单元格都有自己的计数。下一个状态是根据这些计算得出的。
注2 - GameOfLife类不更新显示...每个单元更新它自己的小块屏幕。
那么,没有Cell_t状态,下一个状态,生活邻居数或死邻居数等的吸气剂。
关于这两个类的这方面
The cohesion (of Cell_t) is functional, the most desirable.
The coupling (of GameOfLife_t to Cell_t) is 'none', also the most
desirable.
Changing the name or type of a Cell_t private data attribute has no
impact on any other code.
哦,我经常添加一个调试例程(另一个例子):
std::string Cell_t dump() {
std::stringstream ss;
ss << // .... anything you want to 'dump' from this instance
return (ss.str());
}
我使用方法名称dump()来表示“更深层次”的意图。调查特定Cell_t的活动...我有时会生成状态变化的表格数据,带有时间戳。
我经常使用一个名为show()的非常类似的方法,它通常为用户提供一个字符串...
这两个例子或许可以说明吸气剂只是绕过设计过程的一个重要方面 - 命名你正在做的事情。
答案 3 :(得分:0)
我相信问题中陈述的问题可以修改。问题不应该是“我怎样才能避免吸气者和杀手?”。这个问题还与其他问题有关,例如“这种方法应该是非静态成员,静态成员,朋友还是帮助者?”或“这个财产应该是私有的还是受保护的?”。问自己的一个更好的问题是,“谁需要访问特定的财产”。
编写易于维护的类的一种方法是限制可以访问特定属性的函数的数量。这并不一定意味着任何函数都不应该访问私有属性,或者永远不应该使用getter / setter。以类std::vector
为例。这可以简化为类似的东西(有很多预留)。 vector
的实际实现通常要复杂得多,可能有不同的内部实现,但这种简化的结构将用于表明观点。
template<class T, class Allocator<T> a = basic_allocator<T>>
class vector {
size_t sz;
size_t cap;
Allocator a;
T* elem;
// ... private methods
public:
// public methods and operators.
}
此类允许开发人员访问存储数据的内部数组中的所有元素。这可以通过运算符[]
(未选中)或通过函数at
(已选中)完成。开发人员拥有读取或写入这些元素的完全权限。如果没有此访问权限,vector
类将毫无用处,人们将会恢复使用数组。该课程还通过方法sz
和cap
为size()
和capacity()
提供了获取者。但是,sz
和cap
被视为内部信息,开发人员不得直接更改这些信息。相反,开发人员可以使用push_back()
,pop_back()
,shrink_to_fit()
,resize()
等方法,...添加或删除数据,管理分配的内存等...原因这些操作需要一些非常先进的内存处理,修改这些变量会导致泄漏和/或崩溃。此外,开发人员确实不需要为这些抽象而烦恼,因为开发人员只需要数组中的元素。
因此,总结封装是好的,需要加以考虑。但是,这并不意味着永远不允许开发人员直接修改某些类的属性。