JavaRush /Java 博客 /Random-ZH /喝咖啡休息#85。我通过艰难的方式学到了三个 Java 课程。如何在代码中使用 SOLID 原则

喝咖啡休息#85。我通过艰难的方式学到了三个 Java 课程。如何在代码中使用 SOLID 原则

已在 Random-ZH 群组中发布

我艰难学到的三个 Java 教训

来源:Medium 学习 Java 很困难。我从错误中吸取了教训。现在你也可以从我的错误和痛苦经历中吸取教训,而你不一定需要拥有这些。 喝咖啡休息#85。 我通过艰难的方式学到了三个 Java 课程。 如何在代码中使用 SOLID 原则 - 1

1. Lambda 可能会带来麻烦。

Lambda 表达式通常超过 4 行代码,并且比预期的要大。这会给工作记忆带来负担。您需要从 lambda 更改变量吗?你不能那样做。为什么?如果 lambda 可以访问调用站点变量,则可能会出现线程问题。因此您无法更改 lambda 中的变量。但 lambda 中的快乐路径工作得很好。运行时失败后,您将收到以下响应:
at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
跟踪 lambda 堆栈跟踪很困难。这些名称令人困惑并且难以追踪和调试。更多 lambda = 更多堆栈跟踪。调试 lambda 的最佳方法是什么?使用中间结果。
map(elem -> {
 int result = elem.getResult();
 return result;
});
另一个好方法是使用先进的 IntelliJ 调试技术。使用 TAB 选择要调试的代码并将其与中间结果结合起来。 “当我们停在包含 lambda 的行时,如果我们按 F7(步入),那么 IntelliJ 会突出显示需要调试的片段。我们可以使用 Tab 切换要调试的块,一旦我们决定,请再次按 F7。” 如何从 lambda 访问调用站点变量?您只能访问最终或实际最终变量。您需要围绕调用位置变量创建一个包装器。使用AtomicType或使用您自己的类型。您可以使用 lambda 更改创建的变量包装器。如何解决堆栈跟踪问题?使用命名函数。这样,您可以快速找到负责的代码、检查逻辑并解决问题。使用命名函数来去除神秘的堆栈跟踪。相同的 lambda 是否重复?将其放在命名函数中。您将有一个参考点。每个 lambda 都有一个生成的函数,这使得跟踪变得困难。
lambda$yourNamedFunction
lambda$0
命名函数解决不同的问题。大 lambda。命名函数分解大型 lambda,创建更小的代码片段,并创建可插入函数。
.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. 列表的问题

您需要使用列表(Lists)。您需要一个 HashMap来存储数据。对于角色,您将需要一个 TreeMap。这样的例子还在继续。您无法避免使用集合。如何列清单?您需要什么样的清单?它应该是不可变的还是可变的?所有这些答案都会影响代码的未来。提前选择正确的列表,这样您就不会后悔。 Arrays::asList创建一个“端到端”列表。你不能用这份清单做什么?您无法调整它的大小。他是不可改变的。你在这里能做什么?指定元素、排序或其他不影响大小的操作。请小心使用Arrays::asList,因为它的大小是不可变的,但它的内容却不是。 new ArrayList()创建一个新的“可变”列表。创建的列表支持哪些操作?就是这样,这就是要小心的原因。使用new ArrayList()从不可变列表创建可变列表。 List::of创建一个“不可变”集合。其大小和内容在一定条件下不变。如果内容是原始数据,例如int,则列表是不可变的。看一下下面的例子。
@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### 使用可变内容测试 listOF ### a123
您需要创建不可变对象并将它们插入到List::of中。但是,List::of不提供任何不变性保证。 List::of提供不变性、可靠性和可读性。知道何时使用可变结构以及何时使用不可变结构。不应更改的参数列表应位于不可变列表中。可变列表可以是可变列表。了解创建可靠代码所需的集合。

3.注释会减慢你的速度

你使用注释吗?你了解他们吗?你知道他们做什么吗?如果您认为Logged注解适用于每种方法,那么您就错了。我使用Logged来记录方法参数。令我惊讶的是,它不起作用。
@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
这段代码有什么问题?这里有很多配置摘要。你会多次遇到这种情况。配置与常规代码混合。本身并不坏,但它吸引了你的眼球。需要注释来减少样板代码。您不需要为每个端点编写日志记录逻辑。无需设置事务,使用@Transactional。注释通过提取代码来减少模式。这里没有明显的赢家,因为两者都在游戏中。我仍然使用 XML 和注释。当您发现重复模式时,最好将逻辑移至注释中。例如,日志记录是一个很好的注释选项。寓意:不要过度使用注释,也不要忘记 XML。

奖励:您可能会遇到可选问题

您将使用Optional中的orElse。当您不传递orElse常量时,会发生不良行为。您应该意识到这一点,以防止将来出现问题。让我们看几个例子。当getValue(x)返回值时,执行getValue(y)如果getValue(x)返回非空可选值,则执行orElse中的方法。
getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
使用orElseGet。它不会执行非空Options的代码。
getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());

  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

结论

学习Java很难。你不可能在 24 小时内学会 Java。磨练你的技能。花时间学习并在工作中表现出色。

