POJO 是反面向对象(OO)的吗?

Is POJO anti Object Orientation (OO)?

在大多数定义中,POJO(普通旧 Java 对象)是具有属性的对象,只有 getters/setters。有些人将这些命名为 DTO。所以基本上我喜欢称它们为 'Data classes'。无论名称是什么,我都知道有这些类型的对象,其中只有 getters/setters。这些通常用于开发多层 MVC web 应用程序 - 这些对象将通过不同的层传递。

也许我的问题有点自以为是——但这是否是纯粹的 OOP? 我决心找出这个问题的答案,即什么是对的,什么是错的?

...因为,我从小就知道 OOP 就是通过交互将现实生活 scenarios/situations 建模为对象。对象本身封装了数据和作用于该数据的行为。 - 在我看来,只有 getters/setters 的对象不符合这个定义。

另外,数据 类 很糟糕(代码味道)——这就是我在大学里学到的东西。

您可能将 POJO 与 Beans 混淆了一点,但两者之间存在显着差异。

  • bean 是一个 class,它对其 getters/setters 的命名有很强的约定(get___ 用于任何非布尔值,is___ 用于布尔值) . POJO 没有。
  • 一个 bean 中有一个无参数的构造函数,它可以实现 Serializable。 POJO 可能也做不到。
  • bean 的字段必须是私有的。 POJO 的字段可能不是。

现在,问题来了:如果做得好,POJO 可以正确地封装数据并以面向对象的方式运行。您将对特定对象进行操作,而不是暴露其内部结构,以便您必须 fiddle 与(例如,如果您只有一个地图可以使用;它将是 如果将该地图封装在一个对象中,稍微 会更好)。

它不会真正以 OO 方式运行的时候是 POJO 承担了比它应该承担的更多的责任。

当您说数据 类 时,我假设您说的是与 C 和 C++ 中的 struct 类似的结构,其中它仅包含数据。我个人不认为数据 类 不好,也不是代码味道。

数据类 允许您将属于同一实体的多个属性组合在一起。例如:

class Monster{
    String name;
    int hp;
    int damage;
}

通过以上你可以在参数中传递整个怪物对象:

public void attack(Monster m){
    //Attack monster
}

而不是:

public void attack (String name, int hp, int damage){
    //Attack monster
}

如果不好,为什么人们偶尔会在 C++ 中使用 struct 而不是 类?

没有纯 OOP 这样的东西。多年来,人们使用 OO 的方式发生了变化。

我更倾向于为我的类型添加行为 (https://en.wikipedia.org/wiki/Domain-driven_design)。如果将类型视为数据容器 (POJO),则需要将该行为放在某处。通常是一个叫做 xxxService 的东西。这是一个额外的间接级别,只是为了做一些可能是 "POJO" 的核心业务的事情。

在编写更多代码后,您可能最终会在其他地方需要此功能。现在你需要将它提取到一些你可以共享的助手。这意味着您将有类似 2 个服务调用对您的 POJO 起作用的相同方法。添加了另一个间接级别。

但这是一种方法。我从事过很多项目,很多开发人员对 setter/getter 感到更舒服。如果你的团队对 setters/getters 感觉更舒服,为什么不呢。 它不会终止项目,但您必须每天查看它 ;)

POJO 和 XXService 是个坏主意!任何动作(方法)都属于一个对象。

好吧,看个例子,就是人家的简单动作:

Class People {
    string name;
}

Class PeopleService{
   public void pee(People people){
      System.out.print(people.name + " relax...");
   }   
}

啊哈,现在我们需要扩展它...,我们知道男人和女人之间的一些区别

class Man extends People{}

class Woman extends People{}

如果只有一项服务,你应该写:

Class PeopleService {
   public void pee(People people){
      if(people instanceof Man)//if ... and if ...
          System.out.println("stand up");
      if(people instanceof Woman)
          System.out.println("sit down");
      System.out.print(people.name + " relax...");
   }  
}

如果您延长服务时间:

Class ManService extends PeopleService {
    @Override
    public void pee(People people){
      if(people instanceof Man)
          System.out.println("stand up");
      super.pee();
   }
}

Class WomanService extends PeopleService {
    @Override
    public void pee(People people){
      if(people instanceof woman) //you must check it
          System.out.println("sit down");
      super.pee();
   }
}

//maybe wrong when anyone call these service
...
People p = new Woman();
...
manService.pee(p);//you can't stop it! 

比我们能写一个 serviceFactory...,太疯狂了! 但是 OOP 代码,People 是域对象,它是完美的!

Class People {

   String name;

   public void pee(){
      System.out.print(name + " relax...");
   } 
}

Class Man extends People {
   @Override 
   public void pee() {
      System.out.println("stand up");
      super.pee();
   }
}

Class Woman extends People {
   @Override 
   public void pee() {
      System.out.println("sit down");
      super.pee();
   }
}

//we call it now, don't care about details 
...
People p1 = new Man();
People p2 = new Woman();
p1.pee();
p2.pee();

基本想到了OOP!大多数程序员都忘记了!

我意识到这个问题已经有一年了。但是我觉得其他答案没有解决 POJO 的问题。

回答您的问题:"Is POJO anti Object Orientation (OO)?"。

不,是。

简单解答

不,因为 OO 是关于您需要的建模概念。 POJO 正是这样做的。它将信息位捆绑到一个对象中。

是的,因为 OO 中使用了一些 POJO 违反的原则。比如tell don't ask-原则。

详细答案解释

POJO 只是 data-containers。您不会告诉 POJO 做任何事情。它只是一些信息的表示。因此,您询问它的当前状态。这意味着您违反了 tell dont ask.

然而,这在某一时刻是不可避免的。就像函数式编程语言引入 IO 定义来指定函数不是 "pure" 一样。你将不得不走出你整洁的小 OO-bubble 程序去问事情.. urhg.. 外面..

包装您的 POJO

这就是我将 POJO 包装在模型中的原因(命名取决于您喜欢什么,我称它们为模型)。您可以通过扩展(就像克劳斯在他的回答中所做的那样)或组合(我更喜欢)来做到这一点。哪一个让你爱不释手。

POJO 定义了一个 real-life/abstract data-source(你并不总是建模 real-life 东西)。

实行告诉不问

然后将其传递给负责操作 POJO 的 DataModel。

它通过 "told" 来操纵 POJO 来做到这一点。我这样做的原因是因为有时 POJO 包含类型信息。 因此,直接操作 POJO 通常会导致某些 Class 不得不检查 POJO 的类型。

经常做这样的事情:

if(POJO.type == "something"){
   this.doSmthWithPOJO();
}

现在您可能会得到大量不同类型的 POJO。在那种情况下,你可能会得到这样的结果:

if(POJO.type == "something"){
       this.doSomethingWithPOJO(POJO);
} 
else if (POJO.type == "something else")
{
    this.doSomethingElseWithPOJO(POJO);
}
else if( POJO.type == "something else entirely")
{
    this.doSomethingElseEntirelyWithPOJO(POJO);
}
else{
   this.print("This is starting to get ugly...");
}

您这样做是因为 POJO 中的类型信息意味着您必须做一些稍微不同的事情。将 POJO 放在具有您可以调用的函数的 DataModel 中,可以让您做一些更简洁的事情。在将操作或类型检查的责任转移到 class.

之外
POJOTypeDataModel model = new POJOTypeDataModel(POJO);

那么上面的If-elseif-else-statement就简单多了

model.doSomething();

正如我上面提到的,您必须在某一时刻询问 "POJO.type"。 当我第一次需要 data-source 时,我会这样做。 data-source 调用 return POJO。您检查它的类型和 return DataModel 而不是 POJO。

重要的是要强调您只需检查一次 POJO 的类型!现在您 retrieve/create POJO 并将其包装起来。

如果 POJO 没有包装或没有专门类型化,您将不得不更多地询问 POJO 的类型。这扰乱了使用 POJO 的 class 的初衷。 它还摆脱了很多 if-statements.

此外,您还鼓励使用抽象而不是特定类型。

简而言之

执行以上操作可以帮助您:

  1. 保持外面。由于 POJO 通常是 data-source.
  2. 尽量坚持tell don't ask
  3. 鼓励使用抽象而不是具体类型(不能比 POJO 更具体)。
  4. 写得更干净(你不会 type-check 无处不在)和更容易理解的代码(众所周知,人们不善于理解大块的过程代码)。
  5. 最终,包装可以帮助您避免更改代码。因为您可能 type-check 在您的代码中到处使用 POJO(参见 if 示例)。现在您只需在 DataModel 上调用一个函数,它就知道该做什么了。

回到简单答案

您无法避免向 POJO 询问信息。然而,它非常适合为 data-source 建模。

因此包装它并使用抽象来调用 DataModel 限制了对某些核心的违反 OO-principles 并且它使代码更整洁。