在getter和setter中进行一些数据处理/验证是一种好习惯吗?在维基百科here中有两个例子:
这些好例子还是最好避免它?如果最好避免它,我怎样才能更好地实现上面的这两个例子?
更新: 请不要以日期等为例严肃对待。这只是一个例子,当然它可能是愚蠢的。
我的真实例子。
我有一个外部第三方系统,我必须进行集成。这个外部系统期望我将一些数据作为具有getter和setter的类。我必须在那里传递2个字段,id(类似09df723987cd7(让我们说GUID))和formattedID类似" objecttype / 09df723987cd7"。我不能改变这个外部系统。
我想像
那样实现它db.url
objectType是此类中的另一个字段。
我的问题:是否可以,或者有更优雅的方式来实现它?
答案 0 :(得分:2)
您提供的示例并不合适,至少不是表格和您提到的名称。
我会尝试一些更好的例子:
<强>塞特斯强>
您可能希望将它们用于验证。例如,setDate(Date d)
可以检查数据是否在某个范围内,例如未来不超过20年等(取决于您的要求)。
<强>吸气剂强>
如果它们包含的不仅仅是简单的逻辑,它们可能代表虚拟属性,即没有基础字段但是在运行中计算的属性。
我们以getAmount()
为例:可能没有任何字段amount
,或者由于某种原因,数量可能以美分(或更小)存储(例如没有精度问题)。因此getAmount()
可能看起来像这样:
public double getAmount() {
return amountInCents / 100.0;
}
请注意,名称getAmount()
可能会产生误导,因此您最好使用getAmountInUSD()
或类似名称。
常规强>
在大多数情况下,使用Java中的getter和setter是可行的,因为你可以使用以下内容(列表未完成):
答案 1 :(得分:1)
有关制定者和吸气者的轶事
我的观点?就像这个世界上的任何东西一样,它不是一个黑白分明的切割使用吸气剂和制定者的最佳方式。
Setterless类和Builder模式
有时候一个类有大量的属性(设计一个构造函数来获取所有属性是不切实际的,然后如果你这样做,那么使用它是非常痛苦的)。补充,很多时候这样的类在构造之后应该是不可变的(所以没有公共制定者)。
A Builder pattern with a Fluent interface让事情......好吧......流动
要在设定时验证还是不验证?
对于大多数情况,可以在设置者级别执行验证。但是,做到这一点并不总是明智的。
例如,有些情况下对象可以&#34;转换&#34;通过无效状态并达到有效且自我一致的状态&#34;只有在安装人员的一系列电话之后。
例如:&#34;没有两个轮子的摩托车不是有效的摩托车&#34;并不意味着我不能undefined
作为修理我的摩托车的临时阶段 ...按合同设计该死的&#39; d,我只是打电话给{ {1}}最后完成它。
<强>多制定者强>
如果您需要对设置进行验证,并且某些值的组合没有意义,请使用多重设置器:
setFrontWheel(null)
<强>吸气剂:强>
&#34;计算得到的吸气剂&#34; - 像那样&#34; 100美元&#34; - 本身不是一个属性,很多人会认为该方法应该被称为&#34; computeSomething&#34;或者&#34; toSomeForm&#34; (比如&#34; toString&#34;),但......非常方便易记:validateMe
&#34;限制吸气者&#34; - 比如
protected ArrayList listeners; // look-but-don't-touch getter public List getListeners() { return Collections.unmodifiableList(this.listeners): }
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. }
答案 2 :(得分:1)
TL; DR:是的。这正是setter和getter的意思!
首先要了解的是为什么我们首先使用getter和setter。
getter和setter的目的是能够将类作为公共API呈现,以便用户可以存储其对象并以操作方式操作数据(对于某些类,例如不可变类,这可能意味着根本不会操纵它。
getter允许我们公开存储在类中的信息,而用户不知道该信息是如何存储的。对于用户而言,对于该号码的每个部分(对于美国号码)是否存储电话号码是否存储在3个单独的字段中或者是否存储在单个字段中应该是无关紧要的。他们所关心的只是他们以类合同
指定的格式获得它 setter允许用户操纵对象。同样,他们不关心当他们传入Date
对象时,您将其拆分为单独的字段。他们只想知道你是否给了一个getter,它承诺传回一个等价的Date
对象,他们得到的Date
对象等同于他们在setter中传递的对象。
getter和setter的全部目的是该类的用户不知道该类中实际存在哪些字段(这就是为什么始终将它们设为私有的原因)。
通过将所有字段设为私有,我们可以稍后返回并更改我们想要的任何内容,只要它不会更改getter方法的结果。
例如,让我们说我们有一个代表一个人的类:
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;
}
}
在创建类时,我们决定不需要单独存储名字和姓氏,并且地址也可以是单个字段(无论出于何种原因)。如果我们想要更改它并将名字和姓氏分成2个单独的字段,并且地址也应该拆分成单独的字段,会发生什么。
如果我们让我们的领域暴露(通过公开),我们永远无法改变它们。任何人都可以通过声明Person.name = "John Doe"
来使用它们。更改字段名称将完全破坏他们的程序。
如果我们将它们保密,我们现在可以做任何我们想要的事情。我们只需要更新我们的方法,以便它们返回相同的数据。因此,为了拆分名称和地址字段,我们将执行以下操作:
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;
}
}
我们只能在不破坏使用我们代码的任何人的代码的情况下更改我们班级的整个实现。这是封装的力量以及它在OOP中如此重要的原因。
如果我们认为用户应该能够直接操作每个字段,那么我们就可以为各个字段添加getter和setter。当然,通过这样做,我们现在无法更改并合并字段,因为有人可能正在使用getter和setter。
您也询问了验证问题。现在应该很清楚,你班级的公共方法是定义你的班级合同的。验证数据非常重要,因为它通过不允许任何人将坏数据放入该对象来强制执行合同。您可以随时放宽限制,添加它们是一件非常难的事情,并且总是有可能破坏代码。
例如,如果您的age
课程中有Person
字段。您可能在课程中使用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岁才能使用您的申请。
如上所述,延迟加入限制比后来放松更难。
答案 3 :(得分:0)
排序。有时,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));
}
}
}
接受setValue
的重载String
是一个有效示例。但是,通常,正确的getter和setter将直接映射到属性的内部表示。例如,setDate
示例只对Date
对象进行操作并设置对象的内部Date
属性,部分原因是因为小时,分钟等都属于Date
并在这种情况下分解Date
是一种不好的做法。
答案 4 :(得分:0)
Getter和setter实现了封装的目的。
例如,
public class Sample
{
private int somedata; // Encapsulated
public int Get() {return somedata;} //Getter
public void Set(int var) {somedata = var;} // Setter
}
在OOP中这样做是一种很好的做法。
为了处理然后设置数据,可能需要设置器 特殊对象,如数据,包含年,月,日三个子部分。 它们可以超载以处理基本数据。
答案 5 :(得分:0)
维基百科链接说, 如果日期由单独的私人年,月和日表示
这并没有重新推测它说如果存在的话。永远不要玩数据类型,而是考虑到某些事情。
验证可以在字段声明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.......
答案 6 :(得分:0)
这些做法好吗? Yes
和No
。原因如下:
<强> 1。 setDate方法将java.util.Date日期存储在3个单独的私有字段中,例如年,月,日。
如果您密切注意java.util.Date
的代码,则不推荐将其存储在3个单独的字段中。这就是它被弃用的原因。
java.util.Date同时包含日期和时间部分。您可以忽略代码中的时间部分。在这种情况下,日期类将按照JVM的默认时区定义的一天开始,并将该时间应用于Date对象。因此,代码的结果将根据其运行的机器或设置的时区而有所不同。可能不是你想要的。
这样做并不是一个好习惯,Java开发者实现并弃用了某些功能。
<强> 2。 getAmount方法连接2个字段的数字和货币,并返回类似&#34; 100 USD&#34;。
是的,这是非常好的,除非并且直到您仅将此值用于显示目的而不是用于引用,因为此值是另外两个值的最终串联。
答案 7 :(得分:0)
setDate方法将java.util.Date日期存储在3个单独的私有中 年,月,日等字段
是的,你可以这样做。您对日期的处理方式是您的业务,如果您愿意,可以将其存储在字符串中,不应该干扰您班级的用户。
验证问题不同。您可以阅读有关design by contract
的信息假设你有一个分类:
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是否为null。这是合同设计解决的问题。所以你可以写:
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并没有完全按照合同来实现设计,但除了例外,你可以保持精神。