ARTS 打卡第四周(2023.9.4~2023.9.8)

77 阅读7分钟

1. Algorithm 一道算法题

本周算法题分糖果,详情见题解:分糖果

2. Review 读一篇英文文章

本周文章为 MF 大佬的 # Inversion of Control Containers and the Dependency Injection pattern 的剩余部分

A Naive Example

To help make all of this more concrete I'll use a running example to talk about all of this. Like all of my examples it's one of those super-simple examples; small enough to be unreal, but hopefully enough for you to visualize what's going on without falling into the bog of a real example.

为了使所有内容更具体化,我将使用一个运行示例来讨论。像我所有的例子一样,这是一个非常简单的示例;它足够简单以至于并不真实,但希望足够让你能够想象正在发生的事情,而不会陷入一个真实示例的繁琐情境中。

In this example I'm writing a component that provides a list of movies directed by a particular director. This stunningly useful function is implemented by a single method.

在这个示例中,该组件提供了由特定导演指导的电影列表。这个异常有用的功能是由一个单一的方法来实现的。

class MovieLister...

  public Movie[] moviesDirectedBy(String arg) {
      List allMovies = finder.findAll();
      for (Iterator it = allMovies.iterator(); it.hasNext();) {
          Movie movie = (Movie) it.next();
          if (!movie.getDirector().equals(arg)) it.remove();
      }
      return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
  }

The implementation of this function is naive in the extreme, it asks a finder object (which we'll get to in a moment) to return every film it knows about. Then it just hunts through this list to return those directed by a particular director. This particular piece of naivety I'm not going to fix, since it's just the scaffolding for the real point of this article.

一个函数的实现方式非常简单。它会让一个 finder 对象(稍后会详细介绍)返回它所知道的所有电影。然后,它会通过遍历这个列表来返回指定导演执导的电影。这个实现方式非常简单,但我不打算修复它,因为这只是本文真正重点的基础结构。

The real point of this article is this finder object, or particularly how we connect the lister object with a particular finder object. The reason why this is interesting is that I want my wonderful moviesDirectedBy method to be completely independent of how all the movies are being stored. So all the method does is refer to a finder, and all that finder does is know how to respond to the findAll method. I can bring this out by defining an interface for the finder.

这篇文章的真正要点是这个查找器对象,或者特别是我们如何将列表对象与特定的查找器对象连接起来。之所以这一点很有趣,是因为我希望我的 moviesDirectedBy 方法能够完全独立于所有电影的存储方式。因此,这个方法所做的只是引用一个查找器,而这个查找器只需要知道如何响应 findAll 方法。为了表达这一点,我可以为这个查找器定义一个接口。

public interface MovieFinder {
    List findAll();
}

Now all of this is very well decoupled, but at some point I have to come up with a concrete class to actually come up with the movies. In this case I put the code for this in the constructor of my lister class.

现在所有的这些都非常好地解耦了,但是在某个时候,我必须提供一个具体的类来实际生成电影。在这种情况下,我将这段代码放在我的列表类的构造函数中。

class MovieLister...

  private MovieFinder finder;
  public MovieLister() {
    finder = new ColonDelimitedMovieFinder("movies1.txt");
  }

The name of the implementation class comes from the fact that I'm getting my list from a colon delimited text file. I'll spare you the details, after all the point is just that there's some implementation.

实现类的名称源自于我从一个以冒号分隔的文本文件中获取列表的事实。我将省略细节,毕竟重点只是有一些实现。

Now if I'm using this class for just myself, this is all fine and dandy. But what happens when my friends are overwhelmed by a desire for this wonderful functionality and would like a copy of my program? If they also store their movie listings in a colon delimited text file called "movies1.txt" then everything is wonderful. If they have a different name for their movies file, then it's easy to put the name of the file in a properties file. But what if they have a completely different form of storing their movie listing: a SQL database, an XML file, a web service, or just another format of text file? In this case we need a different class to grab that data. Now because I've defined a MovieFinder interface, this won't alter my moviesDirectedBy method. But I still need to have some way to get an instance of the right finder implementation into place.

现在,如果我只是为自己使用这个类,这一切都很好。但是当我的朋友被这个功能的美妙所深深吸引,并且想要拷贝我的程序时怎么办呢?如果他们也将他们的电影清单存储在一个以冒号分隔的文本文件中,名为"movies1.txt",那么一切都很好。如果他们对电影文件有不同的命名,那么在一个属性文件中很容易就可以放置文件名。但是,如果他们完全不同的形式来存储他们的电影清单:一个SQL数据库,一个XML文件,一个Web服务,或者只是另一种格式的文本文件呢?在这种情况下,我们需要一个不同的类来获取这些数据。现在因为我定义了一个MovieFinder接口,这不会改变我的moviesDirectedBy方法。但是我仍然需要找到一个正确的finder实现的方法。

Figure 1: The dependencies using a simple creation in the lister class

Figure 1 shows the dependencies for this situation. The MovieLister class is dependent on both the MovieFinder interface and upon the implementation. We would prefer it if it were only dependent on the interface, but then how do we make an instance to work with?

图1显示了这种情况的依赖关系。MovieLister类既依赖于MovieFinder接口,也依赖于具体的实现。我们希望它只依赖于接口,但是那么我们如何创建一个实例来使用呢?

In my book P of EAA, we described this situation as a Plugin. The implementation class for the finder isn't linked into the program at compile time, since I don't know what my friends are going to use. Instead we want my lister to work with any implementation, and for that implementation to be plugged in at some later point, out of my hands. The problem is how can I make that link so that my lister class is ignorant of the implementation class, but can still talk to an instance to do its work.

在我撰写的《企业级应用架构模式》一书中,我们将这种情况描述为插件。找寻者的实现类在编译时没有与程序进行链接,因为我不知道我的朋友们会使用什么。相反,我们希望我的列表能够与任何实现一起工作,并且该实现可以在稍后的某个时点被插入,超出了我的控制范围。问题在于,我如何建立这个链接,使得我的列表类对于实现类一无所知,但仍然能够与实例进行通信并完成工作呢?

Expanding this into a real system, we might have dozens of such services and components. In each case we can abstract our use of these components by talking to them through an interface (and using an adapter if the component isn't designed with an interface in mind). But if we wish to deploy this system in different ways, we need to use plugins to handle the interaction with these services so we can use different implementations in different deployments.

将这扩展为一个实际的系统,我们可能会有数十个这样的服务和组件。在每种情况下,我们可以通过接口与这些组件进行交互(如果组件没有考虑接口设计,我们可以使用适配器)。但是,如果我们希望以不同的方式部署此系统,我们需要使用插件来处理与这些服务的交互,以便在不同的部署中使用不同的实现。

So the core problem is how do we assemble these plugins into an application? This is one of the main problems that this new breed of lightweight containers face, and universally they all do it using Inversion of Control.

因此,核心问题是如何将这些插件组装成一个应用程序?这是这种新型轻量级容器所面临的主要问题之一,而所有的容器都是通过控制反转来解决这个问题的。

3. Techniques/Tips 分享一个小技巧

重构是小步快走,你在重构时应该能够随时停下来而不影响你的工作,如果重构会导致你工作阻塞,那么这就不是重构,而应该是重写,你永远都应该执行小步快走式的重构而不应该是重写。

4. Share 分享一个观点

最近几周都在阅读关于代码重构的文章以及书籍,在编写代码时,不经意间就会出现坏味道,我们要对代码的坏味道有敏锐的洞察力,正确发现即解决,这样代码腐朽的速度才会减慢,项目才能延长生命周期。