用代码理解 OOP 中的封装
Understanding Encapsulation in OOP with code
为了理解封装的概念,我遇到了这个定义 "Combining the attributes and methods in the same entity in such a way as to hide what should be hidden and make visible what is intended to be visible"。
但是同样练习,我不确定下面哪个代码更适合 OOP:
public class Square {
//private attribute
private int square;
//public interface
public int getSquare(int value) {
this.square = value * value;
return this.square;
}
}
或
public class Square {
//private attribute
private int square;
//public interface
public int getSquare(int value) {
this.square = calculateSquare(value);
return this.square;
}
//private implementation
private int calculateSquare(int value) {
return value * value;
}
}
如果任何代码用于封装目的,那么该代码就是正确的。封装的目的是提供一个对其他 classes 隐藏变量的过程(即通过将变量设为私有),并为其他 classes 提供一种访问和修改变量的方法.您的两个代码都正确地达到了这个目的。
如果您将 "calculateSquare(int value)" 方法用作 "public" 那么就会有问题。其他 class 可以不使用 set/get 方法直接调用此方法。所以就你的这个方法来说 "private" 我认为这两种代码都可以。
封装是关于隐藏客户端代码的实现和结构细节。此外,它还与连贯性有关:将彼此高度相关的事物紧密地放在一起。
例如,考虑管理足球队球员的 class:
public class FootballTeam {
public final List<Player> players = new ArrayList<>();
}
客户端代码可以访问玩家列表、查找他们、添加玩家等等:
public class FootballManager {
private final FootballTeam team = new FootballTeam();
public void hirePlayer(Player player) {
team.players.add(player);
}
public void firePlayer(int jerseyNo) {
Optional<Player> player = team.players.stream()
.filter(p -> p.getJerseyNo() == jerseyNo)
.findFirst();
player.ifPresent(p -> team.players.remove(p));
}
}
现在,如果有人决定将字段 FootballTeam.players
更改为 Map<Integer, Player>
,将球员球衣号码映射到球员,客户端代码就会中断。
此外,客户端代码处理与播放器密切相关的方面/功能。为了保护客户端代码并确保 FootballTeam
实现的可变性,隐藏所有实现细节,使玩家相关功能接近结构,代表团队并减少 public 界面:
public class FootballTeam {
private final Map<Integer, Player> players = new HashMap<>();
public void addPlayer(Player player) {
players.put(player.getJerseyNo(), player);
}
public Optional<Player> lookupPlayer(int jerseyNo) {
return Optional.ofNullable(players.get(jerseyNo));
}
public void remove(Player player) {
players.remove(player.getJerseyNo());
}
}
public class FootballManager {
private final FootballTeam team = new FootballTeam();
public void hirePlayer(Player player) {
team.addPlayer(player);
}
public void firePlayer(int jerseyNo) {
team.lookupPlayer(jerseyNo)
.ifPresent(player -> team.remove(player));
}
}
Combining the attributes and methods in the same entity in such a way as to hide what should be hidden and make visible what is intended to be visible
这是一个可能具有误导性的陈述。你不是 hiding
任何人的任何东西。它也与方法或字段无关。不幸的是,这几乎是每个地方的措辞方式。
如何
在编写任何程序(无论是函数、class、模块还是库)时,我们都将我们正在处理的部分视为 my代码,每隔一个代码作为我的客户代码。现在假设所有 客户端代码 都是别人写的,而不是你。您只需编写 this 代码。假设这一点,即使您是整个项目中唯一一个人。
现在客户端代码需要与我的代码交互。所以 我的代码 应该很友善和体面。封装的概念说,我将 我的代码 分为两部分,(1) 客户端代码 应该被打扰,(2) 客户端代码不应该被打扰。实现封装的 OO 方法是使用 public 和 private 等关键字。实现这一点的非 OO 方法是像前导下划线这样的命名约定。请记住,您没有隐藏,您只是将其标记为 none-of-your-business
.
为什么
那我们为什么要封装东西呢? 我的代码应该如何组织成public和私有区域?当 someone
使用 我的代码 时,他们当然会使用整个东西,而不仅仅是 public 东西,所以为什么私有是 none-of-their-business
?注意这里像 someone
和 their
这样的词可以指代你自己——但只能在处理另一段代码时使用。
答案是易于测试和维护。如果经过详尽的测试,一个完整的项目可能是一项艰巨的任务。所以至少,当你完成编码时,你只需测试我的代码 public 方面 。您没有测试任何 客户端代码 ,您没有测试任何 我的代码 的私有方面。这减少了测试工作量,同时保留了足够的覆盖率。
另一方面是可维护性。 我的代码 永远不会完美,它需要修改。由于错误修复或增强,我的代码将需要修补。那么当 my code 的新版本可用时,client code 会受到多大影响? None,如果更改在私有区域。此外,在计划变更时,我们会尽量将其限制在私有区域。因此,从客户的角度来看,这种变化是没有影响的。我的代码 public 方面的更改几乎总是需要更改 客户端代码 ,现在需要测试。在规划 my code 的大图时,我们尝试最大化 private regions 下的面积,最小化 public regions 下的面积。
还有更多
封装的思想与抽象的思想联系在一起,抽象的思想又与多态性的思想联系在一起。 None 其中完全是关于 OO 的。即使在 C 甚至 Assembly 这样的非 OO 世界中,这些也适用。实现这些的方式不同。即使这也适用于计算机以外的事物。
污水处理过程,例如
封装在排水沟的public界面内。一般 public 只关心下水道。处理、处置、回收是一般public的业务none。因此,污水管理可以被视为 -
抽象实体 - 一个只有排水管的接口。不同的政府和公司以自己的方式实施。现在一个城市可能有一个永久性的污水管理系统,或者它可以定期 -
切换供应商。五十年的政府运作,情况很糟糕,但是一旦他们承包了那个BigCorp Inc,现在人们可以自由呼吸了。我们只是做了多态。我们切换了实现,保持 public 接口相同。政府和 BigCorp Inc 使用相同的排水沟,但它们有自己的处理设施,这些设施被封装起来并且可以多态切换。
在你的代码中
在您选择封装存储的两个代码中,该字段都是私有的。这是一个很好的方法,当然也是面向对象的方式。在您的两个代码中,算法也被封装 - 即对客户端不可见。好的。在您的第二个代码中,您继续在单独的非 public 方法中提取算法。这是值得称赞的方法,尽管对于做一些微不足道的事情来说显然有点矫枉过正。更好的OOnone越少
你在第二个代码中所做的甚至有一个名字:strategy pattern。尽管在这里它是无用的(而且是矫枉过正),但在假设您正在处理非常大的数字的场景中它可能很有用,以至于计算它们的平方需要很长时间。在这种情况下,您可以保护 calculateSquare
方法,使用 class FastButApproxSquare extends Square
,并使用不同的算法覆盖 calculateSquare
方法,这样可以更快地计算出近似值.这样你就可以做多态性。需要精确值的人将使用 Square
class。需要近似值的人将使用 FastButApproxSquare
class.
为了理解封装的概念,我遇到了这个定义 "Combining the attributes and methods in the same entity in such a way as to hide what should be hidden and make visible what is intended to be visible"。
但是同样练习,我不确定下面哪个代码更适合 OOP:
public class Square {
//private attribute
private int square;
//public interface
public int getSquare(int value) {
this.square = value * value;
return this.square;
}
}
或
public class Square {
//private attribute
private int square;
//public interface
public int getSquare(int value) {
this.square = calculateSquare(value);
return this.square;
}
//private implementation
private int calculateSquare(int value) {
return value * value;
}
}
如果任何代码用于封装目的,那么该代码就是正确的。封装的目的是提供一个对其他 classes 隐藏变量的过程(即通过将变量设为私有),并为其他 classes 提供一种访问和修改变量的方法.您的两个代码都正确地达到了这个目的。
如果您将 "calculateSquare(int value)" 方法用作 "public" 那么就会有问题。其他 class 可以不使用 set/get 方法直接调用此方法。所以就你的这个方法来说 "private" 我认为这两种代码都可以。
封装是关于隐藏客户端代码的实现和结构细节。此外,它还与连贯性有关:将彼此高度相关的事物紧密地放在一起。
例如,考虑管理足球队球员的 class:
public class FootballTeam {
public final List<Player> players = new ArrayList<>();
}
客户端代码可以访问玩家列表、查找他们、添加玩家等等:
public class FootballManager {
private final FootballTeam team = new FootballTeam();
public void hirePlayer(Player player) {
team.players.add(player);
}
public void firePlayer(int jerseyNo) {
Optional<Player> player = team.players.stream()
.filter(p -> p.getJerseyNo() == jerseyNo)
.findFirst();
player.ifPresent(p -> team.players.remove(p));
}
}
现在,如果有人决定将字段 FootballTeam.players
更改为 Map<Integer, Player>
,将球员球衣号码映射到球员,客户端代码就会中断。
此外,客户端代码处理与播放器密切相关的方面/功能。为了保护客户端代码并确保 FootballTeam
实现的可变性,隐藏所有实现细节,使玩家相关功能接近结构,代表团队并减少 public 界面:
public class FootballTeam {
private final Map<Integer, Player> players = new HashMap<>();
public void addPlayer(Player player) {
players.put(player.getJerseyNo(), player);
}
public Optional<Player> lookupPlayer(int jerseyNo) {
return Optional.ofNullable(players.get(jerseyNo));
}
public void remove(Player player) {
players.remove(player.getJerseyNo());
}
}
public class FootballManager {
private final FootballTeam team = new FootballTeam();
public void hirePlayer(Player player) {
team.addPlayer(player);
}
public void firePlayer(int jerseyNo) {
team.lookupPlayer(jerseyNo)
.ifPresent(player -> team.remove(player));
}
}
Combining the attributes and methods in the same entity in such a way as to hide what should be hidden and make visible what is intended to be visible
这是一个可能具有误导性的陈述。你不是 hiding
任何人的任何东西。它也与方法或字段无关。不幸的是,这几乎是每个地方的措辞方式。
如何
在编写任何程序(无论是函数、class、模块还是库)时,我们都将我们正在处理的部分视为 my代码,每隔一个代码作为我的客户代码。现在假设所有 客户端代码 都是别人写的,而不是你。您只需编写 this 代码。假设这一点,即使您是整个项目中唯一一个人。
现在客户端代码需要与我的代码交互。所以 我的代码 应该很友善和体面。封装的概念说,我将 我的代码 分为两部分,(1) 客户端代码 应该被打扰,(2) 客户端代码不应该被打扰。实现封装的 OO 方法是使用 public 和 private 等关键字。实现这一点的非 OO 方法是像前导下划线这样的命名约定。请记住,您没有隐藏,您只是将其标记为 none-of-your-business
.
为什么
那我们为什么要封装东西呢? 我的代码应该如何组织成public和私有区域?当 someone
使用 我的代码 时,他们当然会使用整个东西,而不仅仅是 public 东西,所以为什么私有是 none-of-their-business
?注意这里像 someone
和 their
这样的词可以指代你自己——但只能在处理另一段代码时使用。
答案是易于测试和维护。如果经过详尽的测试,一个完整的项目可能是一项艰巨的任务。所以至少,当你完成编码时,你只需测试我的代码 public 方面 。您没有测试任何 客户端代码 ,您没有测试任何 我的代码 的私有方面。这减少了测试工作量,同时保留了足够的覆盖率。
另一方面是可维护性。 我的代码 永远不会完美,它需要修改。由于错误修复或增强,我的代码将需要修补。那么当 my code 的新版本可用时,client code 会受到多大影响? None,如果更改在私有区域。此外,在计划变更时,我们会尽量将其限制在私有区域。因此,从客户的角度来看,这种变化是没有影响的。我的代码 public 方面的更改几乎总是需要更改 客户端代码 ,现在需要测试。在规划 my code 的大图时,我们尝试最大化 private regions 下的面积,最小化 public regions 下的面积。
还有更多
封装的思想与抽象的思想联系在一起,抽象的思想又与多态性的思想联系在一起。 None 其中完全是关于 OO 的。即使在 C 甚至 Assembly 这样的非 OO 世界中,这些也适用。实现这些的方式不同。即使这也适用于计算机以外的事物。
污水处理过程,例如
封装在排水沟的public界面内。一般 public 只关心下水道。处理、处置、回收是一般public的业务none。因此,污水管理可以被视为 -
抽象实体 - 一个只有排水管的接口。不同的政府和公司以自己的方式实施。现在一个城市可能有一个永久性的污水管理系统,或者它可以定期 -
切换供应商。五十年的政府运作,情况很糟糕,但是一旦他们承包了那个BigCorp Inc,现在人们可以自由呼吸了。我们只是做了多态。我们切换了实现,保持 public 接口相同。政府和 BigCorp Inc 使用相同的排水沟,但它们有自己的处理设施,这些设施被封装起来并且可以多态切换。
在你的代码中
在您选择封装存储的两个代码中,该字段都是私有的。这是一个很好的方法,当然也是面向对象的方式。在您的两个代码中,算法也被封装 - 即对客户端不可见。好的。在您的第二个代码中,您继续在单独的非 public 方法中提取算法。这是值得称赞的方法,尽管对于做一些微不足道的事情来说显然有点矫枉过正。更好的OOnone越少
你在第二个代码中所做的甚至有一个名字:strategy pattern。尽管在这里它是无用的(而且是矫枉过正),但在假设您正在处理非常大的数字的场景中它可能很有用,以至于计算它们的平方需要很长时间。在这种情况下,您可以保护 calculateSquare
方法,使用 class FastButApproxSquare extends Square
,并使用不同的算法覆盖 calculateSquare
方法,这样可以更快地计算出近似值.这样你就可以做多态性。需要精确值的人将使用 Square
class。需要近似值的人将使用 FastButApproxSquare
class.