被批评的人和二传手通常被批评为不合适的OO。另一方面,我见过的大多数OO代码都具有广泛的Getters和Setters。

何时有合理的距离和固定器?您是否尝试避免使用它们?他们一般过度使用吗?

如果您喜欢的语言具有属性(我的属性),那么此问题也被视为该问题的Geters和Setters。从OO方法论角度来看,它们是同一件事。他们只有更好的语法。

Getter/Setter批评的来源(有些从评论中获取以使他们更好地可见性):

简单地说出批评:获取器和播放器允许您从对象外部操纵对象的内部状态。这违反了封装。只有对象本身才能关心其内部状态。

以及代码的示例程序版本。

struct Fridge
{
    int cheese;
}

void go_shopping(Fridge fridge)
{
     fridge.cheese += 5;
}

密码的突变器版本:

class Fridge
{
     int cheese;

     void set_cheese(int _cheese) { cheese = _cheese; }
     int get_cheese() { return cheese; }
 }

void go_shopping(Fridge fridge)
{
     fridge.set_cheese(fridge.get_cheese() + 5);        
}

Getters和Setters使代码更加复杂,而无需提供适当的封装。由于内部状态可访问其他对象,因此通过添加这些Getter和setter,我们不会获得很多收益。

以前已经在堆栈溢出上讨论了这个问题:

有帮助吗?

解决方案

拥有Getters和Setter本身并不能断开封装。什么是断裂封装是自动为每个数据成员添加一个getter和一个设置器(每个数据成员 场地, ,在Java Lingo),没有任何想法。虽然这比将所有数据成员公开都公开好,但这只是一小步。

封装的目的不是您不应该从对象外部知道或更改对象的状态,而是应该有一个合理的 政策 这样做。

  • 一些数据成员可能完全是对象内部的,并且不应具有getters也不应具有固定器。

  • 某些数据成员应仅阅读,因此他们可能需要Getters,但不需要设置器。

  • 一些数据成员可能需要保持彼此一致。在这种情况下,您不会为每个设置提供一个设置器,而是一种可以同时设置它们的方法,因此您可以检查值的一致性。

  • 某些数据成员可能只需要以某种方式更改,例如增加或减少固定量。在这种情况下,您将提供 increment() 和/或 decrement() 方法,而不是设置器。

  • 实际上,其他人可能需要被读写,并且既有一个getter又有一个固定器。

考虑一个例子 class Person. 。假设一个人有名字,一个社会保险号和一个年龄。假设我们不允许人们更改其姓名或社会安全号码。但是,该人的年龄应每年增加1个。在这种情况下,您将提供一个构造函数,将名称和SSN初始化为给定值,并将年龄初始化为0。您还将提供一种方法 incrementAge(), ,这将使年龄增加1。您还将为这三个年龄提供Getters。在这种情况下,不需要设置器。

在此设计中,您允许从班级外部检查对象的状态,并允许将其从班级外部进行更改。但是,您不允许国家任意更改状态。有一项政策,有效地指出,名称和SSN不能完全更改,并且年龄可以一次增加1年。

现在,假设一个人也有薪水。人们可以随意换工作,这意味着他们的薪水也会改变。为了建模这种情况,我们别无选择,只能提供 setSalary() 方法!在这种情况下,允许随意更改工资是一个完全合理的政策。

顺便说一下,在您的榜样,我会给全班 FridgeputCheese()takeCheese() 方法,而不是 get_cheese()set_cheese(). 。那么您仍然会封装。


public class Fridge {
  private List objects;
  private Date warranty;

  /** How the warranty is stored internally is a detail. */
  public Fridge( Date warranty ) {
    // The Fridge can set its internal warranty, but it is not re-exposed.
    setWarranty( warranty );
  }

  /** Doesn't expose how the fridge knows it is empty. */
  public boolean isEmpty() {
    return getObjects().isEmpty();
  }

  /** When the fridge has no more room... */
  public boolean isFull() {
  }

  /** Answers whether the given object will fit. */
  public boolean canStore( Object o ) {
    boolean result = false;

    // Clients may not ask how much room remains in the fridge.
    if( o instanceof PhysicalObject ) {
      PhysicalObject po = (PhysicalObject)o;

      // How the fridge determines its remaining usable volume is a detail.
      // How a physical object determines whether it fits within a specified
      // volume is also a detail.
      result = po.isEnclosedBy( getUsableVolume() );
    }

     return result;
  }

  /** Doesn't expose how the fridge knows its warranty has expired. */
  public boolean isPastWarranty() {
    return getWarranty().before( new Date() );
  }

  /** Doesn't expose how objects are stored in the fridge. */
  public synchronized void store( Object o ) {
    validateExpiration( o );

    // Can the object fit?
    if( canStore( o ) ) {
      getObjects().add( o );
    }
    else {
      throw FridgeFullException( o );
    }
  }

  /** Doesn't expose how objects are removed from the fridge. */
  public synchronized void remove( Object o ) {
    if( !getObjects().contains( o ) ) {
      throw new ObjectNotFoundException( o );
    }

    getObjects().remove( o );

    validateExpiration( o );
  }

  /** Lazily initialized list, an implementation detail. */
  private synchronized List getObjects() {
    if( this.list == null ) { this.list = new List(); }
    return this.list;
  }

  /** How object expiration is determined is also a detail. */
  private void validateExpiration( Object o ) {
    // Objects can answer whether they have gone past a given
    // expiration date. How each object "knows" it has expired
    // is a detail. The Fridge might use a scanner and
    // items might have embedded RFID chips. It's a detail hidden
    // by proper encapsulation.
    if( o implements Expires && ((Expires)o).expiresBefore( today ) ) {
      throw new ExpiredObjectException( o );
    }
  }

  /** This creates a copy of the warranty for immutability purposes. */
  private void setWarranty( Date warranty ) {
    assert warranty != null;
    this.warranty = new Date( warranty.getTime() )
  }
}

其他提示

在Java中获得Geters和Setter的基本原因非常简单:

  • 您只能指定 方法, ,不是字段,在接口中。

因此,如果要允许字段通过界面,则需要读者和作者方法。这些传统上被称为“ X”字段X的GETX和SETX。

http://www.adam-bien.com/roller/abien/entry/encapsulation_violation_with_getters_and_and

Javabean风格:

connection.setUser("dukie");
connection.setPwd("duke");
connection.initialize();

oo风格:

connection.connect("dukie","duke");

好吧,显然我更喜欢后一种方法。它不会流血的实现详细信息,更简单,更简洁,并且所有必需的信息都包含在方法调用中,因此将其正确正确。我还希望尽可能使用构造函数中的参数设置私有成员。

您的问题是,何时getter/setter是合理的?也许当需要更改模式时,或者您需要询问对象以获取某些信息。

myObject.GetStatus();
myObject.SomeCapabilitySwitch = true;

在思考它时,当我第一次开始在C#中编码时,我用上面说明的Javabeans风格编写了许多代码。但是,随着我在语言方面的经验时,我开始在构造函数中进行更多的成员设置,并使用看起来更像上述OO样式的方法。

何时有合理的距离和固定器?

当行为“获取”和“集”实际上与模型中的行为相匹配时, 实际上永远不会.

其他所有用途都只是作弊,因为业务领域的行为尚不清楚。

编辑

这个答案可能会很轻率,所以让我扩展。上面的答案大多是正确的,但专注于OO设计的编程范例以及一些错过大局的范例。根据我的经验,这使人们认为避免获得和播放器是OO编程语言的一定学术规则(例如,人们认为用属性代替getters是很棒的)

实际上,这是设计过程的结果。您不必涉及界面,封装和模式,即认为它是否破坏了这些范例以及什么是或不是好的OO编程。唯一最重要的一点是,如果您的域中没有像这样的工作,那么将它们放入您的范围内就不会对您的域进行建模。

