ARTS 打卡第七周(2023.9.25~2023.10.1)

112 阅读9分钟

1. Algorithm 一道算法题

本周的算法题为最小栈 本题比较有意思的是使用差值法求解最小值

2. Review 阅读一篇英文文章

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

Using a Service Locator

The key benefit of a Dependency Injector is that it removes the dependency that the MovieLister class has on the concrete MovieFinder implementation. This allows me to give listers to friends and for them to plug in a suitable implementation for their own environment. Injection isn't the only way to break this dependency, another is to use a service locator. Dependency Injector 的关键优势是它消除了 MovieLister 类对具体 MovieFinder 实现的依赖。这使得我可以将 listers 提供给朋友们,让他们可以为自己的环境插入适当的实现。注入不是打破这种依赖的唯一方式,另一种方式是使用 service locator。

The basic idea behind a service locator is to have an object that knows how to get hold of all of the services that an application might need. So a service locator for this application would have a method that returns a movie finder when one is needed. Of course this just shifts the burden a tad, we still have to get the locator into the lister, resulting in the dependencies of Figure 3

service loacator 的基本思想是拥有一个对象,它知道如何获取应用程序可能需要的所有服务。因此,对于这个应用程序来说,一个 service loacator 将拥有一个在需要时返回电影查找器的方法。当然,这只是将负担略微转移,我们仍然需要将定位器传递给列表器,从而导致图3的依赖关系。

Figure 3: The dependencies for a Service Locator

In this case I'll use the ServiceLocator as a singleton Registry. The lister can then use that to get the finder when it's instantiated.

在这种情况下,我将使用 ServiceLocator 作为一个单例 Registry,然后,lister 可以在实例化时使用该 ServiceLocator 来获取 finder。

class MovieLister...

  MovieFinder finder = ServiceLocator.movieFinder();

class ServiceLocator...

  public static MovieFinder movieFinder() {
      return soleInstance.movieFinder;
  }
  private static ServiceLocator soleInstance;
  private MovieFinder movieFinder;

As with the injection approach, we have to configure the service locator. Here I'm doing it in code, but it's not hard to use a mechanism that would read the appropriate data from a configuration file.

与注入方法一样,我们需要对服务定位器进行配置。在这里,我使用的是代码配置的方式,但也可以使用一个机制,从配置文件中读取相应的数据。

class Tester...

  private void configure() {
      ServiceLocator.load(new ServiceLocator(new ColonMovieFinder("movies1.txt")));
  }

class ServiceLocator...

  public static void load(ServiceLocator arg) {
      soleInstance = arg;
  }

  public ServiceLocator(MovieFinder movieFinder) {
      this.movieFinder = movieFinder;
  }

Here's the test code. 下面是测试代码

class Tester...

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

I've often heard the complaint that these kinds of service locators are a bad thing because they aren't testable because you can't substitute implementations for them. Certainly you can design them badly to get into this kind of trouble, but you don't have to. In this case the service locator instance is just a simple data holder. I can easily create the locator with test implementations of my services.

我经常听到人们抱怨这种类型的服务定位器不好的原因是它们无法进行测试,因为无法替换实现。当然,如果设计不好,可能会遇到这样的问题,但并非必然如此。在这种情况下,服务定位器实例只是一个简单的数据持有者。我可以轻松地使用测试实现来创建定位器。

For a more sophisticated locator I can subclass service locator and pass that subclass into the registry's class variable. I can change the static methods to call a method on the instance rather than accessing instance variables directly. I can provide thread–specific locators by using thread–specific storage. All of this can be done without changing clients of service locator.

对于更复杂的定位器,我可以创建一个子类来继承服务定位器,并将该子类传递给注册表的类变量。我可以修改静态方法,使其调用实例的方法而不是直接访问实例变量。我可以通过使用线程特定的存储来提供特定线程的定位器。所有这些都可以在不修改服务定位器的客户端的情况下完成。

A way to think of this is that service locator is a registry not a singleton. A singleton provides a simple way of implementing a registry, but that implementation decision is easily changed.

一种思考方式是,服务定位器是一个注册表而不是一个单例。单例提供了实现注册表的一种简单方式,但这种实现决定很容易改变。

Using a Segregated Interface for the Locator

One of the issues with the simple approach above, is that the MovieLister is dependent on the full service locator class, even though it only uses one service. We can reduce this by using a role interface. That way, instead of using the full service locator interface, the lister can declare just the bit of interface it needs.

上述简单方法的一个问题是,MovieLister 依赖于完整的服务定位器类,即使它只使用一个服务。我们可以通过使用“角色接口(role interface)”来减少这种依赖。这样,而不是使用完整的服务定位器接口,列表器可以声明它所需要的接口部分。

In this situation the provider of the lister would also provide a locator interface which it needs to get hold of the finder.

在这种情况下,列表器的提供者还会提供一个定位器接口,它用于获取查找器。

