Java中的设置和Getters的意义是什么? [复制
-
13-09-2019 - |
题
这个问题在这里已经有一个答案:
请原谅长度,但这里有两个程序,两个程序都完全相同,但一个程序没有一个,一个没有设定的人,getters和构造函数。
我以前参加过基本的C ++课程,并且不记得其中的任何一个,目前我还没有看到它们的观点,如果有人可以用拉梅的术语来解释它们,我会非常感谢。在他们的那一刻,他们似乎只不过是浪费太空,就可以使我的代码看起来更长,但是老师说它们很重要(到目前为止)。
提前致谢!现在是代码:mileage.java:
package gasMileage;
import java.util.Scanner; //program uses class Scanner
public class Mileage
{
public int restart;
public double miles, gallons, totalMiles, totalGallons, milesPerGallon;
public Mileage(int newRestart, double newMiles, double newGallons,
double newTotalMiles, double newTotalGallons, double newMilesPerGallon)
{
setRestart(newRestart);
setMiles(newMiles);
setGallons(newGallons);
setTotalMiles(newTotalMiles);
setTotalGallons(newTotalGallons);
setMilesPerGallon(newMilesPerGallon);
}
public void setRestart(int newRestart)
{
restart = newRestart;
}
public int getRestart()
{
return restart;
}
public void setMiles(double newMiles)
{
miles = newMiles;
}
public double getMiles()
{
return miles;
}
public void setGallons(double newGallons)
{
gallons = newGallons;
}
public double getGallons()
{
return gallons;
}
public void setTotalMiles(double newTotalMiles)
{
totalMiles = newTotalMiles;
}
public double getTotalMiles()
{
return totalMiles;
}
public void setTotalGallons(double newTotalGallons)
{
totalGallons = newTotalGallons;
}
public double getTotalGallons()
{
return totalGallons;
}
public void setMilesPerGallon(double newMilesPerGallon)
{
milesPerGallon = newMilesPerGallon;
}
public double getMilesPerGallon()
{
return milesPerGallon;
}
public void calculateMileage()
{
Scanner input = new Scanner(System.in);
while(restart == 1)
{
System.out.print("Please input number of miles you drove: ");
miles = input.nextDouble();
totalMiles = totalMiles + miles;
System.out.print("Please input number of gallons you used: ");
gallons = input.nextDouble();
totalGallons = totalGallons + gallons;
milesPerGallon = miles / gallons;
System.out.printf("Your mileage is %.2f MPG.\n", milesPerGallon);
System.out.print("Would you like to try again? 1 for yes, 2 for no: ");
restart = input.nextInt();
}
milesPerGallon = totalMiles / totalGallons;
System.out.printf("Your total mileage for these trips is: %.2f.\nYour total gas consumed on these trips was: %.2f.\n", totalMiles, totalGallons);
System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
}
}
MileeAgetest.java:
package gasMileage;
public class Mileagetest
{
public static void main(String[] args)
{
Mileage myMileage = new Mileage(1,0,0,0,0,0);
myMileage.calculateMileage();
}
}
现在,对于没有设定的人和get的人来说,
testmileage.java:
package gasMileage;
import java.util.Scanner;
public class Testmileage
{
int restart = 1;
double miles = 0, milesTotal = 0, gas = 0, gasTotal = 0, mpg = 0;
Scanner input = new Scanner(System.in);
public void testCalculate()
{
while(restart == 1)
{
System.out.print("Please input miles: ");
miles = input.nextDouble();
milesTotal = milesTotal + miles;
System.out.print("Please input gas: ");
gas = input.nextDouble();
gasTotal = gasTotal + gas;
mpg = miles/gas;
System.out.printf("MPG: %.2f", mpg);
System.out.print("\nContinue? 1 = yes, 2 = no: ");
restart = input.nextInt();
}
mpg = milesTotal / gasTotal;
System.out.printf("Total Miles: %.2f\nTotal Gallons: %.2f\nTotal MPG: %.2f\n", milesTotal, gasTotal, mpg);
}
}
testmileagetest.java:
package gasMileage;
public class Testmileagetest
{
/**
* @param args
*/
public static void main(String[] args)
{
Testmileage test = new Testmileage();
test.testCalculate();
}
}
再次感谢!
解决方案
Getters和Setter的点, 不论语言如何, ,是隐藏基础变量。这使您可以在尝试设置值时添加验证逻辑 - 例如,如果您有一个出生日期的字段,则可能只想允许将该字段设置为过去的一段时间。如果该字段可公开访问和修改,则无法执行此操作 - 您需要Geters和Setter。
即使您还不需要任何验证,您将来可能需要它。现在编写Getters和Setter,这意味着接口保持一致,因此更改时现有代码不会破裂。
其他提示
其他答案通常给出了使用Getters和Setter的某些原因的好主意,但是我想给出一个完整的例子,说明它们为什么有用。
让我们以一个文件为例(忽略 File
Java上课)。这个 File
类有一个用于存储文件类型(.pdf,.exe,.txt等)的字段...我们将忽略其他所有内容。
最初,您决定将其存储为 String
没有一个距离和固定器:
public class File {
// ...
public String type;
// ...
}
这是一些不使用Getters和Setter的问题。
无法控制该字段的设置:
您班级的任何客户都可以按照自己的意愿来完成他们的意愿:
public void doSomething(File file) {
// ...
file.type = "this definitely isn't a normal file type";
// ...
}
稍后您决定您可能不希望他们这样做...但是,由于他们可以直接访问班级中的领域,因此您无法阻止它。
无法轻松改变内部表示:
稍后,您决定要将文件类型存储为一个接口的实例,称为 FileType
, ,允许您将某些行为与不同的文件类型相关联。但是,您班的许多客户已经在检索并将文件类型设置为 String
s。因此,您将在那里遇到问题...如果您只是从一个从一个更改了字段,则会打破很多代码(甚至在其他项目中,甚至无法修复自己的代码) String
到 FileType
.
如何得到固定器解决这个问题
现在想象一下您已经制作了类型字段 private
并创建
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
控制设置属性:
现在,当您要实现仅某些字符串是有效文件类型并防止其他字符串的要求时,您可以写下:
public void setType(String type) {
if(!isValidType(type)) {
throw new IllegalArgumentException("Invalid file type: " + type);
}
this.type = type;
}
private boolean isValidType(String type) {
// logic here
}
能够轻松改变内部表示的能力:
更改 String
类型的表示相对容易。想象你有一个 enum
ValidFileType
哪个实施 FileType
并包含有效的文件类型。
您可以轻松地更改类中文件类型的内部表示形式:
public class File {
// ...
private FileType type;
// ...
public String getType() {
return type.toString();
}
public void setType(String type) {
FileType newType = ValidFileType.valueOf(type);
if(newType == null) {
throw new IllegalArgumentException("Invalid file type: " + type);
}
this.type = newType;
}
}
由于班级的客户一直在打电话 getType()
和 setType()
无论如何,从他们的角度来看,没有任何变化。只有类的内部变化,而不是其他类正在使用的接口。
封装
访问者方法(“设置和获取器”)试图隐藏有关对象中数据的存储方式的详细信息。在实践中,它们是一种以非对象为导向的方式存储和检索数据的美化手段。访问者没有有效地封装任何内容,因为以下两部分代码之间几乎没有实际区别:
Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );
和这个:
Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;
这两个代码片段都揭示了一个人紧密耦合到头发的想法。然后,这种紧密的耦合在整个代码库中揭示了自己,从而产生了脆弱的软件。也就是说,很难改变一个人的头发的存储方式。
反而:
Person bob = new Person();
bob.setHairColour( Colour.RED );
这是“告诉,不要问”的前提。换句话说,应指导对象(其他对象)执行特定任务。这是面向对象的编程的全部。而且似乎很少有人得到它。
两种情况之间的区别在于:
- 在第一种情况下,鲍勃无法控制他的头发会变成什么颜色。非常适合对红发女郎偏爱的发型师,对于鄙视这种颜色的鲍勃来说,这并不是很棒。
- 在第二种情况下,鲍勃完全控制了他的头发将变成什么颜色,因为未经鲍勃的许可,系统中没有其他物体可以改变该颜色。
避免此问题的另一种方法是返回Bob的头发颜色的副本(作为新实例),该副本不再与Bob耦合。我发现这是一个不高的解决方案,因为这意味着其他阶级使用一个人的头发不再与人本身相关联的行为。这降低了重复使用代码的能力,从而导致重复的代码。
隐藏数据类型
在Java中,它们不能具有仅通过返回类型而差异的两个方法签名,它确实不会隐藏对象使用的基础数据类型。如果有的话,您很少会看到以下内容:
public class Person {
private long hColour = 1024;
public Colour getHairColour() {
return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
}
}
通常,单个变量通过使用相应的访问者的数据类型逐字曝光,并且需要重构将其更改:
public class Person {
private long hColour = 1024;
public long getHairColour() {
return hColour;
}
/** Cannot exist in Java: compile error. */
public Colour getHairColour() {
return new Colour( hColour & 255, hColour << 8 & 255, hColour<< 16 & 255 );
}
}
虽然它提供了一定程度的抽象,但它是一种薄薄的面纱,无助于松散耦合。
告诉,不要问
有关此方法的更多信息,请阅读 告诉,不要问.
文件示例
考虑以下代码,从Colind的答案中进行了稍微修改:
public class File {
private String type = "";
public String getType() {
return this.type;
}
public void setType( String type ) {
if( type = null ) {
type = "";
}
this.type = type;
}
public boolean isValidType( String type ) {
return getType().equalsIgnoreCase( type );
}
}
方法 getType()
在这种情况下,是多余的,不可避免地会导致重复的代码,例如:
public void arbitraryMethod( File file ) {
if( file.getType() == "JPEG" ) {
// Code.
}
}
public void anotherArbitraryMethod( File file ) {
if( file.getType() == "WP" ) {
// Code.
}
}
问题:
- 数据类型。 这
type
属性无法轻易从字符串更改为整数(或其他类)。 - 隐含协议。 从特定的特定类型中抽象的类型很耗时(
PNG
,JPEG
,TIFF
,EPS
)将军(IMAGE
,DOCUMENT
,SPREADSHEET
). - 引入错误。 更改隐含协议不会生成编译器错误,这可能导致错误。
通过防止其他类的方式完全避免问题 问 数据:
public void arbitraryMethod( File file ) {
if( file.isValidType( "JPEG" ) ) {
// Code.
}
}
这意味着改变 get
登录方法 private
:
public class File {
public final static String TYPE_IMAGE = "IMAGE";
private String type = "";
private String getType() {
return this.type;
}
public void setType( String type ) {
if( type == null ) {
type = "";
}
else if(
type.equalsIgnoreCase( "JPEG" ) ||
type.equalsIgnoreCase( "JPG" ) ||
type.equalsIgnoreCase( "PNG" ) ) {
type = File.TYPE_IMAGE;
}
this.type = type;
}
public boolean isValidType( String type ) {
// Coerce the given type to a generic type.
//
File f = new File( this );
f.setType( type );
// Check if the generic type is valid.
//
return isValidGenericType( f.getType() );
}
}
当系统中没有其他代码会破坏 File
类转换隐含的协议从特定类型(例如JPEG)到通用类型(例如,图像)。系统中的所有代码都必须使用 isValidType
方法,不给调用对象的类型,而是 告诉 这 File
验证类型的类。
这个想法是,如果您的客户端类调用获取/设置功能,则可以更改他们以后的工作,并且呼叫者被绝缘。如果您有一个公共变量,并且我直接访问它,则在访问或设置时,您将无法添加行为。
即使在您的简单示例中,您也可以利用它。
而不是使用:
milesPerGallon = miles / gallons;
在colculatemileage()中
您可以将setmiles()和setgallons()更改为更新Milespergallon时。然后,删除setMilespergallon(),以指示它是一个只读属性。
关键是类不应直接访问其字段,因为这是特定于实现的。您可能需要以后更改课程,以便使用另一个数据存储,但要为其“用户”保持相同的类别,或者您可能需要创建一个界面,该界面也不包括字段。
看看 维基百科文章 就此主题而言。
它们为您的班级提供了公共接口,并提供了一定的封装度量。考虑如何在没有Geters和Setter的情况下访问公共数据。
Mileage m = new Mileage();
m.miles = 5.0;
m.gallons = 10.0;
...
现在,如果您决定要在课程中添加一些验证,则必须更改直接访问字段的任何地方的代码。如果您从一开始就使用getters和setter(仅在需要的地方)您可以避免这种努力,仅在一个地方更改代码。
使用Getters和Setter可以灵活地更改实现。您可能不认为您需要那个,但是有时您会这样做。例如,您可能需要使用代理模式来懒惰加载一个价格昂贵的对象:
class ExpensiveObject {
private int foo;
public ExpensiveObject() {
// Does something that takes a long time.
}
public int getFoo() { return foo; }
public void setFoo(int i) { foo = i; }
}
class ExpensiveObjectProxy extends ExpensiveObject {
private ExpensiveObject realObject;
public ExpensiveObjectProxy() { ; }
protected void Load() {
if ( realObject == null ) realObject = new ExpensiveObject();
}
public int getFoo() { Load(); return realObject.getFoo(); }
public void setFoo(int i) { Load(); realObject.setFoo(i); }
}
class Main {
public static void main( string[] args ) {
// This takes no time, since ExpensiveOjbect is not constructed yet.
ExpensiveObject myObj = new ExpensiveObjectProxy();
// ExpensiveObject is actually constructed here, when you first use it.
int i = myObj.getFoo();
}
}
经常播放的是,当您通过ORM映射到数据库的对象时。您仅加载所需的东西,然后返回数据库以加载其余的,如果/何时使用。
总的来说
有些人称它们为抽象,但事实并非如此。样板设置器/Getter并不比公共成员更好。他们仍然允许完全访问变量有时类别无法控制并仍然限制课堂上的更改(如果您的变量是int,您仍然必须更改调用设置器的所有内容,并将变量更改为字符串)
Getters和Setter鼓励从班级外部访问课堂的数据。任何访问类成员的代码都可能存在于该类(如您的设计状态)中,因此不需要设置器或Getters。它们应该是不必要的。
同样,将二传手迫使您进入所有课程也很恐怖,这意味着您的课程根本无法不变,而实际上您应该有一个充分的理由使课堂可变。
也就是说,它们对于诸如持久引擎和GUI构建器之类的跨切割问题有用,在那里他们可以获得并设置值,并且类可以监视或更改或修改或验证它。
对于那些需要交叉切换变量访问的系统的一个更好的模式是,是通过反射直接访问变量,但如果存在一个设置器或getter,请致电或Getter,如果可能的话,请使用设置器和Getter Private。
这将允许非OOO交叉切割代码正常工作,可以使您的课程修改集合并在需要时获得,并在必要时允许Getters(有时确实有用)。
一个单词的答案是 接口.
接口允许使用方法,而不是字段,因此已建立的约定是为此目的具有GETX和SETX方法。
(并且接口是从Java中实现功能的方法)
您的例子极为荒谬。是的,所有这些获取器和固定器都会膨胀代码,并在这种情况下不添加任何值。但是,封装的基本思想是针对由许多相互作用组件组成的较大系统,而不是针对小型,独立的程序。
有用的,明智使用的特征和固定器的特征:
- 许多其他类都使用的类(隐藏实施细节使客户更容易)
- 仅适用于实际需要的字段 - 尽可能少,大多数字段应是私有的,并且仅在其班级中使用
- 一般而言,很少有固定器:可变的字段使跟踪程序的状态比仅阅读字段更难
- 实际上是 做 除了访问fied的东西外,例如抛出无效值的异常或更新“最后修改”时间戳或计算苍蝇值的getter而不是依靠基础字段的东西之外
快进几个月。也许您的老师要求您实现MIRAGE类的远程版本。也许作为网络服务,也许还有其他事情。
没有Getter/Setter,您将不得不更改各个地方的每个代码,而Getter/Setter几乎(在完美的世界中)只需要更改MILAGE类型的创建即可。
Getters和Setters允许您构建有用的快捷方式,以访问和突变对象中的数据。通常,这可以看作是具有用于获取和设置值的对象的两个函数的替代方法,例如:
{
getValue: function(){
return this._value;
},
setValue: function(val){
this._value = val;
}
}
以这种方式编写JavaScript的明显优势在于,您可以使用它模糊的值,而您不希望用户直接访问。最终结果看起来像以下内容(使用闭合来存储新构造的字段的值):
function Field(val){
var value = val;
this.getValue = function(){
return value;
};
this.setValue = function(val){
value = val;
};
}
添加setter和getter方法使托管bean的状态可访问,您需要为该状态添加setter和getter方法。创建方法称为Bean'sgreet方法,而GetAtain方法检索结果。一旦添加了设置器和Getter方法,Bean就完成了。最终代码看起来像这样:包装问候;
import javax.inject.Inject;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
@Named
@RequestScoped
public class Printer {
@Inject @Informal Greeting greeting;
private String name;
private String salutation;
public void createSalutation() {
this.salutation = greeting.greet(name);
}
public String getSalutation() {
return salutation;
}
public String setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
封装 和 代码重复使用 是面向对象的编程的美丽。如果我们在代码中处理一些敏感数据,则将其声明为私有数据字段,即我们封装数据,以便没有人可以直接访问它。即一种受控的访问机制来处理敏感的数据字段。以下示例可能有助于理解二传手和Getters的优势和重要性。
- 我已经实施了一个课程,在该课程中,我正在使用几天变量。
- 在我的班级中,没有人能设定超过365天的价值。
- 有人想从我的班级继承。(代码可重复使用)。
- 现在,当他进入超过365天的价值时,我班级的所有功能都会失败。
- 因此,我应该将“天数”变量称为私人数据字段。
- 现在,如果我将Days Data字段宣布为私有,那么没有人可以将几天的值设置为365,因为我本来可以实现具有有关输入限制的setter函数。