ARTS 打卡第八周(2023.10.2~2023.10.8)

83 阅读14分钟

1. Algorithm 一道算法题

本周的算法题为:基本计算器

本题给我的最大收获是将问题转换为括号展开,最后进行计算

2. Review 阅读一篇英文文章

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

Deciding which option to use

So far I've concentrated on explaining how I see these patterns and their variations. Now I can start talking about their pros and cons to help figure out which ones to use and when.

到目前为止,我已经集中讲解了我对这些模式及其变化的看法。现在我可以开始谈论它们的利弊,以帮助确定何时使用哪种模式。

Service Locator vs Dependency Injection

The fundamental choice is between Service Locator and Dependency Injection. The first point is that both implementations provide the fundamental decoupling that's missing in the naive example - in both cases application code is independent of the concrete implementation of the service interface. The important difference between the two patterns is about how that implementation is provided to the application class. With service locator the application class asks for it explicitly by a message to the locator. With injection there is no explicit request, the service appears in the application class - hence the inversion of control.

基本选择是在服务定位器(Service Locator)和依赖注入(Dependency Injection)之间进行。首先要指出的是,这两种实现都提供了原始示例中缺失的基本解耦 - 在这两种情况下,应用程序代码独立于服务接口的具体实现。这两种模式之间的重要区别在于如何将该实现提供给应用程序类。在服务定位器中,应用程序类通过向定位器发送消息来显式地请求它。而在依赖注入中,则没有显式的请求,服务出现在应用程序类中 - 因此实现了控制的反转。

Inversion of control is a common feature of frameworks, but it's something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn't to say it's a bad thing, just that I think it needs to justify itself over the more straightforward alternative.

控制反转是框架的一个常见特征,但它是有代价的。它往往很难理解,并且在调试时容易导致问题。因此,总体而言,除非有必要,我更倾向于避免使用它。这并不是说它是一件坏事,只是我认为它需要相对于更直接的替代方案进行自我证明。

The key difference is that with a Service Locator every user of a service has a dependency to the locator. The locator can hide dependencies to other implementations, but you do need to see the locator. So the decision between locator and injector depends on whether that dependency is a problem.

关键区别在于,使用服务定位器时,每个服务的用户都对定位器存在依赖关系。定位器可以隐藏对其他实现的依赖,但你确实需要看到定位器。因此,对于选择定位器还是注入器,取决于该依赖关系是否成问题。

Using dependency injection can help make it easier to see what the component dependencies are. With dependency injector you can just look at the injection mechanism, such as the constructor, and see the dependencies. With the service locator you have to search the source code for calls to the locator. Modern IDEs with a find references feature make this easier, but it's still not as easy as looking at the constructor or setting methods.

使用依赖注入可以帮助更容易地了解组件的依赖关系。使用依赖注入器,您只需查看注入机制(例如构造函数)即可看到依赖关系。而使用服务定位器,则需要在源代码中搜索对定位器的调用。现代化的集成开发环境(IDE)具有查找引用功能,这使得查找变得更容易,但仍不如查看构造函数或设置方法那样直观。

A lot of this depends on the nature of the user of the service. If you are building an application with various classes that use a service, then a dependency from the application classes to the locator isn't a big deal. In my example of giving a Movie Lister to my friends, then using a service locator works quite well. All they need to do is to configure the locator to hook in the right service implementations, either through some configuration code or through a configuration file. In this kind of scenario I don't see the injector's inversion as providing anything compelling.

很多情况下这取决于服务的使用者的性质。如果您正在构建一个使用服务的多个类的应用程序,那么应用程序类从定位器到依赖关系并不是一件大事。在我将一个电影列表提供给朋友的示例中,使用服务定位器效果非常好。他们只需要配置定位器来连接正确的服务实现,可以通过某些配置代码或配置文件来实现。在这种情况下,我认为注入器的反转并没有提供任何令人信服的东西。

The difference comes if the lister is a component that I'm providing to an application that other people are writing. In this case I don't know much about the APIs of the service locators that my customers are going to use. Each customer might have their own incompatible service locators. I can get around some of this by using the segregated interface. Each customer can write an adapter that matches my interface to their locator, but in any case I still need to see the first locator to lookup my specific interface. And once the adapter appears then the simplicity of the direct connection to a locator is beginning to slip.

