Getter 和 Java 中的二传手
Getter and Setters in Java
在 getter 和 setter 中制作一些数据 processing/validation 是个好习惯吗?在维基百科 here 中有 2 个示例:
- setDate 方法将 java.util.Date 日期存储在 3 个单独的私有字段中,例如年、月、日
- getAmount 方法连接 2 个字段数字和货币以及 return 类似“100 美元”的内容。也许amount字段本身根本不存在,getAmount只是一种计算方法。
这些是很好的例子还是最好避免?如果最好避免它,我怎样才能更好地实现上面的这两个例子?
更新:
请不要认真对待带有日期等的例子。只是举个例子,当然可以很蠢
我有过的真实例子
我有一个外部第三方系统,我必须进行集成。这个外部系统期望从我这里得到一些数据,如 class 以及 getter 和 setter。我必须在那里传递 2 个字段,id(类似于 09df723987cd7(比如说 GUID))和 formattedID,类似于 "objecttype/09df723987cd7"。我无法更改此外部系统。
我想像
那样实现
getId() {
return id
}
getFormattedId() {
return objectType + "/" + id;
}
objectType 是此 class 中的另一个字段。
我的问题:是否可以,或者有更优雅的实现方式?
有点。有时 setter 方法可能需要采用不同的类型并将其按摩到 属性 的内部表示中。示例:
class Example {
private Integer value;
public void setValue(Integer value) {
this.value = value;
}
public void setValue(String value) {
try {
this.value = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(String.format("%s contains no valid numeric string.", value));
}
}
}
接受 String
的重载 setValue
是一个有效的例子。但是,一般来说,适当的 getter 和 setter 将直接映射到 属性 的内部表示。因此,例如,setDate
示例将仅对 Date
对象进行操作并设置对象的内部 Date
属性,部分原因是小时、分钟等是Date
的所有部分和分解 Date
在这种情况下是一种不好的做法。
Getter和setter达到封装的目的。
例如,
public class Sample
{
private int somedata; // Encapsulated
public int Get() {return somedata;} //Getter
public void Set(int var) {somedata = var;} // Setter
}
在 OOP 中这样做是一个很好的做法。
为了处理然后设置数据,可能需要 setters
特殊对象,如数据,具有年、月、日三个子部分。
它们可以被重载以处理基本数据。
维基百科 link 说,
如果日期由单独的私有年月日表示
这不推荐它说如果它存在。永远不要玩弄数据类型,它们的设计是有目的的。
可以在字段声明中使用 uisng 验证器框架进行验证,例如
..
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
...
public class xyz{
private int id;
@NotEmpty(message="Name is compulsary")
@Size(min = 3, max = 100,message="Name must be between 3 to 100 characters")
private String personName;
@NotEmpty(message="Person type is compulsary")
private String personType;
private String designation;
private String purpos.......
您提供的示例不合适,至少在您提到的形式和名称中不合适。
我会尝试一些更好的例子:
二传手
您可能希望将它们主要用于验证。例如 setDate(Date d)
可以检查数据是否在某个范围内,例如未来不超过 20 年等(取决于您的要求)。
吸气剂
如果它们包含的不仅仅是简单的逻辑,它们可能代表虚拟属性,即没有基础字段但即时计算的属性。
让我们以 getAmount()
为例:可能没有任何字段 amount
或者由于某些原因(例如没有精度问题),金额可能以美分(或更小)存储。因此 getAmount()
可能 看起来像这样:
public double getAmount() {
return amountInCents / 100.0;
}
请注意,名称 getAmount()
可能会产生误导,因此您最好使用 getAmountInUSD()
或类似的名称。
一般
在大多数情况下,建议在 Java 中使用吸气剂和 setters,因为您可以执行以下操作(列表不完整):
- 添加验证逻辑(到 setters)
- 为虚拟属性添加转换逻辑(setters,getter)
- 定义访问权限,即只读意味着没有 public setter
- 使用基于 Java Beans 规范的库(需要使用 setters 和 getter)
- 解耦 getter/setter 的 client/caller,即如果在某些时候你想添加通过 setter 完成字段访问的验证将不需要客户端更改(除非需要处理验证错误)等等
- 使用 setters 和 getter 进行调试,例如通过在方法上放置一个断点并查看堆栈跟踪以查看调用它的人(dsp_user 提到)
这些做法好吗? Yes
和 No
。原因如下:
1. setDate 方法将 java.util.Date 日期存储在 3 个单独的私有字段中,例如年、月、日。
如果您密切注意 java.util.Date
的代码,它已被弃用,将日期存储在 3 个单独的字段中。这是它被弃用的原因。
A java.util.Date 有日期和时间部分。您可以忽略代码中的时间部分。在这种情况下,date class 将采用 JVM 默认时区定义的一天的开始,并将该时间应用于 Date 对象。因此,您的代码的结果将根据它运行的机器或设置的时区而有所不同。可能不是你想要的。
所以这样做不是好的做法,Java dev 实现并弃用了某些功能。
2。 getAmount 方法连接 2 个字段数字和货币以及 return 类似“100 美元”的东西。
是的,这非常好,除非并且直到您将此值仅用于显示目的而不是用于引用,因为此值是两个其他值的最终串联。
setDate method stores java.util.Date date in 3 separate private
fields like year, month, day
是的,你可以做到。您如何处理日期是您的事,如果您愿意,可以将其存储在字符串中,无论如何都不应该干扰 class.
的用户
验证问题不同。您可以阅读有关 design by contract
的内容
假设你有一个 class 鸿沟:
class Divide {
private int dividend = 0;
private int diviser = 1;
private int result;
private int modulo;
public void setDividend(int dividend) {
this.dividend = dividend;
this.result = this.dividend / this.diviser;
}
public void setDiviser(int diviser) {
this.diviser = diviser;
this.result = this.dividend / this.diviser;
}
...
问题是你是否应该检查 diviser 是否为空。这就是合同设计解决的问题。所以你可以这样写:
public void setDiviser(int diviser) {
if (diviser == 0) throw ... // Zero divide exception
this.diviser = diviser;
this.result = this.dividend / this.diviser;
}
但这非常低效,因为每次设置分隔符时您都在进行此检查,也许调用者也已检查并且操作完成时将再次检查。
所以:
// I have been told "by contract" that diviser won't be zero
public void setDiviser(int diviser) {
this.diviser = diviser;
try {
this.result = this.dividend / this.diviser;
}
catch (Exception e)
{
// do something about it
}
}
还有其他选项,例如使方法可抛出,但如果您想进行有效的验证,请务必小心。当然 Java 不会完全按照合同实施设计,但除了例外情况,您可以保持这种精神。
setters 和 getters
的轶事
我的观点?就像这个世界上的任何东西一样,它不是一个黑白分明的明确界限,什么是使用 getter 和 setters 的最佳方式。
Setterless classes 和 Builder 模式
有时 class 有很多属性(设计一个包含所有属性的构造函数是不切实际的,而且如果你这样做的话,使用起来会非常痛苦)。补充一下,很多时候这样的 classes 在构建后应该是不可变的(所以没有 public setters)。
A Builder pattern with a Fluent interface 得到了东西......好吧......流动
设置时间是否生效?
对于大多数情况,可以在 setters 级别执行验证。然而,这样做并不总是明智的。
例如,在某些情况下,对象可以 "transition" 通过无效状态并仅在 setter 中的一系列调用后达到 "valid and self consistent state"。
例如:"A motorcycle without two wheels is not a valid motorcycle" 并不意味着我不能 setFrontWheel(null)
作为临时阶段 修理我的摩托车...该死的合同设计,我会在最后调用 validateMe
并完成它。
多setters
如果您需要对设置执行验证并且某些值组合没有意义,请使用 multi-setter:
void setDate(int y, int m, int day) {
int maxDaysInMonth=0;
switch(m) {
case 1:
case 3:
...
case 11:
maxDaysInMonth=31;
break;
case 2: // that's feb
maxDaysInMonth=isLeap(y) ? 29 : 28;
break;
case 4:
case 6:
...
maxDaysInMonth=30;
break;
default: // only 12 months in the year
throw something;
}
if(d>=maxDaysInMonth) {
// you catch my drift, yeah?
}
}
吸气剂:
"computed getters" - 就像“100 美元” - 本身不是 属性,许多人会争辩说该方法应该称为 "computeSomething" 或 "toSomeForm"(和"toString"一样),但是...就是这么方便又好记Rectangle.getCentreX
"restricted getters" - 喜欢
protected ArrayList listeners;
// look-but-don't-touch getter
public List getListeners() {
return Collections.unmodifiableList(this.listeners):
}
- "void returning getters" - 当要在作为参数提供的目的地内返回(复制或放置)值时。当您想要拒绝对数据成员本身的直接访问 并避免创建内部数据的副本时很有用 - 也与高性能要求齐头并进。例如
class Rectangle {
void getCentre(Point2D resultHere) {
resultHere.set(minX+width/2, minY+height/2);
}
// and not
// Point2D getCentre() {
// return new Point2D.Double(minX+width/2, minY+height/2);
// }
// because... performance.
}
TL;DR:是的。这正是 setters 和 getters 的意义所在!
首先要理解的是为什么我们首先使用 getters 和 setters。
getters 和 setters 的目的是能够将 class 呈现为 public API,以便用户可以存储它们的对象并以应有的方式操作数据(对于某些 classes,例如不可变的 class,这可能意味着根本不对其进行操作)。
getter 允许我们公开存储在 class 中的信息,而用户不知道这些信息是如何存储的。 phone 号码是存储在号码的每个部分(对于美国号码)的 3 个单独字段中还是存储在单个字段中,这与用户无关。他们只关心以 class 合同
指定的格式获取它
setter 允许用户操作对象。同样,他们不关心当他们传入 Date
对象时,您是否将其拆分为单独的字段。他们只是想知道如果您提供一个 getter 承诺传回等效的 Date
对象,那么他们会得到一个与他们在 setter 中传递的对象等效的 Date
对象。
getter 和 setter 的全部目的是 class 的用户不知道 class 中实际存在哪些字段(这这就是为什么始终将它们设为私有的原因。
为什么将所有字段设为私有如此重要?
通过将所有字段设为私有,我们可以稍后返回并更改我们想要的任何内容,只要它不更改 getter 方法的结果即可。
例如,假设我们有这样一个 class 代表一个人:
public class Person {
private String name;
private String address;
public Person(String firstName, String lastName) {
setName(firstName, lastName);
}
/**
* @return The first and last name as a single String
*/
public String getName() {
return name;
}
public void setName(String firstName, String lastName) {
this.name = firstName + " " + lastName;
}
/**
* @return The entire address as a single String
*/
public String getAddress() {
return address;
}
public void setAddress(String houseNumber, String streetName, String city, String state, String zip) {
this.address = houseNumber + " " + streetName + " " + city + " " + state + " " + zip;
}
}
在创建 class 时,我们决定不需要单独存储名字和姓氏,地址也可以是单个字段(无论出于何种原因)。如果我们想改变它,将名字和姓氏分成两个单独的字段,地址也应该分成单独的字段,会发生什么。
如果我们让字段暴露(通过使它们 public),我们将永远无法更改它们。任何人都可以通过声明 Person.name = "John Doe"
来使用它们。更改字段名称会完全破坏他们的程序。
如果我们将它们保密,我们现在可以为所欲为。我们只需要更新我们的方法,使它们 return 相同的数据。因此,要拆分名称和地址字段,我们将执行以下操作:
public class Person {
private String firstName;
private String lastName;
private String houseNumber;
private String streetName;
private String city;
private String state;
private String zip;
public Person(String firstName, String lastName) {
setName(firstName, lastName);
}
/**
* @return The first and last name as a single String
*/
public String getName() {
return firstName + " " +lastName;
}
public void setName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
/**
* @return The entire address as a single String
*/
public String getAddress() {
return houseNumber + " " + streetName + " " + city + " " + state + " " + zip;
}
public void setAddress(String houseNumber, String streetName, String city, String state, String zip) {
this.houseNumber = houseNumber;
this.streetName = streetName;
this.city = city;
this.state = state;
this.zip = zip;
}
}
我们只是能够更改 class 的整个实现,而不会破坏任何使用我们代码的人的代码。这就是封装的力量,也是它在 OOP 中如此重要的原因。
如果我们觉得用户应该能够直接操作每个字段,那么我们可以为各个字段添加 getters 和 setters。当然,这样做我们现在无法改回并合并字段,因为有人可能正在使用 getters 和 setters.
验证在哪里适合所有这些?
您也询问了验证。现在应该很清楚,class 的 public 方法定义了 class 合约。验证数据非常重要,因为它通过不允许任何人将错误数据放入该对象来强制执行合同。以后你总是可以放宽限制,添加它们是一件更难的事情,并且总是有可能破坏代码。
例如,如果您的 Person
class 中有一个 age
字段。您的 class 中可能有一个 setAge(int age)
方法(更有可能它是根据 birthDate
字段计算的,但现在假设它只是一个 age
字段) .
该方法应如下所示:
public void setAge(int age) {
if (age < 0 || age > 120)
throw IllegalArgumentException("age must be between 0 and 120");
this.age = age;
}
年龄的上限值得商榷,但必须验证下限!没有人可以小于 0 岁。根据您的应用程序,您可能希望提高下限。也许他们需要年满 13 或 18 岁才能使用您的应用程序。
如上所述,晚加限制比晚放宽困难得多。
在 getter 和 setter 中制作一些数据 processing/validation 是个好习惯吗?在维基百科 here 中有 2 个示例:
- setDate 方法将 java.util.Date 日期存储在 3 个单独的私有字段中,例如年、月、日
- getAmount 方法连接 2 个字段数字和货币以及 return 类似“100 美元”的内容。也许amount字段本身根本不存在,getAmount只是一种计算方法。
这些是很好的例子还是最好避免?如果最好避免它,我怎样才能更好地实现上面的这两个例子?
更新: 请不要认真对待带有日期等的例子。只是举个例子,当然可以很蠢
我有过的真实例子
我有一个外部第三方系统,我必须进行集成。这个外部系统期望从我这里得到一些数据,如 class 以及 getter 和 setter。我必须在那里传递 2 个字段,id(类似于 09df723987cd7(比如说 GUID))和 formattedID,类似于 "objecttype/09df723987cd7"。我无法更改此外部系统。
我想像
那样实现getId() {
return id
}
getFormattedId() {
return objectType + "/" + id;
}
objectType 是此 class 中的另一个字段。
我的问题:是否可以,或者有更优雅的实现方式?
有点。有时 setter 方法可能需要采用不同的类型并将其按摩到 属性 的内部表示中。示例:
class Example {
private Integer value;
public void setValue(Integer value) {
this.value = value;
}
public void setValue(String value) {
try {
this.value = Integer.parseInt(value);
} catch (NumberFormatException nfe) {
throw new IllegalArgumentException(String.format("%s contains no valid numeric string.", value));
}
}
}
接受 String
的重载 setValue
是一个有效的例子。但是,一般来说,适当的 getter 和 setter 将直接映射到 属性 的内部表示。因此,例如,setDate
示例将仅对 Date
对象进行操作并设置对象的内部 Date
属性,部分原因是小时、分钟等是Date
的所有部分和分解 Date
在这种情况下是一种不好的做法。
Getter和setter达到封装的目的。
例如,
public class Sample
{
private int somedata; // Encapsulated
public int Get() {return somedata;} //Getter
public void Set(int var) {somedata = var;} // Setter
}
在 OOP 中这样做是一个很好的做法。
为了处理然后设置数据,可能需要 setters 特殊对象,如数据,具有年、月、日三个子部分。 它们可以被重载以处理基本数据。
维基百科 link 说, 如果日期由单独的私有年月日表示
这不推荐它说如果它存在。永远不要玩弄数据类型,它们的设计是有目的的。
可以在字段声明中使用 uisng 验证器框架进行验证,例如
..
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
...
public class xyz{
private int id;
@NotEmpty(message="Name is compulsary")
@Size(min = 3, max = 100,message="Name must be between 3 to 100 characters")
private String personName;
@NotEmpty(message="Person type is compulsary")
private String personType;
private String designation;
private String purpos.......
您提供的示例不合适,至少在您提到的形式和名称中不合适。
我会尝试一些更好的例子:
二传手
您可能希望将它们主要用于验证。例如 setDate(Date d)
可以检查数据是否在某个范围内,例如未来不超过 20 年等(取决于您的要求)。
吸气剂
如果它们包含的不仅仅是简单的逻辑,它们可能代表虚拟属性,即没有基础字段但即时计算的属性。
让我们以 getAmount()
为例:可能没有任何字段 amount
或者由于某些原因(例如没有精度问题),金额可能以美分(或更小)存储。因此 getAmount()
可能 看起来像这样:
public double getAmount() {
return amountInCents / 100.0;
}
请注意,名称 getAmount()
可能会产生误导,因此您最好使用 getAmountInUSD()
或类似的名称。
一般
在大多数情况下,建议在 Java 中使用吸气剂和 setters,因为您可以执行以下操作(列表不完整):
- 添加验证逻辑(到 setters)
- 为虚拟属性添加转换逻辑(setters,getter)
- 定义访问权限,即只读意味着没有 public setter
- 使用基于 Java Beans 规范的库(需要使用 setters 和 getter)
- 解耦 getter/setter 的 client/caller,即如果在某些时候你想添加通过 setter 完成字段访问的验证将不需要客户端更改(除非需要处理验证错误)等等
- 使用 setters 和 getter 进行调试,例如通过在方法上放置一个断点并查看堆栈跟踪以查看调用它的人(dsp_user 提到)
这些做法好吗? Yes
和 No
。原因如下:
1. setDate 方法将 java.util.Date 日期存储在 3 个单独的私有字段中,例如年、月、日。
如果您密切注意 java.util.Date
的代码,它已被弃用,将日期存储在 3 个单独的字段中。这是它被弃用的原因。
A java.util.Date 有日期和时间部分。您可以忽略代码中的时间部分。在这种情况下,date class 将采用 JVM 默认时区定义的一天的开始,并将该时间应用于 Date 对象。因此,您的代码的结果将根据它运行的机器或设置的时区而有所不同。可能不是你想要的。
所以这样做不是好的做法,Java dev 实现并弃用了某些功能。
2。 getAmount 方法连接 2 个字段数字和货币以及 return 类似“100 美元”的东西。
是的,这非常好,除非并且直到您将此值仅用于显示目的而不是用于引用,因为此值是两个其他值的最终串联。
setDate method stores java.util.Date date in 3 separate private fields like year, month, day
是的,你可以做到。您如何处理日期是您的事,如果您愿意,可以将其存储在字符串中,无论如何都不应该干扰 class.
的用户验证问题不同。您可以阅读有关 design by contract
的内容假设你有一个 class 鸿沟:
class Divide {
private int dividend = 0;
private int diviser = 1;
private int result;
private int modulo;
public void setDividend(int dividend) {
this.dividend = dividend;
this.result = this.dividend / this.diviser;
}
public void setDiviser(int diviser) {
this.diviser = diviser;
this.result = this.dividend / this.diviser;
}
...
问题是你是否应该检查 diviser 是否为空。这就是合同设计解决的问题。所以你可以这样写:
public void setDiviser(int diviser) {
if (diviser == 0) throw ... // Zero divide exception
this.diviser = diviser;
this.result = this.dividend / this.diviser;
}
但这非常低效,因为每次设置分隔符时您都在进行此检查,也许调用者也已检查并且操作完成时将再次检查。
所以:
// I have been told "by contract" that diviser won't be zero
public void setDiviser(int diviser) {
this.diviser = diviser;
try {
this.result = this.dividend / this.diviser;
}
catch (Exception e)
{
// do something about it
}
}
还有其他选项,例如使方法可抛出,但如果您想进行有效的验证,请务必小心。当然 Java 不会完全按照合同实施设计,但除了例外情况,您可以保持这种精神。
setters 和 getters
的轶事我的观点?就像这个世界上的任何东西一样,它不是一个黑白分明的明确界限,什么是使用 getter 和 setters 的最佳方式。
Setterless classes 和 Builder 模式
有时 class 有很多属性(设计一个包含所有属性的构造函数是不切实际的,而且如果你这样做的话,使用起来会非常痛苦)。补充一下,很多时候这样的 classes 在构建后应该是不可变的(所以没有 public setters)。
A Builder pattern with a Fluent interface 得到了东西......好吧......流动
设置时间是否生效?
对于大多数情况,可以在 setters 级别执行验证。然而,这样做并不总是明智的。
例如,在某些情况下,对象可以 "transition" 通过无效状态并仅在 setter 中的一系列调用后达到 "valid and self consistent state"。
例如:"A motorcycle without two wheels is not a valid motorcycle" 并不意味着我不能 setFrontWheel(null)
作为临时阶段 修理我的摩托车...该死的合同设计,我会在最后调用 validateMe
并完成它。
多setters
如果您需要对设置执行验证并且某些值组合没有意义,请使用 multi-setter:
void setDate(int y, int m, int day) {
int maxDaysInMonth=0;
switch(m) {
case 1:
case 3:
...
case 11:
maxDaysInMonth=31;
break;
case 2: // that's feb
maxDaysInMonth=isLeap(y) ? 29 : 28;
break;
case 4:
case 6:
...
maxDaysInMonth=30;
break;
default: // only 12 months in the year
throw something;
}
if(d>=maxDaysInMonth) {
// you catch my drift, yeah?
}
}
吸气剂:
"computed getters" - 就像“100 美元” - 本身不是 属性,许多人会争辩说该方法应该称为 "computeSomething" 或 "toSomeForm"(和"toString"一样),但是...就是这么方便又好记
Rectangle.getCentreX
"restricted getters" - 喜欢
protected ArrayList listeners; // look-but-don't-touch getter public List getListeners() { return Collections.unmodifiableList(this.listeners): }
- "void returning getters" - 当要在作为参数提供的目的地内返回(复制或放置)值时。当您想要拒绝对数据成员本身的直接访问 并避免创建内部数据的副本时很有用 - 也与高性能要求齐头并进。例如
class Rectangle { void getCentre(Point2D resultHere) { resultHere.set(minX+width/2, minY+height/2); } // and not // Point2D getCentre() { // return new Point2D.Double(minX+width/2, minY+height/2); // } // because... performance. }
TL;DR:是的。这正是 setters 和 getters 的意义所在!
首先要理解的是为什么我们首先使用 getters 和 setters。
getters 和 setters 的目的是能够将 class 呈现为 public API,以便用户可以存储它们的对象并以应有的方式操作数据(对于某些 classes,例如不可变的 class,这可能意味着根本不对其进行操作)。
getter 允许我们公开存储在 class 中的信息,而用户不知道这些信息是如何存储的。 phone 号码是存储在号码的每个部分(对于美国号码)的 3 个单独字段中还是存储在单个字段中,这与用户无关。他们只关心以 class 合同
指定的格式获取它 setter 允许用户操作对象。同样,他们不关心当他们传入 Date
对象时,您是否将其拆分为单独的字段。他们只是想知道如果您提供一个 getter 承诺传回等效的 Date
对象,那么他们会得到一个与他们在 setter 中传递的对象等效的 Date
对象。
getter 和 setter 的全部目的是 class 的用户不知道 class 中实际存在哪些字段(这这就是为什么始终将它们设为私有的原因。
为什么将所有字段设为私有如此重要?
通过将所有字段设为私有,我们可以稍后返回并更改我们想要的任何内容,只要它不更改 getter 方法的结果即可。
例如,假设我们有这样一个 class 代表一个人:
public class Person {
private String name;
private String address;
public Person(String firstName, String lastName) {
setName(firstName, lastName);
}
/**
* @return The first and last name as a single String
*/
public String getName() {
return name;
}
public void setName(String firstName, String lastName) {
this.name = firstName + " " + lastName;
}
/**
* @return The entire address as a single String
*/
public String getAddress() {
return address;
}
public void setAddress(String houseNumber, String streetName, String city, String state, String zip) {
this.address = houseNumber + " " + streetName + " " + city + " " + state + " " + zip;
}
}
在创建 class 时,我们决定不需要单独存储名字和姓氏,地址也可以是单个字段(无论出于何种原因)。如果我们想改变它,将名字和姓氏分成两个单独的字段,地址也应该分成单独的字段,会发生什么。
如果我们让字段暴露(通过使它们 public),我们将永远无法更改它们。任何人都可以通过声明 Person.name = "John Doe"
来使用它们。更改字段名称会完全破坏他们的程序。
如果我们将它们保密,我们现在可以为所欲为。我们只需要更新我们的方法,使它们 return 相同的数据。因此,要拆分名称和地址字段,我们将执行以下操作:
public class Person {
private String firstName;
private String lastName;
private String houseNumber;
private String streetName;
private String city;
private String state;
private String zip;
public Person(String firstName, String lastName) {
setName(firstName, lastName);
}
/**
* @return The first and last name as a single String
*/
public String getName() {
return firstName + " " +lastName;
}
public void setName(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
/**
* @return The entire address as a single String
*/
public String getAddress() {
return houseNumber + " " + streetName + " " + city + " " + state + " " + zip;
}
public void setAddress(String houseNumber, String streetName, String city, String state, String zip) {
this.houseNumber = houseNumber;
this.streetName = streetName;
this.city = city;
this.state = state;
this.zip = zip;
}
}
我们只是能够更改 class 的整个实现,而不会破坏任何使用我们代码的人的代码。这就是封装的力量,也是它在 OOP 中如此重要的原因。
如果我们觉得用户应该能够直接操作每个字段,那么我们可以为各个字段添加 getters 和 setters。当然,这样做我们现在无法改回并合并字段,因为有人可能正在使用 getters 和 setters.
验证在哪里适合所有这些?
您也询问了验证。现在应该很清楚,class 的 public 方法定义了 class 合约。验证数据非常重要,因为它通过不允许任何人将错误数据放入该对象来强制执行合同。以后你总是可以放宽限制,添加它们是一件更难的事情,并且总是有可能破坏代码。
例如,如果您的 Person
class 中有一个 age
字段。您的 class 中可能有一个 setAge(int age)
方法(更有可能它是根据 birthDate
字段计算的,但现在假设它只是一个 age
字段) .
该方法应如下所示:
public void setAge(int age) {
if (age < 0 || age > 120)
throw IllegalArgumentException("age must be between 0 and 120");
this.age = age;
}
年龄的上限值得商榷,但必须验证下限!没有人可以小于 0 岁。根据您的应用程序,您可能希望提高下限。也许他们需要年满 13 或 18 岁才能使用您的应用程序。
如上所述,晚加限制比晚放宽困难得多。