现实情况是,您的域空间中的任何系统都有很大的距离。您不能走上负责工资单的男人或女人,只是说 “将此工资设置为X” 或者 “给我这个薪水”. 。这种行为根本不存在

如果您将其放入代码中,则不会设计系统以匹配域的模型。是的,这破坏了接口和封装,但这不是重点。关键是您正在建模不存在的东西。

更多的您可能会错过一个重要的步骤或过程,因为可能有一个原因,我不能只是走上付款并说将此工资设置为X。

当人们使用Getters和Setter时,他们倾向于将此过程的规则推向错误的位置。这更远离您的领域。使用现实世界的例子,就像薪水一样,假设走进去的随机人有许可获得这些价值观,否则他不会要求他们。不仅不是域的方式,而且实际上是关于域的谎言。

通常,Getters和Setter是一个坏主意。如果一个字段在界面上不是逻辑上的一部分,而您将其私有化,那很好。如果它是界面的一部分,并且您将其公开,那很好。但是,如果您将其私有化,然后转身通过提供Getter和Setter再次有效地公开,您会回到开始的地方,除了您的代码现在更加详细和混淆。

显然,有例外。在Java中,您可能需要使用接口。 Java标准库的向后兼容性要求非常极端,以至于超过了代码质量的正常度量。甚至您实际上可能正在处理传奇但罕见的情况,在这种情况下,您很有可能以稍后在飞行计算中替换存储的字段而不会打破界面。但是这些是例外。 Getters和Setters是一个需要特殊理由的反模式。

字段是直接加入还是通过方法并不重要。

班级不变(有用的)很重要。为了保护它们,有时我们需要无法从外部改变一些东西。例如。如果我们有具有分离宽度和高度的班级正方形,则更改其中一个使其成为正方形以外的东西。因此,如果它是矩形,我们需要更改方法,我们可能会有设置器/公共字段。但是二传手比测试它是否大于零会更好。

以及具体语言(例如Java)是我们需要这些方法(接口)的原因。方法的另一个原因是兼容性(源和二进制)。因此,更容易添加它们,然后考虑公共场是否足够。

顺便提一句。我喜欢使用公共最终字段的简单不变的价值持有课程。

您可能希望将内部设备更改为任何界面相同的东西。如果您的接口没有变化,则它们代码将不会破裂。您仍然可以按照自己的意愿更改内部。

我的方法是 -

当我期望以后散布数据时,一个Getter/setter是合理的。另外,如果发生变化,我经常将数据推入GETER/SETETER。

如果是POD结构,我会留下可用的插槽。

在更抽象的层面上,问题是“谁管理数据”,这取决于项目。

如果使用Getters和Setter感到很复杂,那么问题可能是语言,而不是概念本身。

这是来自 第二 用Ruby编写的示例:

class Fridge
  attr_accessor :cheese
end

def go_shopping fridge
  fridge.cheese += 5
end

注意到它看起来很像 第一的 Java中的例子?当将Getters和Setter视为一流的公民时,它们不是使用的琐事,而增加的灵活性有时可能是一个真正的福音 - 例如,我们可以决定在新的冰箱上返回奶酪的默认值:

class Fridge
  attr_accessor :cheese

  def cheese
    @cheese || 0
  end
end

当然,会有许多变量根本不应该公开暴露。不必要地暴露内部变量会使您的代码恶化,但是您几乎不能将其归咎于Getters和Setters。

考虑 Size 封装宽度和高度的类。我可以通过使用构造函数来消除固定器,但是这如何帮助我绘制一个矩形 Size?宽度和高度不是类的内部数据;它们是共享数据,必须适用于大小的消费者。

对象由行为和状态或属性组成。如果没有暴露状态,则只有行为是公开的。

没有状态,您将如何对对象集合进行分类?您如何搜索对象的特定实例?如果您仅使用构造函数,那么当您的对象具有很长的属性列表时会发生什么?