public interface MovieFinderLocator {
    public MovieFinder movieFinder();

The locator then needs to implement this interface to provide access to a finder.

然后,定位器需要实现此接口以提供对查找器的访问。

MovieFinderLocator locator = ServiceLocator.locator();
MovieFinder finder = locator.movieFinder();
public static ServiceLocator locator() {
     return soleInstance;
 }
 public MovieFinder movieFinder() {
     return movieFinder;
 }
 private static ServiceLocator soleInstance;
 private MovieFinder movieFinder;

You'll notice that since we want to use an interface, we can't just access the services through static methods any more. We have to use the class to get a locator instance and then use that to get what we need.

你会注意到,由于我们想使用一个接口,我们不能再通过静态方法直接访问服务。我们必须使用类来获取一个定位器实例,然后使用它来获取我们需要的内容。

A Dynamic Service Locator

The above example was static, in that the service locator class has methods for each of the services that you need. This isn't the only way of doing it, you can also make a dynamic service locator that allows you to stash any service you need into it and make your choices at runtime.

上述示例是静态的,在这种情况下,服务定位器类具有您所需的每个服务的方法。这并不是唯一的方法,您还可以创建一个动态的服务定位器,允许您将任何需要的服务保存其中,并在运行时进行选择。

In this case, the service locator uses a map instead of fields for each of the services, and provides generic methods to get and load services.

在这种情况下,服务定位器使用一个映射而不是每个服务的字段,并提供通用的方法来获取和加载服务。

class ServiceLocator...

  private static ServiceLocator soleInstance;
  public static void load(ServiceLocator arg) {
      soleInstance = arg;
  }
  private Map services = new HashMap();
  public static Object getService(String key){
      return soleInstance.services.get(key);
  }
  public void loadService (String key, Object service) {
      services.put(key, service);
  }

Configuring involves loading a service with an appropriate key.

配置涉及使用适当的键加载服务。

class Tester...

  private void configure() {
      ServiceLocator locator = new ServiceLocator();
      locator.loadService("MovieFinder", new ColonMovieFinder("movies1.txt"));
      ServiceLocator.load(locator);
  }

I use the service by using the same key string.

我通过使用相同的键字符串来使用该服务。

class MovieLister...

  MovieFinder finder = (MovieFinder) ServiceLocator.getService("MovieFinder");

On the whole I dislike this approach. Although it's certainly flexible, it's not very explicit. The only way I can find out how to reach a service is through textual keys. I prefer explicit methods because it's easier to find where they are by looking at the interface definitions.

总的来说,我不喜欢这种方法。虽然它确实很灵活,但不够明确。我只能通过文本键来找到访问服务的方式。我更喜欢显式的方法,因为通过查看接口定义更容易找到它们所在的位置。

Using both a locator and injection with Avalon

Dependency injection and a service locator aren't necessarily mutually exclusive concepts. A good example of using both together is the Avalon framework. Avalon uses a service locator, but uses injection to tell components where to find the locator.

依赖注入和服务定位器并不一定是互斥的概念。一个很好的例子是使用这两种方法的Avalon框架。Avalon使用服务定位器,但使用注入来告诉组件如何找到定位器。

Berin Loritsch sent me this simple version of my running example using Avalon.

Berin Loritsch给我发送了使用Avalon的我的示例的简化版本。

public class MyMovieLister implements MovieLister, Serviceable {
    private MovieFinder finder;

    public void service( ServiceManager manager ) throws ServiceException {
        finder = (MovieFinder)manager.lookup("finder");
    } 
      

The service method is an example of interface injection, allowing the container to inject a service manager into MyMovieLister. The service manager is an example of a service locator. In this example the lister doesn't store the manager in a field, instead it immediately uses it to lookup the finder, which it does store.

服务方法是接口注入的一个例子,允许容器将服务管理器注入到MyMovieLister中。服务管理器是服务定位器的一个例子。在这个例子中,列表器不会将管理器存储在字段中,而是立即使用它来查找查找器,并将其存储。

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

偏向锁在 jdk 15 中已经被取消了,取消的原因我觉得是性价比极低造成的,偏向锁太复杂了,但是收益却很小,食之无味、弃之可惜,被取消也在意料之中

4. Share 分享一个观点

保持理性的重要性: 最近工作中遇到一个问题,在给商家生成数据时发现某些关联数据缺失,这些关联数据关系到商家商品库存值,如果出错会导致商家线上售卖出现客诉,因此需要及时解决该问题,在解决问题时我们发现了问题的表面原因,可以解决该问题,但是问题是如果使用该方法,会付出大量时间修复该问题,而且过程极易出错,别的同事准备立刻按照该方法解决问题,但是我整体回顾了一下,觉得应该找到问题的根源一次性解决问题,同事似乎并不愿意,因为影响商家的时间可能会被拉长,但是我坚持了自己的观点,并且在心里给自己了一个限制时间,10分钟,如果找不到根因,就按照现有计划解决问题,很幸运的是不到5分钟,就发现了根因所在,并且后续修复该问题,总共耗时不到10分钟,如果按照最开始的解决方案,可能我们需要一天时间解决问题,并且还要防备引发额外问题。

这个问题给我的启发是越是紧急的情况下,越要保持冷静,保持冷静的思考