【问题标题】:Does this code violate open-closed principle?这段代码是否违反了开闭原则?
【发布时间】:2019-07-09 19:30:31
【问题描述】:

我想知道下面的代码是否违反了开闭原则。

AnimalDog 的父类,但是Animal 具有帮助ObjectMapper (反)序列化类的杰克逊注解。扩展Animal 的任何人都必须只编辑Animal 上存在的注释,以确保(反)序列化按预期工作,而不影响类。

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = JsonTypeInfo.As.PROPERTY, 
  property = "type")
@JsonSubTypes({ 
  // all subclasses
  @Type(value = Dog.class, name = "dog")
})
public abstract class Animal {
    // fields, constructors, getters and setters
}

public class Dog extends Animal {

}

【问题讨论】:

    标签: java oop design-patterns jackson jackson-databind


    【解决方案1】:

    打开/关闭意味着一个类应该对扩展开放,但对修改关闭。

    换句话说...如果你想改变一个类的行为你应该以某种方式扩展它,但你不应该修改它。

    你可以扩展一个类

    • 创建子类。这通常使用例如完成。模板方法模式。
    • 定义类 A 使用的接口,以便可以通过向其传递该接口的另一个实例来扩展其行为,例如一种策略模式。一个很好的现实生活示例是 TreeSet(Comparator<? super E> comparator),因为它的排序行为可以在不修改 TreeSet 本身的情况下更改。

    在我看来,@JsonSubTypes 注释不是Animal 类行为的一部分。它改变了另一个类的行为——对象映射器。因此,这并不是真正的违规。并不是真的意味着即使你不改变行为,你也必须触摸Animal 类并重新编译它。

    注解的设计真的很奇怪。为什么那个 json 开发人员不允许您在子类上添加注释,例如就像 JPA 在层次映射方面所做的那样。见DiscriminatorValue

    超类型引用子类型是一种奇怪的设计。 抽象类型不应依赖于具体类型。在我看来,这是一个应该始终适用的原则。

    【讨论】:

    • 感谢您分享不同的观点!按照定义,当抽象类的源代码被修改时,所有情况下都违反了开闭原则。类的注释和文档是源代码的一部分吗?
    【解决方案2】:

    确实如此。开闭原则的思想是使对象可扩展而无需在内部修改它们。由于Animal 的任何新子代都必须对其进行修改才能正常工作,因此违反了原则。

    【讨论】:

      【解决方案3】:

      理论观点

      Open/closed principle 就像整个SOLID 一样是Utopia。我们应该朝着这个方向不断升级我们的代码,但可能我们永远不会到那里,因为这是不可能的。让我们阅读下面的文章,看看经典的 gettersannotation 构造如何值得商榷。

      1. Printers Instead of Getters
      2. Java Annotations Are a Big Mistake

      实用观点

      像每个实际的程序员一样,我喜欢使用好的工具来解决问题,而不是自己实现一些新的东西。当我被要求将给定模型序列化为 JSON 文件时,我正在检查它是否是:

      1. 开源
      2. 快速
      3. 正在积极开发中
      4. 使用方便

      当我们谈论Jackson 和它的注释时,我认为,我们可以在理论和实践之间找到黄金中间道路。这要归功于MixIn 功能。您可以将模型与其序列化到JSON 的方式分开。当然,当您添加扩展基类的新类时,您需要使用注释更改MixIn interface,但这是我们需要付出的代价。

      编辑或为什么我忘了回答问题?

      抱歉,我忘了回答上述示例是否违反Open/Closed principle 的问题。首先,从Wikipedia article获取定义:

      一个类是关闭的,因为它可以被编译,存储在一个库中, 基线,并由客户端类使用。但它也是开放的,因为任何 新类可以将其用作父类,添加新功能。当一个后代 类已定义,无需更改原来的或 打扰它的客户。

      以上示例违反了When a descendant class is defined, there is no need to change the original 部分。即使我们使用MixIn,也需要更改应用程序的其他部分。更重要的是,如果您的解决方案在 99.99% 的情况下使用 annotations,则您违反了这一部分,因为需要以某种方式配置隐藏在它们后面的功能。

      【讨论】:

      • 抱歉,我忘了回答基本问题,因为在我看来,从理论的角度来看:违反,而从实践的角度来看:因为cost 不高。但是每个人都应该为自己定义cost
      • 感谢您的编辑!除了MixIn,还有另一种解决方案。 @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type") public abstract class Animal { // fields, constructors, getters and setters } @JsonTypeName(value = "Dog") public class Dog extends Animal { }
      • 来自同一个Wikipedia:“如果一个模块[它]可供其他模块使用,则称该模块已关闭。这假设该模块已被赋予定义明确的、稳定的描述”。此解决方案不是well-definedstable,因为您可以从根目录中删除JsonTypeInfo 注释,所有后代都会突然中断。您不能强制后代拥有此注释,也不能强制根类拥有它,因为接口/类没有损坏。它包含相同的方法。 API 是一样的,但有些东西不起作用。 SO 上超过 50% 的问题都存在问题。
      • 我同意 SOLID 代码是应许之地。我同意我们应该努力实现目标。我不同意这是不可能的。
      • @aridlehoover,当我写is not possible 时,我指的是整个项目。当然,我们可以在SOLID模式下实现项目的某些部分,但整个项目是不可能的,或者很难实现目标。
      猜你喜欢
      • 2021-12-15
      • 2014-02-15
      • 2010-11-29
      • 1970-01-01
      • 2018-06-11
      • 2015-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多