锈湖根源成就
One of the means to achieve decoupling software modules is following Dependency Inversion Principle (DIP). In this post I want to show how to implement the Strategy Design Pattern in order to achieve Dependency Inversion. The basic idea behind Strategy is given that an algorithm, solving a particular problem, we define only skeleton of the algorithm at abstract level, but delegate specific algorithm’s implementation or some of its parts to another class or method. A client using algorithm may choose a specific implementation, while the general algorithm workflow remains the same. In other words, the abstract specification of the class does not depend on the specific implementation of the derived class, but specific implementation must adhere to the abstract specification. This is why we call it Dependency Inversion.
遵循依赖反转原理(DIP)是实现软件模块解耦的一种方法。 在这篇文章中,我想展示如何实现策略设计模式以实现依赖倒置。 策略背后的基本思想是,一种算法可以解决一个特定的问题,我们仅在抽象级别定义该算法的框架,但是将特定算法的实现或部分实现委托给另一个类或方法。 使用算法的客户端可以选择特定的实现,而常规算法的工作流程保持不变。 换句话说,类的抽象规范不依赖于派生类的特定实现,但是特定的实现必须遵循抽象规范。 这就是为什么我们称其为依赖倒置。
Let’s start by stating a simple problem. Imagine we have a key-value set (e.g. from database) and want to serialise it in different formats, e.g., in plain text as following
让我们从一个简单的问题开始。 假设我们有一个键值集(例如,来自数据库),并希望以不同的格式(例如,以纯文本格式)进行序列化
name john
age 25
occupation developer
or in json
format like this
或像这样的json
格式
{
"name": "john",
"age": 25,
"occupation": "developer"
}
A simplest approach would be implement separate classes for each format. But we don’t want to repeat code for invariant part of the algorithm for each format, especially imagine we if we had 20 or 30 different formats. We don’t either want to change the entire class (or our code base) just because we add a new output format.
最简单的方法是为每种格式实现单独的类。 但是我们不想为每种格式的算法不变部分重复代码,特别是想象我们有20或30种不同的格式。 我们都不希望仅仅因为我们添加了新的输出格式而更改了整个类(或我们的代码库)。
Alright. Let’s create a new project cargo new design_patters
and create a new folder src/strategy.
Inside src/strategy
create a new file mod.rs
.
好的。 让我们创建一个新的项目cargo new design_patters
并创建一个新的文件夹src/strategy.
在src/strategy
内部创建一个新文件mod.rs
First of all we create a struct Report
holding two vectors: keys and values. For simplicity, keys are strings and values are integers.
首先,我们创建一个结构Report
两个向量:键和值。 为简单起见,键是字符串,值是整数。
Next we need to define interfaces for creating a new report and generating key-value output
接下来,我们需要定义用于创建新报告和生成键值输出的接口
At this point, before we implement new,
I’d like to mention a well-known technique called Dependency Injection. In our implementation we use Dependency Injection when we create a new report. We provide a strategy which will be used when we call generate
on Report. However, Report
struct needs somehow to store the strategy so that it could later use it when client invokes generate
. So, how do we deal with this problem?
在这一点上,在我们实现new,
之前new,
我想提到一种称为Dependency Injection的众所周知的技术。 在我们的实现中,当我们创建新报告时,我们将使用依赖注入。 我们提供一种策略,当我们在Report上调用generate
时将使用该策略。 但是, Report
结构需要某种方式存储策略,以便稍后在客户端调用generate
时可以使用它。 那么,我们如何处理这个问题呢?
Our approach is provide Report
, when we instantiate it, with another object (instance of another struct) which is a strategy implementing some trait. In other words, we specify some rules at abstract (high) which should be followed by strategy objects.
我们的方法是在实例化时为Report
提供另一个对象(另一个结构的实例),该对象是一种实现某些特征的策略。 换句话说,我们在抽象(高级)处指定了一些规则,策略对象应遵循这些规则。
We introduce a new trait Generator
which has a single method run
. This method expects a reference to a Report
object. We also add member to Report
struct, generator
, of type Box<dyn Generator>.
In simple words Box<dyn Generator>
means any type inside a Box must implement the Generator
trait. Now, let’s implement generate
and new
.
我们引入了一个新的特征Generator
,它可以run
一个方法。 该方法需要引用Report
对象。 我们还向Box<dyn Generator>.
类型的Report
结构generator
添加成员Box<dyn Generator>.
简而言之Box<dyn Generator>
表示Box内的任何类型都必须实现Generator
特征。 现在,让我们实现generate
和new
。
When we create a new Report
we provide a strategy object which must implement trait Generator
invoked upon in Report
’s generate
method. This is where dependency injection occurs. In summary, when we create a Report
we must provide key, values and a strategy object, and then call generate
upon the report to get key value output. The following snippet show how to create and use Report.
当我们创建一个新的Report
我们提供了一个策略对象,该对象必须实现在Report
的generate
方法中调用的特征Generator
。 这是依赖注入发生的地方。 总而言之,当我们创建Report
我们必须提供键,值和策略对象,然后在报表上调用generate
以获得键值输出。 以下代码段显示了如何创建和使用Report.
So, I think everything is clear except Text{}
inside Box::new
. Text{}
is our strategy object implemented separately. Report
object does not care about how Text
generates key-values. Report
only provides Text
with raw data by passing self
: self.generator.run(&self).
因此,我认为除了Box::new
内的Text{}
外,其他所有内容都很清晰。 Text{}
是我们单独实现的策略对象。 Report
对象不关心Text
如何生成键值。 Report
仅通过传递self
: self.generator.run(&self).
为Text
提供原始数据self.generator.run(&self).
Now that we have high level specifications defined, let’s implement our first strategy object. Create a new file src/strategy/text.rs
with the following code
现在我们已经定义了高级规范,让我们实现第一个策略对象。 使用以下代码创建一个新文件src/strategy/text.rs
This is Dependency Inversion in action. Report
struct does not depend on strategy object implementation, but strategy objects, as well as Report
object itself, adhere to a high level abstraction. In particular, any strategy object must implement Generator trait and provide the run
interface and any client using the Report
must provide a strategy object, apart from data, and call generate
to get output. That’s all about high level abstraction.
这是实际的依赖倒置。 Report
结构不依赖于策略对象的实现,但是策略对象以及Report
对象本身都遵循高级抽象。 特别是,任何策略对象都必须实现Generator特征并提供run
接口,并且使用Report
任何客户端都必须提供除数据之外的策略对象,并调用generate
来获取输出。 这就是高层抽象。
Let’s implement another strategy for generating in json
format.
让我们实现另一种以json
格式生成的策略。
Create a new file src/strategy/json.rs
with the following code
使用以下代码创建一个新文件src/strategy/json.rs
and use both strategy objects
并同时使用两个策略对象
Full code can be found here.
完整的代码可以在这里找到。
Thank you for reading :-)
感谢您的阅读:-)
翻译自: https://medium.com/swlh/strategy-design-pattern-in-rust-5f5486cd294c
锈湖根源成就