未经验证,不应消耗任何方法参数。因此,写作是懒惰的:

setMyField(int myField){
    this.myField = myField;
}

如果您确实以这种方式编写它,那么您至少有准备使用设置器进行验证;它比公共场所好 - 但勉强。但是至少您有一个一致的公共界面,您可以回来并放入验证规则而不破坏客户的代码。

getters,setter,属性,突变器,将其称为您的内容,但它们是必要的。

如果Getters和Setter违反了封装和真实的OO,那么我会严重麻烦。

我总是觉得一个对象代表了您需要做的任何最佳想法。

我刚刚完成了一个在Java产生迷宫的程序,并且我的课程代表“迷宫广场”。我在此类中有代表坐标,墙壁和布尔值等的数据。

我必须有一些更改/操纵/访问此数据的方法!我在没有得到的人和固定器的情况下该怎么办? Java不使用属性,也不使用该课程本地的所有数据肯定是违反封装和OO的。

首先,有两种类型的对象,我将评论价值和服务类型。

服务类型绝不应该拥有设定器,无论他们需要什么依赖项都不应获得。通过依赖关系的最佳方法是通过构造函数或工厂,从一开始就完全形成所有实例,简单明了。

价值类型也应该是不可变的,在Oher手上,有时这是不切实际的,例如ORM或其他一些映射,例如从小部件到对象。从一层或部分传递到另一层或另一部分的所有其他值类型都不可变,并且不应有设置器。

就像任何其他公共方法是合理的一样,getter/setter是合理的。理由主要是因为我们想向外界提供一个界面,以定义我们的班级如何与其他班级互动。我们之所以这样做,是因为我们想减少单独实体之间的耦合。由于许多原因,减少耦合是一件好事:

  • 我可以从当前代码中使用相同的类界面,尽管同时该类添加了新方法(但保留了旧接口)
  • 可以改变班级的内部表示,而不会影响其消费者
  • 减少错误的机会:如果您将内部数据私有化,您可以确保外部代码不会弄乱您的内部数据

是的,Getters和Setters是面向对象的编程中的反模式,应该 绝不 使用: http://www.yegor256.com/2014/09/16/getters-and-setters-are-evil.html. 。简而言之,它们不适合面向对象的范例,因为它们鼓励您像数据结构一样对待对象。这是一个主要的误解。

我希望公司开发您的编程语言自动生成的IDE开发的任何东西都不会违反“对象取向原理”。

话虽如此,只要您有一个公共范围的变量,请使用Geter和Setter,并且您的黄金。在我看来,这是非常面向对象的封装原理的必要元素 - 即使它看起来像很多垃圾代码。

使用它们的原因是,适当的封装可能仍会发生,只要您抽象出使与您的对象造成的人感觉到您的对象的层,您甚至可以在没有他的情况下将其拿走,甚至不知道吗?

可以说,一所房屋无法忍受,OO的一位租户不会打开自己,您不能同时提供封装和过早优化。

我以为我们在这里有更多常规的答案吗?

Getters和Setter通常都非常糟糕(任何人说其他错误的人,很简单)。

尽管您需要记住不要过度工程师,但切勿制造不需要的Getter或Setter。您不应该有任何Getter或Setter的其他班级都不会使用的任何信息。

但是,您之所以不公开字段,而有距离的人则主要是:

  • 不可能使用字段进行适当的测试。在这方面,获取器/固定器要好得多。

  • 在实时编程设置中正确使用字段是不可能的。同步的Getters/Setter是必经之路。

  • 接口主题(已经解释了,所以我不会)。

  • 正确重复系统时,它更容易使用。

  • 比Getter/setter更难知道使用您的课程的类别更难知道哪些类,什么会破裂。

因此,您唯一使用公共场而不是Getter/Setter的时间是当您不做官方项目而不是为了娱乐而无法打扰Getter/Setter时。

许可以下: CC-BY-SA归因
scroll top