如何在代码中使用 SOLID 原则

来源:Cleanthecode 编写可靠的代码需要坚实的原则。在某些时候,我们都必须学习如何编程。老实说。我们很愚蠢。我们的代码是相同的。感谢上帝,我们有固体。 喝咖啡休息#85。 我通过艰难的方式学到了三个 Java 课程。 如何在代码中使用 SOLID 原则 - 2

坚实的原则

那么如何编写 SOLID 代码呢?其实很简单。您只需要遵循以下五个规则:
  • 单一责任原则
  • 开闭原则
  • 里氏替换原理
  • 接口分离原理
  • 依赖倒置原则
不用担心!这些原则比看起来简单得多!

单一责任原则

罗伯特·C·马丁 (Robert C. Martin) 在他的书中这样描述这一原则:“一个类应该只有一个改变的理由。” 我们一起来看两个例子。

1. 不该做什么

我们有一个名为User的类,它允许用户执行以下操作:
  • 注册账户
  • 登录
  • 首次登录时收到通知
这个类现在有几个职责。如果注册过程发生变化,User类也会发生变化。如果登录流程或通知流程发生变化,也会发生同样的情况。这意味着该类已超载。他有太多的责任。解决此问题的最简单方法是将责任转移给您的类,以便User类仅负责组合类。如果流程发生变化,您就会有一个明确的、单独的类需要更改。

2. 做什么

想象一个应该向新用户显示通知的类FirstUseNotification。它将由三个功能组成:
  • 检查通知是否已经显示
  • 显示通知
  • 将通知标记为已显示
这个类有多个改变的理由吗?不。该类有一个明确的功能 - 显示新用户的通知。这意味着班级有一个改变的理由。也就是说,如果这个目标发生变化。所以,这个类并没有违反单一责任原则。当然,有一些事情可能会发生变化:通知标记为已读的方式可能会发生变化,或者通知的显示方式可能会发生变化。然而,由于课程的目的是明确且基本的,所以这很好。

开闭原则

开闭原则是 Bertrand Meyer 提出的:“软件对象(类、模块、函数等)应该对扩展开放,但对修改关闭。” 这个原理其实很简单。您必须编写代码,以便可以在不更改源代码的情况下向其中添加新功能。这有助于防止出现需要更改依赖于已修改类的类的情况。然而,这一原则的实施要困难得多。迈耶建议使用继承。但这会带来牢固的联系。我们将在接口分离原则和依赖倒置原则中讨论这一点。于是Martin想出了一个更好的方法:使用多态性。此方法不使用传统的继承,而是使用抽象基类。这样,继承规范可以被重用,而无需实现。接口可以编写一次,然后关闭以进行更改。新函数必须实现该接口并扩展它。

里氏替换原理

这一原理是由图灵奖获得者芭芭拉·利斯科夫(Barbara Liskov)发明的,她因对编程语言和软件方法论的贡献而获得了图灵奖。在她的文章中,她将自己的原则定义如下:“程序中的对象应该可以用其子类型的实例替换,而不影响程序的正确执行。” 让我们以程序员的身份来看看这个原则。想象我们有一个正方形。它可能是一个矩形,这听起来很合乎逻辑,因为正方形是矩形的特殊形状。这就是里氏替换原理可以发挥作用的地方。无论您希望在代码中的何处看到矩形,也有可能出现正方形。现在假设您的矩形有SetWidthSetHeight方法。这意味着广场也需要这些手段。不幸的是,这没有任何意义。这意味着这里违反了里氏替换原则。

接口分离原理

与所有原则一样,接口分离的原则比看起来简单得多:“许多特定于客户端的接口比一个通用接口更好。” 与单一责任原则一样,目标是减少副作用和所需的更改数量。当然,没有人故意编写这样的代码。但很容易遇到。还记得前面原理中的平方吗?现在想象一下我们决定实施我们的计划:我们从一个矩形生成一个正方形。现在我们强制正方形实现setWidthsetHeight,这可能不会做任何事情。如果他们这样做,我们可能会破坏一些东西,因为宽度和高度不是我们所期望的。对我们来说幸运的是,这意味着我们不再违反里氏替换原则,因为我们现在允许在任何使用矩形的地方使用正方形。然而,这产生了一个新问题:我们现在违反了分离接口的原则。我们强制派生类实现它选择不使用的功能。

依赖倒置原则

最后一个原则很简单:高层模块应该是可重用的,并且不应该受到低层模块更改的影响。
  • A. 高层模块不应该依赖于低层模块。两者都必须依赖于抽象(例如接口)。
  • B. 抽象不应依赖于细节。细节(具体实现)必须取决于抽象。
这可以通过实现分离高级模块和低级模块的抽象来实现。该原理的名称表明依赖的方向发生变化,但事实并非如此。它只是通过在它们之间引入抽象来分离依赖关系。结果,您将获得两个依赖项:
  • 高层模块,取决于抽象
  • 依赖于相同抽象的低级模块
这看起来可能很困难,但事实上,如果你正确应用开/闭原则和里氏替换原则,它就会自动发生。就这样!您现在已经了解了支撑 SOLID 的五个核心原则。遵循这五个原则,您可以使您的代码变得令人惊叹!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION