ARTS 打卡第六周(2023.9.18~2023.9.24)

148 阅读9分钟

1. Algorithm 一道算法题

本周算法题是矩阵置零

2. Review 读一篇英文文章

本周继续阅读 MF 的# Inversion of Control Containers and the Dependency Injection pattern

Forms of Dependency Injection

The basic idea of the Dependency Injection is to have a separate object, an assembler, that populates a field in the lister class with an appropriate implementation for the finder interface, resulting in a dependency diagram along the lines of Figure 2

依赖注入的基本思想是使用一个独立的对象,即装配器,将一个列表类中的字段与适当的实现为查找器接口的对象进行填充,从而形成一个类似以下的依赖关系图。

Figure 2: The dependencies for a Dependency Injector

There are three main styles of dependency injection. The names I'm using for them are Constructor Injection, Setter Injection, and Interface Injection. If you read about this stuff in the current discussions about Inversion of Control you'll hear these referred to as type 1 IoC (interface injection), type 2 IoC (setter injection) and type 3 IoC (constructor injection). I find numeric names rather hard to remember, which is why I've used the names I have here.

依赖注入有三种主要的风格。我所使用的名称分别是构造器注入、setter注入和接口注入。如果你在关于控制反转的当前讨论中阅读相关内容,你会听到它们被称为类型1 IoC(接口注入)、类型2 IoC(setter注入)和类型3 IoC(构造器注入)。我发现数字名称很难记住,这就是为什么我在这里使用了我所使用的名称。

Constructor Injection with PicoContainer

I'll start with showing how this injection is done using a lightweight container called PicoContainer. I'm starting here primarily because several of my colleagues at Thoughtworks are very active in the development of PicoContainer (yes, it's a sort of corporate nepotism.)

我将首先介绍如何使用一个轻量级容器叫做PicoContainer来实现这种依赖注入。我选择从这里开始主要是因为我在Thoughtworks这个公司的几位同事在PicoContainer的开发方面非常活跃(是的,这有点像企业中的亲情主义,裙带关系)。

PicoContainer uses a constructor to decide how to inject a finder implementation into the lister class. For this to work, the movie lister class needs to declare a constructor that includes everything it needs injected.

PicoContainer使用构造器来决定如何将一个查找器实现注入到列表类中。为了使其正常工作,电影列表类需要声明一个包含所需注入对象的构造器。

class MovieLister...

  public MovieLister(MovieFinder finder) {
      this.finder = finder;       
  }

The finder itself will also be managed by the pico container, and as such will have the filename of the text file injected into it by the container.

查找器本身也将由Pico容器管理,并且会由容器将文本文件的文件名注入到其中。

class ColonMovieFinder...

  public ColonMovieFinder(String filename) {
      this.filename = filename;
  }

The pico container then needs to be told which implementation class to associate with each interface, and which string to inject into the finder.

然后需要告诉Pico容器将哪个实现类与每个接口关联,并将哪个字符串注入到查找器中。

private MutablePicoContainer configureContainer() {
    MutablePicoContainer pico = new DefaultPicoContainer();
    Parameter[] finderParams =  {new ConstantParameter("movies1.txt")};
    pico.registerComponentImplementation(MovieFinder.class, ColonMovieFinder.class, finderParams);
    pico.registerComponentImplementation(MovieLister.class);
    return pico;
}

This configuration code is typically set up in a different class. For our example, each friend who uses my lister might write the appropriate configuration code in some setup class of their own. Of course it's common to hold this kind of configuration information in separate config files. You can write a class to read a config file and set up the container appropriately. Although PicoContainer doesn't contain this functionality itself, there is a closely related project called NanoContainer that provides the appropriate wrappers to allow you to have XML configuration files. Such a nano container will parse the XML and then configure an underlying pico container. The philosophy of the project is to separate the config file format from the underlying mechanism.

这种配置代码通常在另一个类中设置。对于我们的示例,每位使用我的列表器的朋友都可以在自己的设置类中编写适当的配置代码。当然,通常将这种配置信息保存在单独的配置文件中。您可以编写一个类来读取配置文件并适当地设置容器。尽管PicoContainer本身不包含这个功能,但有一个与之紧密相关的项目叫做NanoContainer,它提供了适当的包装器,使您可以使用XML配置文件。这样的Nano容器将解析XML,然后配置底层的Pico容器。该项目的理念是将配置文件格式与底层机制分开。

To use the container you write code something like this.

为了使用容器,您需要编写类似以下的代码。

public void testWithPico() {
    MutablePicoContainer pico = configureContainer();
    MovieLister lister = (MovieLister) pico.getComponentInstance(MovieLister.class);
    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
    assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}

Although in this example I've used constructor injection, PicoContainer also supports setter injection, although its developers do prefer constructor injection.

尽管在这个例子中我使用了构造器注入,但是PicoContainer也支持属性注入,尽管它的开发者更喜欢构造器注入。

Setter Injection with Spring

The Spring framework is a wide ranging framework for enterprise Java development. It includes abstraction layers for transactions, persistence frameworks, web application development and JDBC. Like PicoContainer it supports both constructor and setter injection, but its developers tend to prefer setter injection - which makes it an appropriate choice for this example.

Spring框架是一个广泛应用于企业级Java开发的框架。它包括事务、持久化框架、Web应用程序开发和JDBC的抽象层。与PicoContainer一样,它支持构造器注入和属性注入,但是它的开发者更倾向于使用属性注入 - 这使得它成为这个示例的合适选择。

To get my movie lister to accept the injection I define a setting method for that service

为了使我的电影列表接受注入,我为该服务定义了一个设置方法。

class MovieLister...

  private MovieFinder finder;
public void setFinder(MovieFinder finder) {
  this.finder = finder;
}

Similarly I define a setter for the filename.

同样地,我为文件名定义了一个设置方法。

class ColonMovieFinder...

  public void setFilename(String filename) {
      this.filename = filename;
  }

The third step is to set up the configuration for the files. Spring supports configuration through XML files and also through code, but XML is the expected way to do it.

第三个步骤是为文件设置配置。Spring支持通过XML文件和代码进行配置,但是预期的方式是使用XML进行配置。

<beans>
    <bean id="MovieLister" class="spring.MovieLister">
        <property name="finder">
            <ref local="MovieFinder"/>
        </property>
    </bean>
    <bean id="MovieFinder" class="spring.ColonMovieFinder">
        <property name="filename">
            <value>movies1.txt</value>
        </property>
    </bean>
</beans>

The test then looks like this.

测试代码如下所示。

public void testWithSpring() throws Exception {
    ApplicationContext ctx = new FileSystemXmlApplicationContext("spring.xml");
    MovieLister lister = (MovieLister) ctx.getBean("MovieLister");
    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
    assertEquals("Once Upon a Time in the West", movies[0].getTitle());
}

Interface Injection

The third injection technique is to define and use interfaces for the injection. Avalon is an example of a framework that uses this technique in places. I'll talk a bit more about that later, but in this case I'm going to use it with some simple sample code.

第三种注入技术是为注入定义和使用接口。Avalon是一个使用这种技术的框架的例子。我稍后会再详细谈一些相关内容,但在这个例子中,我将在一些简单的示例代码中使用它。

With this technique I begin by defining an interface that I'll use to perform the injection through. Here's the interface for injecting a movie finder into an object.

使用这种技术,我首先定义一个接口,通过这个接口进行注入。以下是将电影查找器注入到对象中的接口定义。

public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}

