Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

当设计模式遇上 Kotlin #1780

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 59 additions & 59 deletions TODO/gang-of-four-patterns-in-kotlin.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
> * 原文地址:[Gang of Four Patterns in Kotlin](https://dev.to/lovis/gang-of-four-patterns-in-kotlin)
> * 原文作者:[Lovis](https://dev.to/lovis)
> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 译者:
> * 译者:[Boiler Yao](https://github.com/boileryao)
> * 校对者:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

校对者:wilsonandusa


Kotlin is getting more and more relevant. How would common design patterns implemented in Kotlin look like?
Kotlin 正在得到越来越广泛的应用。如果把常用的设计模式用 Kotlin 来实现会是什么样子呢?

Inspired by Mario Fusco's [Talk](https://www.youtube.com/watch?v=Rmer37g9AZM)/[Blog posts](https://www.voxxed.com/blog/2016/04/gang-four-patterns-functional-light-part-1/)/[Repository](https://github.com/mariofusco/from-gof-to-lambda) "From GoF to lambda", I decided to implement some of the most famous design patterns in computer science in Kotlin!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[I decided to implement some of the most famous design patterns in computer science in Kotlin!]=>[我决定用 Kotlin来实现一些计算机科学领域最著名的设计模式!]
可斟酌..

受到 Mario Fusco 的“从‘四人帮’到 lambda”(相关的[视频](https://www.youtube.com/watch?v=Rmer37g9AZM)、[博客](https://www.voxxed.com/blog/2016/04/gang-four-patterns-functional-light-part-1/)、[代码](https://github.com/mariofusco/from-gof-to-lambda))的启发,我决定动手实现一些计算机科学领域最著名的设计模式,用 Kotlin!(“四人帮”指 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides,四人在所著的《Design Patterns: Elements of Reusable Object-Oriented Software 》一书中介绍了 23 种设计模式,该书被誉为设计模式的经典之作。——译注)

The goal is not to simply *implement* the patterns, though. Since Kotlin supports object oriented programming and is interoperable with Java, I could just copy-auto-convert every java class in Mario's repository (doesn't matter whether it's the "traditional" or the "lambda" examples) and **it would still work!**
当然,我的目标不是简单的 **实现** 这些模式。因为 Kotlin 支持面向对象编程并且和 Java 是可互操作的,我可以从 Mario 的仓库直接复制粘贴每一个 Java 文件(先不管是“传统”的还是“lambada 风格”的),**它们将仍然可以正常工作**!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo: [lambada] => [lambda]

It's also important to note that these patterns where discovered to overcome the shortcomings of how imperative programming languages were designed in the 90s (C++ in particular). Many modern languages provide features to overcome these issues without writing extra code or falling back to patterns.
认识到这些模式的发明是为了弥补起源于上世纪九十年代的一些命令式编程语言(尤其是 C++)的不足是很重要的。很多现代编程语言提供了解决这些不足的特性,我们完全不需要再写多余的代码或者做刻意模仿设计模式这种事了。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[It's also important to note that these patterns]=>[需要特别说明一下,这些设计模式的发明]


That's why —just like Mario in the `g ∘ f` repository— I will try to find a simpler, easier or more idiomatic way to solve the same problem each pattern is solving.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[a simpler, easier or more idiomatic way]=>[一种更简单方便、更惯用的方式]

这就是为什么我像 Mario 那样,去寻找一种简单方便、惯用的方式来解决这些模式所要解决的问题。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g of f 地址(https://github.com/mariofusco/from-gof-to-lambda) 可以考虑加上

If you don't like reading explanations, you can directly jump to my [github repository](https://github.com/lmller/gof-in-kotlin)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[reading explanations]=>[看说明文字]

如果不想看下面这坨文字的话,你可以直接去 [这个 GitHub 仓库](https://github.com/lmller/gof-in-kotlin) 看代码。

---

As you might know, there are three types of (gof) patterns: **structural**, **creational** and **behavioral** patterns.
众所周知,根据“四人帮”的定义设计模式可以分为三种: **结构型**、**创建型 **和 **行为型**。
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image


First up are the structural patterns. This is tough, because structural patterns are about - structure! How could I implement a structure with a *different* structure? I can't. An exception is the **Decorator**. While it's technically just about structure, its usage is more about behavior or responsibilities.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[I can't]=>[做不到。]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[its usage is more about behavior or responsibilities]=>[但它的使用上,更多的和行为及职责有关。]

一开始,我们先来看结构型设计模式。这不是很好搞,因为结构型设计模式是关于结构的。怎样用一个 **不同** 的结构来实现另一个结构呢,臣妾做不到啊。不过, **装饰器模式 **是个例外。虽然在技术层面来说是结构型,但就使用来说,更像多个职责构成的行为型设计模式(装饰器模式,每个负责进行包装的类具有增加某一行为这一职责。——译注)。

### Structural Patterns
### 结构型设计模式

#### Decorator
#### 装饰器模式(Decorator

> Attach additional responsibilities to an object dynamically
> 动态地给对象添加行为(职责)

Let's say I want to decorate a class `Text` with some text effects:
假设我们想用一些特效(duang)来装饰 `Text` 这个类:

```
class Text(val text: String) {
fun draw() = print(text)
}
```

If you know the pattern, you also know that a set of classes has to be created in order to "decorate" (that is extending the behavior) the `Text` class.
如果了解这个模式的话,你应该知道我们需要创建一些类来“修饰”(即,拓展行 为) `Text` 类。

These extra classes can be avoided in Kotlin by using *extension functions*:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[These extra classes]=>[这些额外的类]

Kotlin 中,我们可以用 **函数拓展(extension functions)** 来避免创建这么一坨类:

```
fun Text.underline(decorated: Text.() -> Unit) {
Expand All @@ -54,7 +54,7 @@ fun Text.background(decorated: Text.() -> Unit) {
}
```

With these extension functions, I can now create a new `Text` and decorate its `draw` method without creating other classes:
有了这些拓展函数,我们现在可以实例化一个 `Text` 对象,并且在不创建其他类的情况下来修饰它的 `draw` 方法:

```
Text("Hello").run {
Expand All @@ -66,27 +66,27 @@ Text("Hello").run {
}
```

If you run this from the command line, you will see the text "_Hello_" with colored background (if your terminal supports ansi colors).
运行这段代码,你会看见带有彩色背景的“\_Hello\_”(如果终端支持 ansi 颜色的话)。

Compared to the original Decorator pattern, there is one drawback: I can not pass "pre-decorated" objects around, since there is no Decorator class anymore.
跟原本的装饰者相比,这里有一个不足:由于没有用来装饰的类了,所以我们不能使用“预装饰”过的对象了。

To solve this I can use functions again. Functions are first-class citizens in Kotlin, so I can pass those around instead.
可以再次使用函数来解决这个问题,函数是 Kotlin 中的“一等公民”。我们可以这样写:

```
fun preDecorated(decorated: Text.() -> Unit): Text.() -> Unit {
return { background { underline { decorated() } } }
}
```

### Creational
### 创建型设计模式

#### Builder
#### Builder 模式

> Separate the construction of a complex object from its representation so that the same construction process can create different representations
> 将复杂对象的构造与其表示分开,以便相同的构造过程可以创建不同形式的对象

The **Builder** pattern is really useful. I can avoid variable-heavy constructors and easily re-use predefined setups. Kotlin supports this pattern out of the box with the `apply` extension function.
**Builder** 模式很好用,可以避免臃肿的构造函数参数列表,还能方便地复用预先定义好的配置对象的代码。 Kotlin `apply` 扩展原生支持 Builder 模式。

Consider a class `Car`:
假设有一个 `Car` 类:

```
class Car() {
Expand All @@ -95,7 +95,7 @@ class Car() {
}
```

Instead of creating a separate `CarBuilder` for this class, I can now use the `apply` (`also` works as well) extension to initialize the car:
除了为这个类单独创建一个 `CarBuilder` ,我们可以使用 `apply``also` 也行)拓展来初始化一辆车:

```
Car().apply {
Expand All @@ -104,17 +104,17 @@ Car().apply {
}
```

Since functions can be stored in a variable, this initialization could also be stored in a variable. This way, I can have pre-configured **Builder**-functions, e.g. `val yellowCar: Car.() -> Unit = { color = "yellow" }`
由于函数可以赋值给一个变量,所以这个初始化过程也可以放在一个变量里。这样,我们就有了一个预先定义好的 **Builder** “函数”,比如 `val yellowCar: Car.() -> Unit = { color = "yellow" }`

#### Prototype
#### 原型模式(Prototype

> Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype
> 使用原型化的实例指定要创建的对象的种类,并通过复制此实例来创建特定的新对象

In Java, prototyping could theoretically be implemented using the `Cloneable` interface and `Object.clone()`. However, [`clone` is broken](http://www.artima.com/intv/bloch13.html), so we should avoid it.
Java 中,原型模式理论上可以用 `Cloneable` 接口和 `Object.clone()` 来实现。然而,[`clone` 有很大的不足](http://www.artima.com/intv/bloch13.html),所以我们应该避免使用它。

Kotlin fixes this with data classes.
Kotlin 用数据类(data classes)提供了解决方案。

When I use data classes, I get `equals`, `hashCode`, `toString` and `copy` for free. By using `copy`, it's possible to clone the whole object and optionally change some of the new object's properties.
当使用数据类的时候,我们将免费得到 `equals``hashCode``toString` `copy` 这几个函数。通过 `copy`,我们可以复制一整个对象并且修改所得到的新对象的一些属性。

```
data class EMail(var recipient: String, var subject: String?, var message: String?)
Expand All @@ -128,13 +128,13 @@ println("Email1 goes to " + mail.recipient + " with subject " + mail.subject)
println("Email2 goes to " + copy.recipient + " with subject " + copy.subject)
```

#### Singleton
#### 单例模式(Singleton

> Ensure a class only has one instance, and provide a global point of access to it
> 确保一个类只有一个实例,并提供这个实例的全局访问点

Although the **Singleton** is considered an anti-pattern these days, it has its usages (I won't discuss this topic here, just use it with caution).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[just use it with caution]=>[但需要谨慎地使用它。]

尽管近来 **单例模式** 被认为是“反设计模式的”,但是它也有自己独特的用处(本文不会讨论这个话题,只是战战克克克克的来用它)。

Creating a **Singleton** in Java needs a lot of ceremony, but Kotlin makes it easy with *`object` declarations*.
在 Java 中创建 **单例** 还是需要一番操作的,但是在 Kotlin 中只需要简单的使用 **`object` ** 声明就可以了。

```
object Dictionary {
Expand All @@ -148,23 +148,23 @@ object Dictionary {
}
```

Using the `object` keyword here will automatically create a class `Dictionary` and a single instance of it. The instance is created lazily, so it's not created until it is actually used.
这里使用的 `object` 关键词会自动创建出 `Dictionary` 这个类以及它的一个单例。这个单例以“懒汉模式”创建,用到它时才会进行创建。

The object is accessed like static functions in java:
单例的访问方式和 Java 的静态方法差不多:

```
val word = "kotlin"
Dictionary.addDefinition(word, "an awesome programming language created by JetBrains")
println(word + " is " + Dictionary.getDefinition(word))
```

### Behavioral
### 行为型设计模式

#### Template Method
#### 模板方法(Template Method

> Define the skeleton of an algorithm in an operation, deferring some steps to subclasses
> 在操作中定义算法(步骤)的骨架,将一些步骤委托给子类

This pattern makes use of class hierarchies as well. You define an `abstract` method and call it somewhere inside the base class. The implementation is handled by the subclasses.
这个设计模式同时用到了类的继承。定义一些 `抽象方法` 并且在基类调用这些方法。抽象方法由子类负责实现。

```
//java
Expand All @@ -178,9 +178,9 @@ public abstract class Task {
}
```

I could now derive a concrete `Task`, that actually does something in `work`.
现在从 `Task` 派生出一个在 `work` 方法中真正做了事情的具体类。

Similar to how the **Decorator** example uses extension functions, my **Template Method** approach uses a top-level function.
和 **装饰器模式** 使用函数拓展类似,这里的 **模板方法** 通过顶层函数实现。

```
//kotlin
Expand All @@ -197,13 +197,13 @@ execute {
}
```

As you can see, there is no need to have a class at all! One could argue, that this now resembles the **Strategy** pattern, which is probably correct. But then again, **Strategy** and **Template Method** are solving very similar problems (if not the same).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[As you can see, there is no need to have a class at all!]=>[可以看到,这里不需要写任何一个类!]

看,根本没有必要写一个类!有人可能会有疑问,这不是 **策略模式** 吗,这个疑问不无道理。从另一方面来看,**策略模式** **模板方法** 确实在解决很相似的问题(如果有什么不同)。

#### Strategy
#### 策略模式(Strategy

> Define a family of algorithms, encapsulate each one, and make them interchangeable
> 定义一系列算法,封装每个算法,并使它们可以互换

Let's say I have a `Customer` that pays a certain fee per month. This fee can be discounted. Instead of having a subclass of `Customer` for each discounting-*strategy*, I use the **Strategy** pattern.
有一些 `Customer` ,他们每个月都要付一笔特定的费用。对于某些特定的人,这笔费用可以打折。我们不去为每种打折 **策略** 都去写一个对应的 `Customer` 子类,而是采用 **策略模式**。

```
class Customer(val name: String, val fee: Double, val discount: (Double) -> Double) {
Expand All @@ -213,9 +213,9 @@ class Customer(val name: String, val fee: Double, val discount: (Double) -> Doub
}
```

Notice that I'm using a function `(Double) -> Double` instead of an interface for the strategy. To make this more domain-specific, I could also declare a type alias, without losing the flexibility of an higher-order-function: `typealias Discount = (Double) -> Double`.
注意这里没有使用接口,而是使用 `(Double) -> Double` (Double 到 Double)的函数来替代。为了使这个变换看上去有意义,我们可以声明一个类型别名,这样也不失高阶函数的灵活性: `typealias Discount = (Double) -> Double`.

Either way, I can define multiple strategies for calculating the discount.
无论哪种方式,我都可以定义多种 **策略** 来计算折扣。

```
val studentDiscount = { fee: Double -> fee/2 }
Expand All @@ -229,33 +229,33 @@ println("${student.name} pays %.2f per month".format(student.pricePerMonth()))
println("${regular.name} pays %.2f per month".format(regular.pricePerMonth()))
```

#### Iterator
#### 迭代器模式(Iterator

> Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
> 提供了一种在不暴露其底层表示的情况下顺序访问聚合对象内部元素的方法

Writing an **Iterator** is a rare task. Most of the time, it's easier and more convenient to wrap a `List` and implement the `Iterable` interface.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Writing an Iterator is a rare task]=>[很少需要自己写一个迭代器。]

很难遇到手搓一个 **迭代器** 的情况。大多数情况,包装一个 `List` 并且实现 `Iterable`接口要更简单方便。

In Kotlin, `iterator()` is an operator function. This means that when a class defines a function `operator fun iterator()`, it can be iterated using a `for` loop (no interface needed). That's particularly cool because it works also with extension functions. That's right - by using an extension function, I can make *every* object iterable. Consider this example:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[That's particularly cool]=>[这种方式非常酷。]

Kotlin 中, `iterator()` 是个操作符函数。这意味着当一个类定义了 `operator fun iterator()` 这个函数后,可以使用 `for` 循环来遍历它(不需要声明接口)。这个函数也能通过拓展函数配合使用,这是很酷炫的。 通过拓展函数,我们可以让 **每一个** 对象都是可迭代的。看下面这个例子:

```
class Sentence(val words: List<String>)
...
operator fun Sentence.iterator(): Iterator<String> = words.iterator()
```

I can now iterate over a `Sentence`. This also works if I'm not the owner of the class.
现在我们可以在 `Sentence` 上进行迭代操作了。如果没有这个类的控制权的话,迭代器仍然将正常工作。

### More patterns
### 更多的模式……

I mentioned a few patterns, but those are not all *Gang of Four* patterns. As I said in the introduction, especially the structural patterns are hard or impossible to write in a different way than in Java. [Some other patterns can be found in the repository](https://github.com/lmller/gof-in-kotlin). I'm open for feedback and pull-requests ☺.
这篇文章确实提到了相当几个设计模式,但这不是 **“四人帮”** 设计模式的全部。就像我在一开始提到的那样,尤其是结构型设计模式很难甚至根本不可能用和 Java 不同的方法来实现。 你可以在 [这个代码仓库](https://github.com/lmller/gof-in-kotlin) 找到更多的设计模式。欢迎来提交反馈和 PR。☺

I hope this post gave you an idea on how Kotlin can result in different approaches to well known problems.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[-I hope this post gave you an idea on how Kotlin can result in different approaches to well known problems.]=>[希望这篇文章能给你些启发,让你了解用Kotlin可以带来新的思路来解决那些广为人知的问题。]

希望这篇文章能给你些启发,让你对于 Kotlin 为广为人知的问题带来的新解决方案有自己的认识。

One last thing I want to mention is, that the java-to-kotlin ratio in the repository is ~⅓ Kotlin and ~⅔ Java, although both versions do the same thing 🙃
最后我想说的是,仓库中的代码量大概有 ⅓ 的 Kotlin 和 ⅔ 的 Java,虽然这两部分代码干了同样的事情🙃

---

The cover image was taken from [stocksnap.io](stocksnap.io)
封面图片来自 [stocksnap.io](stocksnap.io)

---

Expand Down