关于Java:为什么使用getters and setters/accessors?

Why use getters and setters/accessors?

使用getter和setter(只获取和设置)而不是简单地为这些变量使用公共字段有什么好处?

如果getter和setter做的不仅仅是简单的get/set,那么我可以很快找到它,但我不完全清楚如何:

1
public String foo;

比以下情况更糟:

1
2
3
private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

而前者需要的样板代码要少得多。


实际上,有很多很好的理由可以考虑使用访问器,而不是直接公开类的字段,而不仅仅是封装和使未来的更改更容易。

以下是我知道的一些原因:

  • 与获取或设置属性相关联的行为的封装-这允许以后更容易地添加其他功能(如验证)。
  • 隐藏属性的内部表示,同时使用替代表示公开属性。
  • 将公共接口与更改隔离开来-允许公共接口在实现更改时保持不变,而不会影响现有的使用者。
  • 控制属性的生命周期和内存管理(处置)语义-在非托管内存环境(如C++或Objtovi-C)中尤为重要。
  • 为属性在运行时发生更改时提供调试拦截点-在某些语言中,如果没有此设置,将很难在何时何地调试属性更改为特定值。
  • 改进了与设计用于针对属性getter/setter操作的库的互操作性——mocking、serialization和wpf浮现在脑海中。
  • 允许继承者通过重写getter/setter方法来更改属性的行为和公开的语义。
  • 允许将getter/setter作为lambda表达式而不是值传递。
  • getter和setter可以允许不同的访问级别-例如,get可能是公共的,但该集可以受到保护。


因为2周(月、年)之后,当您意识到setter需要做的不仅仅是设置值时,您还将意识到该属性已直接用于238个其他类:—)


公共字段并不比getter/setter对差,后者除了返回字段并分配给它之外什么都不做。首先,很明显(在大多数语言中)没有功能差异。任何差异都必须存在于其他因素中,比如可维护性或可读性。

getter/setter对的一个经常提到的优点不是。这里有一个声明,您可以更改实现,并且您的客户机不必重新编译。据说,setter允许您稍后添加验证之类的功能,您的客户机甚至不需要知道它。但是,向setter添加验证是对其前提条件的更改,这违反了先前的约定,这非常简单,"您可以在这里放置任何内容,并且稍后可以从getter获得相同的内容"。

因此,既然您违反了合同,那么更改代码库中的每个文件是您应该做的,而不是避免的事情。如果您避免了它,那么您将假定所有代码都假定这些方法的契约是不同的。

如果这不应该是契约,那么接口允许客户机将对象置于无效状态。这与封装正好相反,如果该字段从一开始就不能真正设置为任何内容,那么为什么验证从一开始就不存在呢?

同样的论点也适用于这些传递getter/setter对的其他假定优势:如果您稍后决定更改正在设置的值,那么您将破坏契约。如果您在派生类中重写了默认功能,在一些无害的修改(如日志记录或其他不可观察的行为)之外,您将破坏基类的契约。这违背了被视为OO原则之一的Liskov可替代性原则。

如果一个类对每个字段都有这些愚蠢的getter和setter,那么它就是一个没有不变量、没有契约的类。这真的是面向对象的设计吗?如果所有类都有那些getter和setter,那么它只是一个哑数据容器,哑数据容器应该看起来像哑数据容器:

1
2
3
4
5
class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

向此类添加传递getter/setter对不会增加任何值。其他类应该提供有意义的操作,而不仅仅是字段已经提供的操作。这就是如何定义和维护有用的不变量。

Client:"What can I do with an object of this class?"
Designer:"You can read and write several variables."
Client:"Oh... cool, I guess?"

有理由使用getter和setter,但是如果这些理由不存在,那么以虚假封装之神的名义创建getter/setter对就不是一件好事。创建getter或setter的有效原因包括经常提到的可能的更改,比如验证或不同的内部表示。或者客户机应该可以读取该值,但不可写(例如,读取字典的大小),所以简单的getter是一个不错的选择。但是当你做出选择的时候,这些原因应该存在,而不仅仅是你以后可能想要的潜在的东西。这是雅格尼的一个例子(你不需要它)。