This interface would be defined by whoever provides the MovieFinder interface. It needs to be implemented by any class that wants to use a finder, such as the lister.

这个接口将由提供MovieFinder接口的人来定义。任何想要使用查找器的类,比如Lister,都需要实现这个接口。

class MovieLister implements InjectFinder

  public void injectFinder(MovieFinder finder) {
      this.finder = finder;
  }

I use a similar approach to inject the filename into the finder implementation.

我使用类似的方法将文件名注入到查找器的实现中。

public interface InjectFinderFilename {
    void injectFilename (String filename);
}

class ColonMovieFinder implements MovieFinder, InjectFinderFilename...

  public void injectFilename(String filename) {
      this.filename = filename;
  }

Then, as usual, I need some configuration code to wire up the implementations. For simplicity's sake I'll do it in code.

然后,像往常一样,我需要一些配置代码来连接各个实现。为了简单起见,我将使用代码来完成。

class Tester...

  private Container container;

   private void configureContainer() {
     container = new Container();
     registerComponents();
     registerInjectors();
     container.start();
  }

This configuration has two stages, registering components through lookup keys is pretty similar to the other examples.

这个配置有两个阶段,通过查找键注册组件与其他示例非常相似。

class Tester...

  private void registerComponents() {
    container.registerComponent("MovieLister", MovieLister.class);
    container.registerComponent("MovieFinder", ColonMovieFinder.class);
  }

A new step is to register the injectors that will inject the dependent components. Each injection interface needs some code to inject the dependent object. Here I do this by registering injector objects with the container. Each injector object implements the injector interface.

一个新的步骤是注册将注入依赖组件的注入器。每个注入接口都需要一些代码来注入依赖对象。在这里,我通过向容器注册注入器对象来完成。每个注入器对象都实现了注入器接口。

class Tester...

  private void registerInjectors() {
    container.registerInjector(InjectFinder.class, container.lookup("MovieFinder"));
    container.registerInjector(InjectFinderFilename.class, new FinderFilenameInjector());
  }
public interface Injector {
  public void inject(Object target);

}

When the dependent is a class written for this container, it makes sense for the component to implement the injector interface itself, as I do here with the movie finder. For generic classes, such as the string, I use an inner class within the configuration code.

当依赖项是为该容器编写的类时,让组件自己实现注入器接口是有意义的,就像我在这里用电影查找器做的一样。对于泛型类,例如字符串,我在配置代码中使用一个内部类。

class ColonMovieFinder implements Injector...

  public void inject(Object target) {
    ((InjectFinder) target).injectFinder(this);        
  }

class Tester...

  public static class FinderFilenameInjector implements Injector {
    public void inject(Object target) {
      ((InjectFinderFilename)target).injectFilename("movies1.txt");      
    }
    }

The tests then use the container. 测试案例会使用容器。

class Tester…

  public void testIface() {
    configureContainer();
    MovieLister lister = (MovieLister)container.lookup("MovieLister");
    Movie[] movies = lister.moviesDirectedBy("Sergio Leone");
    assertEquals("Once Upon a Time in the West", movies[0].getTitle());
  }

The container uses the declared injection interfaces to figure out the dependencies and the injectors to inject the correct dependents. (The specific container implementation I did here isn't important to the technique, and I won't show it because you'd only laugh.)

容器使用声明的注入接口来确定依赖关系,并使用注入器来注入正确的依赖项。(我在这里做的具体容器实现对技术并不重要,也不会展示出来,因为你只会笑。)

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

TDD 可以提升你重构的信心,有了单元测试,重构才有意义,也才能保证代码的稳定性

4. Share 分享一个观点

本周在学习 Bob 大叔的代码整洁之道——程序员的职业素养,其中介绍了专业人士(专业程序员)应当具备的素养,对我感触较深的是TDD(测试驱动开发),很多次都想进行尝试,但还没找到突破口,如果大家有什么好的TDD教程,希望不吝分享