这种区别在于,如果列表器是我提供给其他人编写的应用程序的组件。在这种情况下,我对我的客户将使用的服务定位器的API了解不多。每个客户可能拥有自己的不兼容的服务定位器。我可以通过使用分离接口来解决其中的一部分问题。每个客户可以编写一个与我的接口匹配的适配器,但无论如何,我仍然需要了解第一个定位器来查找我的特定接口。而一旦出现适配器,直接连接到定位器的简洁性便开始减弱。

Since with an injector you don't have a dependency from a component to the injector, the component cannot obtain further services from the injector once it's been configured.

由于使用注入器时,组件没有对注入器的依赖关系,一旦组件被配置,它就无法从注入器获取进一步的服务。

A common reason people give for preferring dependency injection is that it makes testing easier. The point here is that to do testing, you need to easily replace real service implementations with stubs or mocks. However there is really no difference here between dependency injection and service locator: both are very amenable to stubbing. I suspect this observation comes from projects where people don't make the effort to ensure that their service locator can be easily substituted. This is where continual testing helps, if you can't easily stub services for testing, then this implies a serious problem with your design.

人们通常喜欢依赖注入的一个常见原因是它使测试更加容易。关键在于,在测试中,您需要能够轻松地使用存根或模拟替换真实的服务实现。然而,在依赖注入和服务定位器之间实际上没有区别:两者都非常适合产生存根。我怀疑这种观察是来自项目中人们没有努力确保其服务定位器易于被替换的情况。这就是持续测试的帮助之处,如果您无法轻松地为测试而存根服务,那么这意味着设计上存在严重问题。

Of course the testing problem is exacerbated by component environments that are very intrusive, such as Java's EJB framework. My view is that these kinds of frameworks should minimize their impact upon application code, and particularly should not do things that slow down the edit-execute cycle. Using plugins to substitute heavyweight components does a lot to help this process, which is vital for practices such as Test Driven Development.

当然,组件环境对测试的困扰在非常具有侵入性的环境下会更加严重,比如Java的EJB框架。我的观点是,这些框架应该尽量减少对应用程序代码的影响,尤其不应该做会减慢编辑-执行周期的事情。使用插件来替换重量级组件对这个过程有很大帮助,这对于诸如测试驱动开发这样的实践非常重要。

So the primary issue is for people who are writing code that expects to be used in applications outside of the control of the writer. In these cases even a minimal assumption about a Service Locator is a problem.

因此,主要问题是对于那些编写代码,期望在编写者无法控制的应用程序中使用的人来说。在这些情况下,对于服务定位器的任何最小假设都是一个问题。

Constructor versus Setter Injection

For service combination, you always have to have some convention in order to wire things together. The advantage of injection is primarily that it requires very simple conventions - at least for the constructor and setter injections. You don't have to do anything odd in your component and it's fairly straightforward for an injector to get everything configured.

对于服务的组合,您始终需要一些约定以将其连接在一起。依赖注入的优势主要在于它需要非常简单的约定-至少对于构造函数和setter注入而言。您不需要在组件中进行任何奇怪的操作,对于注入器来说,配置一切都相当直观。

Interface injection is more invasive since you have to write a lot of interfaces to get things all sorted out. For a small set of interfaces required by the container, such as in Avalon's approach, this isn't too bad. But it's a lot of work for assembling components and dependencies, which is why the current crop of lightweight containers go with setter and constructor injection.

接口注入更具侵入性,因为您需要编写大量接口来整理事务。对于容器所需的一小组接口(例如Avalon方法),这并不太糟糕。但是,对于组装组件和依赖关系来说,这需要大量工作,这就是为什么当前的轻量级容器使用setter和构造函数注入的原因。

The choice between setter and constructor injection is interesting as it mirrors a more general issue with object-oriented programming - should you fill fields in a constructor or with setters.

选择使用setter注入还是构造函数注入是有趣的,因为它反映了面向对象编程中一个更普遍的问题-在构造函数中填充字段还是使用setter方法。

My long running default with objects is as much as possible, to create valid objects at construction time. This advice goes back to Kent Beck's Smalltalk Best Practice Patterns: Constructor Method and Constructor Parameter Method. Constructors with parameters give you a clear statement of what it means to create a valid object in an obvious place. If there's more than one way to do it, create multiple constructors that show the different combinations.

在对象方面,我的默认策略是尽量在构造时创建有效的对象。这个建议可以追溯到肯特·贝克(Kent Beck)的《Smalltalk最佳实践模式》(Smalltalk Best Practice Patterns):构造方法和构造参数方法。带参数的构造函数能够清楚地说明在一个明显的位置创建有效对象的含义。如果有多种方法可以实现,可以创建多个构造函数来展示不同的组合方式。