很多人都在谈论能手和二传手的优势,但我想扮演魔鬼的拥护者。现在我正在调试一个非常大的程序,程序员决定让所有的东西都成为getter和setter。这看起来不错,但这是一个逆向工程的噩梦。

假设你正在浏览数百行代码,你会发现:

1
person.name ="Joe";

在你意识到它是一个setter之前,它只是一段漂亮的代码。现在,您遵循这个设置器,发现它还设置了person.firstname、person.lastname、person.ishuman、person.hasreallycommonfirstname,并调用person.update(),后者将查询发送到数据库等。噢,这就是发生内存泄漏的地方。

乍一看理解本地代码是getter和setter容易破坏的具有良好可读性的一个重要特性。这就是为什么我尽量避免使用它们,并尽量减少它们在我使用时的作用。


原因很多。我最喜欢的是当你需要改变行为或者调整你可以在变量上设置的内容时。例如,假设您有一个setspeed(int speed)方法。但是你想要的是你只能设定100的最大速度。您可以执行以下操作:

1
2
3
4
5
6
7
public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

现在,如果您的代码中的任何地方都使用了公共字段,然后您意识到您需要上述要求,该怎么办?寻找公共领域的每一个使用乐趣,而不是仅仅修改你的设置器。

我的2分:


在纯面向对象的世界里,getters和setters是一种可怕的反模式。阅读本文:getter/setter。邪恶的。时期。简而言之,它们鼓励程序员把对象看作数据结构,这种类型的思考是纯过程的(就像COBOL或C中那样)。在面向对象的语言中,没有数据结构,只有暴露行为的对象(不是属性/属性!)

您可以在优雅对象的第3.5节(我的书关于面向对象编程)中找到更多关于它们的信息。


访问器和赋值器的一个优点是可以执行验证。

例如,如果foo是公共的,我可以很容易地将其设置为null,然后其他人可以尝试对对象调用方法。但它已经不在了!使用setFoo方法,我可以确保foo从未设置为null

访问器和赋值函数也允许封装——如果您不应该在值集之后看到它(可能它是在构造函数中设置的,然后被方法使用,但不应该被更改),那么任何人都不会看到它。但是,如果您允许其他类查看或更改它,那么您可以提供适当的访问器和/或变异器。


取决于你的语言。你已经标记了这个"面向对象"而不是"Java",所以我想指出CHSPLISY76的答案是依赖于语言的。例如,在Python中,没有理由使用getter和setter。如果需要更改行为,可以使用一个属性,它围绕基本属性访问包装了getter和setter。像这样:

1
2
3
4
5
6
7
8
9
10
11
12
class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)


好吧,我只是想补充一点,即使有时候它们对于变量/对象的封装和安全是必要的,如果我们想编写一个真正的面向对象的程序,那么我们需要停止过度使用访问器,因为有时候我们在不必要的时候非常依赖它们,这就使得我们把变量ES公开。


我知道有点晚了,但我想有些人对表演感兴趣。

我做了一点性能测试。我写了一个类"numberholder",它包含一个整数。您可以使用getter方法读取该整数anInstance.getNumber()或直接使用anInstance.number访问号码。我的程序通过两种方式读取100000000次。该过程重复五次,并打印时间。我得到了以下结果:

1
2
3
4
5
Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(时间1是直接途径,时间2是吸气剂)

你看,盖特总是(几乎)快一点。然后我尝试了不同数量的循环。我用了1000万和100万而不是100万。结果:

1000万次循环:

1
2
3
4
5
Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

在1000万次循环中,时间几乎相同。这里有10万(10万)个周期:

1
2
3
4
5
Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

同样,在不同的循环量下,吸气剂比常规的方法快一点。我希望这对你有帮助。


谢谢,这真的澄清了我的想法。现在有(几乎)10个(几乎)不使用getter和setter的好理由:

  • 当您意识到需要做的不仅仅是设置和获取值时,您可以将该字段设为私有字段,它会立即告诉您在哪里直接访问了该字段。
  • 您在其中执行的任何验证都只能是上下文无关的,而实际上很少是上下文无关的。
  • 您可以更改正在设置的值-当来电者传递给您一个他们[震惊恐惧]希望您按原样存储的值时,这绝对是一个噩梦。
  • 您可以隐藏内部表示——非常好,所以您要确保所有这些操作都是对称的,对吗?
  • 你已经将你的公共接口与工作表下的变更隔离开来了——如果你正在设计一个接口,并且不确定直接访问某个东西是否正常,那么你应该继续设计。
  • 有些库希望这样做,但不是很多——反射、序列化、模拟对象都可以在公共字段中正常工作。
  • 继承这个类,您可以重写默认的功能——换句话说,您不仅可以隐藏实现,还可以使其不一致,从而真正地混淆调用方。
  • 我要离开的最后三个(不适用或信用证)


    除非您当前的交付需要,否则不要使用getters setter,也就是说,不要过多地考虑将来会发生什么,如果要更改的任何东西在大多数生产应用程序、系统中都是一个更改请求。

    思考简单,简单,必要时增加复杂性。

    我不会仅仅因为我认为这是正确的或者我喜欢这种方法,就利用企业主对深层次技术知识的无知。

    我有大量的系统,没有getter setter,只使用访问修饰符和一些方法来验证n执行biz逻辑。如果你绝对需要的话。使用任何东西。


    它对于延迟加载很有用。假设有问题的对象存储在数据库中,除非您需要,否则您不想得到它。如果对象是由getter检索的,那么内部对象可以为空,直到有人请求它为止,然后您可以在第一次调用getter时获取它。

    我在一个项目中有一个基本页类,它被交付给我,从几个不同的Web服务调用中加载一些数据,但是这些Web服务调用中的数据并不总是用于所有子页面。Web服务,为了所有的好处,开创了"慢"的新定义,所以如果你不需要的话,你不想打一个Web服务电话。

    我从公共字段转移到getter,现在getter检查缓存,如果没有缓存,请调用Web服务。因此,只要稍微包装一下,就可以防止许多Web服务调用。

    所以getter帮我避免了在每个孩子的页面上试图弄清楚我需要什么。如果我需要它,我会打电话给getter,如果我还没有,它会帮我找到它。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
        protected YourType _yourName = null;
        public YourType YourName{
          get
          {
            if (_yourName == null)
            {
              _yourName = new YourType();
              return _yourName;
            }
          }
        }


    我们使用getter和setter:

    • 可重用性
    • 在编程的后期执行验证

    getter和setter方法是访问私有类成员的公共接口。

    封装咒语

    封装准则是使字段私有化,方法公共化。

    Getter Methods: We can get access to private variables.

    Setter Methods: We can modify private fields.

    即使getter和setter方法没有添加新的功能,我们也可以稍后改变主意,使该方法成为可能。

    • 更好;
    • 更安全;
    • 更快。

    任何可以使用值的地方,都可以添加返回该值的方法。而不是:

    1
    int x = 1000 - 500

    使用

    1
    int x = 1000 - class_name.getValue();

    用外行的话来说

    Representation of

    假设我们需要存储这个Person的细节。这个Personname字段、age字段和sex字段。这样做需要为nameagesex创建方法。现在,如果我们需要创建另一个人,就需要重新创建nameagesex的方法。

    我们可以使用getter和setter方法创建beaneclass(Person),而不是这样做。所以明天只要我们需要添加一个新的人,我们就可以创建这个bean class(Person class)的对象(见图)。因此,我们重用bean类的字段和方法,这是更好的方法。


    我花了相当长的时间来思考这个Java案例,我认为真正的原因是:

  • 接口的代码,而不是实现
  • 接口只指定方法,而不是字段
  • 换句话说,在接口中指定字段的唯一方法是提供一种方法来写入新值,并提供一种方法来读取当前值。

    这些方法是臭名昭著的getter和setter….


    到目前为止,我在答案中遗漏了一个方面,即访问规范:

    • 对于成员,对于设置和获取,您只有一个访问规范
    • 对于setter和getter,您可以对其进行微调并分别定义

    在不支持"属性"(C++、Java)的语言中,或在将字段更改为属性时需要重新编译客户机(C**),使用GET/SET方法更容易修改。例如,向setfoo方法添加验证逻辑不需要更改类的公共接口。

    在支持"真实"属性的语言中(python、ruby,或者smalltalk?)没有获取/设置方法的意义。


    编辑:我回答这个问题是因为有很多人在学习编程时都会问这个问题,而且大多数答案在技术上都很有能力,但是如果你是新手的话,它们就不那么容易理解了。我们都是新手,所以我想我可以试着用手回答一个更适合新手的问题。

    两个主要的是多态性和验证。即使只是一个愚蠢的数据结构。

    假设我们有一个简单的类:

    1
    2
    3
    4
    public class Bottle {
      public int amountOfWaterMl;
      public int capacityMl;
    }

    一个非常简单的类,它包含有多少液体,它的容量是多少(毫升)。

    当我这样做时会发生什么:

    1
    2
    3
    Bottle bot = new Bottle();
    bot.amountOfWaterMl = 1500;
    bot.capacity = 1000;

    好吧,你不会指望那会奏效吧?你想做些健康检查。更糟的是,如果我从未指定最大容量呢?哦,天哪,我们有问题。

    但还有另一个问题。如果瓶子只是一种容器呢?如果我们有几个容器,所有的容器都有容量和液体填充量呢?如果我们能做一个接口,我们就可以让程序的其余部分接受这个接口,瓶子、Jerrycans和各种各样的东西就可以互换工作。那不是更好吗?由于接口需要方法,这也是一件好事。

    我们最终会得到如下的结果:

    1
    2
    3
    4
    5
    6
    public interface LiquidContainer {
      public int getAmountMl();
      public void setAmountMl(int amountMl);
      public int getCapacityMl();
      public void setCapcityMl(int capacityMl);
    }

    伟大的!现在我们把瓶子换成这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class Bottle extends LiquidContainer {
      private int capacityMl;
      private int amountFilledMl;
      public Bottle(int capacityMl, int amountFilledMl) {
        this.capacityMl = capacityMl;
        this.amountFilledMl = amountFilledMl;
        checkNotOverFlow();
      }

      public int getAmountMl() {
        return amountFilledMl;
      }

      public void setAmountMl(int amountMl) {
         this.amountFilled = amountMl;
         checkNotOverFlow();
      }
      public int getCapacityMl() {
        return capacityMl;
      public void setCapcityMl(int capacityMl) {
        this.capacityMl = capacityMl;
        checkNotOverFlow();
      }

      private void checkNotOverFlow() {
        if(amountOfWaterMl > capacityMl) {
          throw new BottleOverflowException();
        }
    }

    我将把bottleOverflowException的定义留给读者作为练习。

    现在注意到它有多强大。我们现在可以通过接受液体容器而不是瓶子来处理代码中的任何类型的容器。这些瓶子是如何处理这类东西的,可能各不相同。您可以有一些瓶子,它们在更改时将状态写入磁盘,或者保存在SQL数据库或GNU上的瓶子,它们知道还有什么。

    所有这些都可以有不同的方法来处理各种各样的人。瓶子只是检查一下,如果它溢出,就会抛出一个runtimeexception。但这可能是错误的做法。(有一个关于错误处理的有用讨论,但是我在这里故意保持非常简单的讨论。评论中的人可能会指出这种简单方法的缺陷。;)

    是的,我们似乎从一个非常简单的想法快速得到了更好的答案。

    还有第三件事并不是每个人都解决过:getter和setter使用方法调用。这意味着它们在其他地方看起来都像普通的方法。与对DTO和其他东西使用奇怪的特定语法不同,您在任何地方都有相同的东西。


    OO设计的基本原则之一:封装!

    它为您提供了许多好处,其中之一就是您可以在幕后更改getter/setter的实现,但是只要数据类型保持不变,该值的任何使用者都将继续工作。


    在以下情况下应使用getter和setter:

    • 您处理的是概念上的属性,但是:
      • 您的语言没有属性(或者类似的机制,比如tcl的变量跟踪),或者
      • 您的语言的属性支持不足以支持此用例,或者
      • 您的语言(或有时您的框架)惯用惯例鼓励这个用例的getter或setter。

    所以这很少是一个普通的OO问题;它是一个特定于语言的问题,对于不同的语言(以及不同的用例)有不同的答案。

    从OO理论的观点来看,getter和setter是无用的。类的接口是它所做的,而不是它的状态。(如果不是,你写错了类。)在非常简单的情况下,类所做的只是,例如,用直角坐标表示一个点,*属性是接口的一部分;getter和setter只是对其进行了模糊处理。但是,在非常简单的情况下,属性、getter和setter都不是接口的一部分。

    换一种方式:如果你认为你的类的消费者甚至不应该知道你有一个spam属性,那么就更不能随意地更改它了,那么给他们一个set_spam方法是你最不想做的事情。

    即使对于这个简单类,您也不一定要允许设置xy值。如果这真的是一个类,它不应该有像translaterotate等方法吗?如果它只是一个类,因为您的语言没有记录/结构/命名的元组,那么这实际上不是OO的问题…

    但是没有人做过一般的OO设计。他们用一种特定的语言进行设计和实现。在某些语言中,getter和setter并不是无用的。

    如果您的语言没有属性,那么表示概念上是一个属性,但实际上是通过getter和setter计算或验证的东西的唯一方法就是。

    即使您的语言确实具有属性,也可能存在一些不足或不适当的情况。例如,如果要允许子类控制属性的语义,在没有动态访问的语言中,子类不能用计算属性替换属性。

    至于"如果我以后要更改我的实现怎么办?"问题(在op的问题和接受的答案中以不同的措辞重复多次):如果它确实是一个纯粹的实现更改,并且从一个属性开始,则可以将其更改为一个属性,而不会影响接口。当然,除非你的语言不支持这一点。所以这又是同样的情况了。

    此外,遵循您所使用的语言(或框架)的习惯用法也很重要。如果用C语言编写漂亮的Ruby风格的代码,那么任何有经验的C开发人员(除了你之外)都会遇到阅读困难,这很糟糕。有些语言在他们的习俗中比其他语言有更强的文化。-这可能不是巧合,爪哇和Python,在相反的末端,对于惯用的吸引子来说,碰巧有两种最强烈的文化。

    除了人类读者之外,还有一些库和工具希望你遵循这些约定,如果你不这么做,就会让你的生活变得更困难。把界面生成器小部件挂在任何东西上,除了Objc属性,或者使用一些没有取笑器的Java嘲弄库,只会让你的生活更加困难。如果这些工具对你很重要,不要和它们战斗。


    从面向对象的设计角度来看,这两种选择都会削弱类的封装,从而破坏代码的维护。有关讨论,请参阅这篇优秀的文章:http://typicalprogrammer.com/?P=23


    如果没有属性继承,就有充分的理由考虑使用访问器。请参阅下一个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class TestPropertyOverride {
        public static class A {
            public int i = 0;

            public void add() {
                i++;
            }

            public int getI() {
                return i;
            }
        }

        public static class B extends A {
            public int i = 2;

            @Override
            public void add() {
                i = i + 2;
            }

            @Override
            public int getI() {
                return i;
            }
        }

        public static void main(String[] args) {
            A a = new B();
            System.out.println(a.i);
            a.add();
            System.out.println(a.i);
            System.out.println(a.getI());
        }
    }

    输出:

    1
    2
    3
    0
    0
    4

    getter和setter方法是访问器方法,这意味着它们通常是用于更改私有类成员的公共接口。使用getter和setter方法定义属性。即使在类内将getter和setter方法定义为方法,也可以将它们作为类外的属性访问。类外部的那些属性可以与类中的属性名具有不同的名称。

    使用getter和setter方法有一些优点,例如可以使用复杂的功能创建成员,这些功能可以访问类似属性的内容。它们还允许您创建只读和只写属性。

    尽管getter和setter方法很有用,但是应该注意不要过度使用它们,因为在其他问题中,它们会使代码维护在某些情况下变得更加困难。此外,它们还提供对类实现的访问,如公共成员。OOP实践不鼓励直接访问类中的属性。

    编写类时,总是鼓励尽可能多地将实例变量设置为私有,并相应地添加getter和setter方法。这是因为有时您可能不想让用户更改类中的某些变量。例如,如果有一个私有静态方法跟踪为特定类创建的实例数,则不希望用户使用代码修改该计数器。只有constructor语句在调用该变量时才应该递增。在这种情况下,您可以创建一个私有实例变量,并且只允许对计数器变量使用getter方法,这意味着用户只能使用getter方法来检索当前值,并且不能使用setter方法设置新值。在没有setter的情况下创建getter是使类中某些变量只读的简单方法。


    代码演变。private非常适合需要数据成员保护的情况。最终,所有的类都应该是一种"微型程序",它有一个定义良好的接口,您不能仅仅使用其内部结构。

    这就是说,软件开发并不是要设置类的最终版本,就好像您在第一次尝试时按下了一些铸铁雕像。当你使用它的时候,代码更像黏土。它会随着您的开发而发展,并进一步了解您正在解决的问题领域。在开发过程中,类可能会相互交互,而不是它们应该交互(您计划分解的依赖关系)、合并在一起或分开。所以我认为这场辩论归根结底就是人们不想宗教写作

    1
    int getVar() const { return var ; }

    所以你有:

    1
    doSomething( obj->getVar() ) ;

    而不是

    1
    doSomething( obj->var ) ;

    getVar()不仅在视觉上有噪音,它还给人一种错觉,认为gettingVar()在某种程度上比实际情况更复杂。你(作为班级作家)如何看待var的神圣性,对于你班上的一个用户来说尤其令人困惑,如果它有一个直通设置器,那么看起来你是在设置这些门来"保护"你所坚持的有价值的东西,(var的神圣性),但即使你承认var的保护并不值得这么做。他能让任何人进来,让任何人都能随心所欲地得到他们想要的价值,而你甚至不用看他们在做什么。

    因此,我编程如下(假设采用"敏捷"类型的方法——即当我编写的代码不知道它将要做什么/没有时间或经验来计划一个复杂的瀑布式接口集):

    1)从具有数据和行为的基本对象的所有公共成员开始。这就是为什么在我的C++"示例"代码中,你会注意到我使用EDCOX1,8,而不是EDCOX1,9,到处都是。

    2)当对象对数据成员的内部行为变得足够复杂(例如,它喜欢保持内部std::list的某种顺序)时,就编写了访问器类型函数。因为我是自己编程的,我并不总是马上设置成员private,但是在类的某个演化过程中,成员将被"提升"到protectedprivate

    3)对于那些完全充实并且对其内部有严格规则的类(即它们确切地知道它们在做什么,而您不必"操"(技术术语)其内部)被赋予class名称,默认的私有成员,并且只允许选择少数成员成为public

    我发现这种方法允许我避免坐在那里,在类进化的早期阶段,当许多数据成员被迁移、转移等时,虔诚地编写getter/setter。


    另一种用法(在支持属性的语言中)是,setter和getter可能意味着操作是非常重要的。通常,您希望避免在一个属性中执行任何计算上昂贵的操作。


    在面向对象的语言中,方法及其访问修饰符声明该对象的接口。在构造函数、访问器和mutator方法之间,开发人员可以控制对对象内部状态的访问。如果变量被简单地声明为公共变量,那么就没有办法控制该访问。当我们使用setter时,我们可以限制用户输入我们需要的内容。意味着这个变量的馈送将通过一个适当的通道,通道由我们预先定义。所以使用setter更安全。


    getter和setter用于实现面向对象编程的两个基本方面,即:

  • 抽象化
  • 包封
  • 假设我们有一个员工班:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package com.highmark.productConfig.types;

    public class Employee {

        private String firstName;
        private String middleName;
        private String lastName;

        public String getFirstName() {
          return firstName;
        }
        public void setFirstName(String firstName) {
           this.firstName = firstName;
        }
        public String getMiddleName() {
            return middleName;
        }
        public void setMiddleName(String middleName) {
             this.middleName = middleName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }

        public String getFullName(){
            return this.getFirstName() + this.getMiddleName() +  this.getLastName();
        }
     }

    与公共属性不同,全名的实现细节对用户是隐藏的,用户无法直接访问。


    getter/setter的一个相对现代的优势是,它使在带标记(索引)的代码编辑器中浏览代码更加容易。例如,如果要查看谁设置了成员,可以打开setter的调用层次结构。

    另一方面,如果成员是公共的,那么这些工具不能过滤对成员的读/写访问。所以你必须在成员的所有使用中努力。


    此外,这是为了"证明未来"你的班级。特别是,从一个字段更改为一个属性是一个ABI中断,因此如果您稍后决定您需要更多的逻辑而不仅仅是"设置/获取字段",那么您需要中断ABI,这当然会给已经针对类编译的任何其他内容带来问题。


    我只想抛开注释的想法:@getter和@setter。有了@getter,您应该能够obj=class.field,而不是class.field=obj。使用@setter,反之亦然。有了@getter和@setter,您应该能够同时完成这两项工作。这将通过在运行时不调用琐碎的方法来保留封装并减少时间。


    我能想到一个原因,为什么你不想把一切都公开。

    例如,可以通过链变量访问(即object.item.origin.x)直接访问从未打算在类外部使用的变量,甚至可以直接访问。

    通过让所有的东西都是私有的,只有你想要扩展的东西,并且可能在子类中被引用为受保护的,并且通常只有静态的最终对象是公共的,那么你就可以控制其他程序员和程序在API中可以使用什么,它可以访问什么,它不能访问什么,通过使用setter和getter来访问这些东西。您希望程序,或者可能是其他恰好使用您的代码的程序员,可以在您的程序中进行修改。


    Getters and setters coming from data hiding. Data Hiding means We
    are hiding data from outsiders or outside person/thing cannot access
    our data.This is a useful feature in OOP.

    举个例子:

    如果创建公共变量,则可以访问该变量并在任何地方(任何类)更改值。但是,如果您创建为private,那么除了声明的类之外,该变量不能在任何类中看到/访问。

    public and private are access modifiers.

    那么我们如何在外部访问这个变量:

    这是一个有很多人和二传手的地方。可以将变量声明为private,然后可以实现该变量的getter和setter。

    示例(Java):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private String name;

    public String getName(){
       return this.name;
    }

    public void setName(String name){
       this.name= name;
    }

    优势:

    当任何人想要访问或更改/设置balance变量的值时,他/她必须有权限。

    1
    2
    3
    4
    5
    6
    //assume we have person1 object
    //to give permission to check balance
    person1.getName()

    //to give permission to set balance
    person1.setName()

    也可以在构造函数中设置值,但以后需要时要更新/更改值,必须实现setter方法。


    我想发布一个真实的例子,我刚刚完成:

    后台-我休眠工具来为我的数据库生成映射,一个我开发时正在更改的数据库。我更改数据库模式,推动更改,然后运行Hibernate工具来生成Java代码。在我想向那些映射的实体添加方法之前,一切都很好。如果修改生成的文件,则每次对数据库进行更改时,都将覆盖这些文件。所以我对生成的类进行如下扩展:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.foo.entities.custom
    class User extends com.foo.entities.User{
         public Integer getSomething(){
             return super.getSomething();            
         }
         public void setSomething(Integer something){
             something+=1;
             super.setSomething(something);
         }
    }

    我在上面所做的是用我的新功能(something+1)覆盖超级类上的现有方法,而不必触及基类。如果您一年前编写了一个类,并且希望在不更改基础类的情况下转到版本2(测试噩梦),那么情况也是一样的。希望有帮助。


    虽然getter和setter不常见,但是这些方法的使用也可以在AOP/代理模式使用中使用。例如,对于审计变量,可以使用AOP来审计任何值的更新。没有getter/setter,除了在任何地方更改代码之外,都不可能实现。就个人而言,我从未使用过AOP,但它显示了使用getter/setter的另一个优势。


    getter/setter的一个相对现代的优势是,它使在带标记(索引)的代码编辑器中浏览代码更加容易。例如,如果要查看谁设置了成员,可以打开setter的调用层次结构。

    另一方面,如果成员是公共的,那么这些工具不能过滤对成员的读/写访问。所以你必须在成员的所有使用中努力。


    如果您想要一个只读变量,但不希望客户端改变访问它的方式,请尝试使用此模板类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    template<typename MemberOfWhichClass, typename primative>                                      
    class ReadOnly {
        friend MemberOfWhichClass;
    public:
        template<typename number> inline bool   operator==(const number& y) const { return x == y; }
        template<typename number> inline number operator+ (const number& y) const { return x + y; }
        template<typename number> inline number operator- (const number& y) const { return x - y; }
        template<typename number> inline number operator* (const number& y) const { return x * y; }  
        template<typename number> inline number operator/ (const number& y) const { return x / y; }
        template<typename number> inline number operator<<(const number& y) const { return x << y; }
        template<typename number> inline number operator^(const number& y) const  { return x^y; }
        template<typename number> inline number operator~() const                 { return ~x; }
        template<typename number> inline operator number() const                  { return x; }
    protected:
        template<typename number> inline number operator= (const number& y) { return x = y; }      
        template<typename number> inline number operator+=(const number& y) { return x += y; }      
        template<typename number> inline number operator-=(const number& y) { return x -= y; }      
        template<typename number> inline number operator*=(const number& y) { return x *= y; }      
        template<typename number> inline number operator/=(const number& y) { return x /= y; }      
        primative x;                                                                                
    };

    实例使用:

    1
    2
    3
    4
    class Foo {
    public:
        ReadOnly<Foo, int> cantChangeMe;
    };

    记住,还需要添加位运算符和一元运算符!这只是为了让你开始


    我将让代码本身说明:

    1
    2
    3
    4
    5
    Mesh mesh = new Mesh();
    BoundingVolume vol = new BoundingVolume();
    mesh.boundingVolume = vol;
    vol.mesh = mesh;
    vol.compute();

    你喜欢吗?下面是设置者:

    1
    2
    3
    Mesh mesh = new Mesh();
    BoundingVolume vol = new BoundingVolume();
    mesh.setBoundingVolume(vol);


    这是一个有更好答案的好问题。因为它们太多了,我会再多放一点。这个例子基于C,只是一段有用的代码。已经解释了括号中的验证数据。

    1
    2
    3
    4
    5
    6
    7
    public class foo
    {
        public int f1 { get; set; }             // A classic GS
        public int f2 { get; private set; }     // A GS with public read access, the write is only on the private level
        public int f3 { private get; set; }     // A GS where"You can set, but you can't get" outside the class
        public int f4 { get; set; } = 10;       // A GS with default value, this is a NEW feature of C# 6.0 / .NET 4.6
    }