Another advantage with constructor initialization is that it allows you to clearly hide any fields that are immutable by simply not providing a setter. I think this is important - if something shouldn't change then the lack of a setter communicates this very well. If you use setters for initialization, then this can become a pain. (Indeed in these situations I prefer to avoid the usual setting convention, I'd prefer a method like initFoo, to stress that it's something you should only do at birth.)

使用构造函数初始化的另一个优点是,它允许您通过不提供setter方法来明确隐藏任何不可变的字段。我认为这一点很重要-如果某个属性不应该改变,那么缺少setter方法可以很好地传达这一信息。如果您在初始化时使用setter方法,那么这可能会变得很麻烦。(实际上,在这些情况下,我更喜欢避免常规的设置约定,而是更喜欢像initFoo这样的方法,以强调这只是在对象创建时进行的操作。)

But with any situation there are exceptions. If you have a lot of constructor parameters things can look messy, particularly in languages without keyword parameters. It's true that a long constructor is often a sign of an over-busy object that should be split, but there are cases when that's what you need.

但是,在任何情况下都存在例外情况。如果有很多构造函数参数,代码可能看起来很乱,尤其是在没有关键字参数的语言中。长构造函数通常意味着对象功能过于繁杂,应该进行拆分,这是正确的。但也有一些情况确实需要这样的构造函数。

If you have multiple ways to construct a valid object, it can be hard to show this through constructors, since constructors can only vary on the number and type of parameters. This is when Factory Methods come into play, these can use a combination of private constructors and setters to implement their work. The problem with classic Factory Methods for components assembly is that they are usually seen as static methods, and you can't have those on interfaces. You can make a factory class, but then that just becomes another service instance. A factory service is often a good tactic, but you still have to instantiate the factory using one of the techniques here.

如果您有多种构造一个有效对象的方式,通过构造函数来展示这一点可能很困难,因为构造函数只能通过参数的数量和类型进行变化。这就是工厂方法发挥作用的时候,它们可以使用私有构造函数和setter方法的组合来实现工作。传统的组件组装工厂方法的问题在于它们通常被视为静态方法,而接口上无法定义静态方法。您可以创建一个工厂类,但那只会变成另一个服务实例。工厂服务通常是一个好的策略,但您仍然需要使用这里所介绍的技术之一来实例化工厂。

Constructors also suffer if you have simple parameters such as strings. With setter injection you can give each setter a name to indicate what the string is supposed to do. With constructors you are just relying on the position, which is harder to follow.

如果您的参数比较简单,比如字符串,构造函数也会遇到一些问题。使用setter注入,您可以为每个setter方法添加名称,以指示字符串的用途。而使用构造函数,您只能依赖位置来确定参数的含义,这更难以跟踪。

If you have multiple constructors and inheritance, then things can get particularly awkward. In order to initialize everything you have to provide constructors to forward to each superclass constructor, while also adding you own arguments. This can lead to an even bigger explosion of constructors.

如果您有多个构造函数和继承关系,情况可能会变得特别麻烦。为了初始化所有的东西,您必须提供构造函数来将参数传递给每个超类的构造函数,并添加自己的参数。这可能会导致更多的构造函数组合。

Despite the disadvantages my preference is to start with constructor injection, but be ready to switch to setter injection as soon as the problems I've outlined above start to become a problem.

尽管存在缺点,但我更倾向于从构造函数注入开始,但一旦上述问题开始成为问题,我随时准备切换到setter注入。

This issue has led to a lot of debate between the various teams who provide dependency injectors as part of their frameworks. However it seems that most people who build these frameworks have realized that it's important to support both mechanisms, even if there's a preference for one of them.

这个问题在为框架提供依赖注入器的各个团队之间引发了很多争议。然而,似乎大多数构建这些框架的人已经意识到支持两种机制是很重要的,即使对其中一种机制有偏好也是如此。

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

MySQL 中由于数据都是由 B+ 树进行组织的,因此在增删改时,如果 where 条件的列没有建索引,并且事务的隔离级别是 RR,那么由于 MySQL 引擎需要遍历主键索引,因此需要将整张表锁住,但是 RC 隔离级别则会在判断行不满足条件时,将锁进行解锁,最终只会锁住满足 where 条件的行。

4. Share 分享一个小观点

很多时候我们在做一个需求时,要从多方面进行思考为什么该需求值得做,一般需要考虑: 现状、痛点、目标、措施等,然后经过这些分析后在思考如何做,怎么做,这样你的需求方案才能被别人更简单的理解清楚