分类 软件开发 下的文章

你可以让 Git 帮助你轻松发布你的网站。在我们《鲜为人知的 Git 用法》系列的第一篇文章中学习如何做到。

Git 是一个少有的能将如此多的现代计算封装到一个程序之中的应用程序,它可以用作许多其他应用程序的计算引擎。虽然它以跟踪软件开发中的源代码更改而闻名,但它还有许多其他用途,可以让你的生活更轻松、更有条理。在这个 Git 系列中,我们将分享七种鲜为人知的使用 Git 的方法。

创建一个网站曾经是极其简单的,而同时它又是一种黑魔法。回到 Web 1.0 的旧时代(不是每个人都会这样称呼它),你可以打开任何网站,查看其源代码,并对 HTML 及其内联样式和基于表格的布局进行反向工程,在这样的一两个下午之后,你就会感觉自己像一个程序员一样。不过要让你创建的页面放到互联网上,仍然有一些问题,因为这意味着你需要处理服务器、FTP 以及 webroot 目录和文件权限。虽然从那时起,现代网站变得愈加复杂,但如果你让 Git 帮助你,自出版可以同样容易(或更容易!)。

用 Hugo 创建一个网站

Hugo 是一个开源的静态站点生成器。静态网站是过去的 Web 的基础(如果你回溯到很久以前,那就是 Web 的全部了)。静态站点有几个优点:它们相对容易编写,因为你不必编写代码;它们相对安全,因为页面上没有执行代码;并且它们可以非常快,因为除了在页面上传输的任何内容之外没有任何处理。

Hugo 并不是唯一一个静态站点生成器。GravPicoJekyllPodwrite 以及许多其他的同类软件都提供了一种创建一个功能最少的、只需要很少维护的网站的简单方法。Hugo 恰好是内置集成了 GitLab 集成的一个静态站点生成器,这意味着你可以使用免费的 GitLab 帐户生成和托管你的网站。

Hugo 也有一些非常大的用户。例如,如果你曾经去过 Let’s Encrypt 网站,那么你已经用过了一个用 Hugo 构建的网站。

 title=

安装 Hugo

Hugo 是跨平台的,你可以在 Hugo 的入门资源中找到适用于 MacOS、Windows、Linux、OpenBSD 和 FreeBSD 的安装说明。

如果你使用的是 Linux 或 BSD,最简单的方法是从软件存储库或 ports 树安装 Hugo。确切的命令取决于你的发行版,但在 Fedora 上,你应该输入:

$ sudo dnf install hugo

通过打开终端并键入以下内容确认你已正确安装:

$ hugo help

这将打印 hugo 命令的所有可用选项。如果你没有看到,你可能没有正确安装 Hugo 或需要将该命令添加到你的路径

创建你的站点

要构建 Hugo 站点,你必须有个特定的目录结构,通过输入以下命令 Hugo 将为你生成它:

$ hugo new site mysite

你现在有了一个名为 mysite 的目录,它包含构建 Hugo 网站所需的默认目录。

Git 是你将网站放到互联网上的接口,因此切换到你新的 mysite 文件夹,并将其初始化为 Git 存储库:

$ cd mysite
$ git init .

Hugo 与 Git 配合的很好,所以你甚至可以使用 Git 为你的网站安装主题。除非你计划开发你正在安装的主题,否则可以使用 --depth 选项克隆该主题的源的最新状态:

$ git clone --depth 1 https://github.com/darshanbaral/mero.git themes/mero

现在为你的网站创建一些内容:

$ hugo new posts/hello.md

使用你喜欢的文本编辑器编辑 content/posts 目录中的 hello.md 文件。Hugo 接受 Markdown 文件,并会在发布时将它们转换为经过主题化的 HTML 文件,因此你的内容必须采用 Markdown 格式

如果要在帖子中包含图像,请在 static 目录中创建一个名为 images 的文件夹。将图像放入此文件夹,并使用以 /images 开头的绝对路径在标记中引用它们。例如:

![A picture of a thing](/images/thing.jpeg)

选择主题

你可以在 themes.gohugo.io 找到更多主题,但最好在测试时保持一个基本主题。标准的 Hugo 测试主题是 Ananke。某些主题具有复杂的依赖关系,而另外一些主题如果没有复杂的配置的话,也许不会以你预期的方式呈现页面。本例中使用的 Mero 主题捆绑了一个详细的 config.toml 配置文件,但是(为了简单起见)我将在这里只提供基本的配置。在文本编辑器中打开名为 config.toml 的文件,并添加三个配置参数:

languageCode = "en-us"
title = "My website on the web"
theme = "mero"

[params]
  author = "Seth Kenlon"
  description = "My hugo demo"

预览

在你准备发布之前不必(预先)在互联网上放置任何内容。在你开发网站时,你可以通过启动 Hugo 附带的仅限本地访问的 Web 服务器来预览你的站点。

$ hugo server --buildDrafts --disableFastRender

打开 Web 浏览器并导航到 http://localhost:1313 以查看正在进行的工作。

用 Git 发布到 GitLab

要在 GitLab 上发布和托管你的站点,请为你的站点内容创建一个存储库。

要在 GitLab 中创建存储库,请单击 GitLab 的 “Projects” 页面中的 “New Project” 按钮。创建一个名为 yourGitLabUsername.gitlab.io 的空存储库,用你的 GitLab 用户名或组名替换 yourGitLabUsername。你必须使用此命名方式作为该项目的名称。你也可以稍后为其添加自定义域。

不要在 GitLab 上包含许可证或 README 文件(因为你已经在本地启动了一个项目,现在添加这些文件会使将你的数据推向 GitLab 时更加复杂,以后你可以随时添加它们)。

在 GitLab 上创建空存储库后,将其添加为 Hugo 站点的本地副本的远程位置,该站点已经是一个 Git 存储库:

$ git remote add origin [email protected]:skenlon/mysite.git

创建名为 .gitlab-ci.yml 的 GitLab 站点配置文件并输入以下选项:

image: monachus/hugo

variables:
  GIT_SUBMODULE_STRATEGY: recursive

pages:
  script:
  - hugo
  artifacts:
    paths:
    - public
  only:
  - master

image 参数定义了一个为你的站点提供服务的容器化图像。其他参数是告诉 GitLab 服务器在将新代码推送到远程存储库时要执行的操作的说明。有关 GitLab 的 CI/CD(持续集成和交付)选项的更多信息,请参阅 GitLab 文档的 CI/CD 部分

设置排除的内容

你的 Git 存储库已配置好,在 GitLab 服务器上构建站点的命令也已设置,你的站点已准备好发布了。对于你的第一个 Git 提交,你必须采取一些额外的预防措施,以便你不会对你不打算进行版本控制的文件进行版本控制。

首先,将构建你的站点时 Hugo 创建的 /public 目录添加到 .gitignore 文件。你无需在 Git 中管理已完成发布的站点;你需要跟踪的是你的 Hugo 源文件。

$ echo "/public" >> .gitignore

如果不创建 Git 子模块,则无法在 Git 存储库中维护另一个 Git 存储库。为了简单起见,请移除嵌入的存储库的 .git 目录,以使主题(存储库)只是一个主题(目录)。

请注意,你必须将你的主题文件添加到你的 Git 存储库,以便 GitLab 可以访问该主题。如果不提交主题文件,你的网站将无法成功构建。

$ mv themes/mero/.git ~/.local/share/Trash/files/

你也可以像使用回收站一样使用 trash

$ trash themes/mero/.git

现在,你可以将本地项目目录的所有内容添加到 Git 并将其推送到 GitLab:

$ git add .
$ git commit -m 'hugo init'
$ git push -u origin HEAD

用 GitLab 上线

将代码推送到 GitLab 后,请查看你的项目页面。有个图标表示 GitLab 正在处理你的构建。第一次推送代码可能需要几分钟,所以请耐心等待。但是,请不要一直等待,因为该图标并不总是可靠地更新。

 title=

当你在等待 GitLab 组装你的站点时,请转到你的项目设置并找到 “Pages” 面板。你的网站准备就绪后,它的 URL 就可以用了。该 URL 是 yourGitLabUsername.gitlab.io/yourProjectName。导航到该地址以查看你的劳动成果。

 title=

如果你的站点无法正确组装,GitLab 提供了可以深入了解 CI/CD 管道的日志。查看错误消息以找出发生了什么问题。

Git 和 Web

Hugo(或 Jekyll 等类似工具)只是利用 Git 作为 Web 发布工具的一种方式。使用服务器端 Git 挂钩,你可以使用最少的脚本设计你自己的 Git-to-web 工作流。使用 GitLab 的社区版,你可以自行托管你自己的 GitLab 实例;或者你可以使用 GitoliteGitea 等替代方案,并使用本文作为自定义解决方案的灵感来源。祝你玩得开心!


via: https://opensource.com/article/19/4/building-hosting-website-git

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

使用 Python 函数来最大程度地减少重复任务编码工作量。

你是否对函数、类、方法、库和模块等花哨的编程术语感到困惑?你是否在与变量作用域斗争?无论你是自学成才的还是经过正式培训的程序员,代码的模块化都会令人困惑。但是类和库鼓励模块化代码,因为模块化代码意味着只需构建一个多用途代码块集合,就可以在许多项目中使用它们来减少编码工作量。换句话说,如果你按照本文对 Python 函数的研究,你将找到更聪明的工作方法,这意味着更少的工作。

本文假定你对 Python 很熟(LCTT 译注:稍微熟悉就可以),并且可以编写和运行一个简单的脚本。如果你还没有使用过 Python,请首先阅读我的文章:Python 简介

函数

函数是迈向模块化过程中重要的一步,因为它们是形式化的重复方法。如果在你的程序中,有一个任务需要反复执行,那么你可以将代码放入一个函数中,根据需要随时调用该函数。这样,你只需编写一次代码,就可以随意使用它。

以下一个简单函数的示例:

#!/usr/bin/env python3
import time

def Timer():
    print("Time is " + str(time.time() ))

创建一个名为 mymodularity 的目录,并将以上函数代码保存为该目录下的 timestamp.py

除了这个函数,在 mymodularity 目录中创建一个名为 __init__.py 的文件,你可以在文件管理器或 bash shell 中执行此操作:

$ touch mymodularity/__init__.py

现在,你已经创建了属于你自己的 Python 库(Python 中称为“模块”),名为 mymodularity。它不是一个特别有用的模块,因为它所做的只是导入 time 模块并打印一个时间戳,但这只是一个开始。

要使用你的函数,像对待任何其他 Python 模块一样对待它。以下是一个小应用,它使用你的 mymodularity 软件包来测试 Python sleep() 函数的准确性。将此文件保存为 sleeptest.py,注意要在 mymodularity 文件夹 之外,因为如果你将它保存在 mymodularity 里面,那么它将成为你的包中的一个模块,你肯定不希望这样。

#!/usr/bin/env python3

import time
from mymodularity import timestamp

print("Testing Python sleep()...")

# modularity
timestamp.Timer()
time.sleep(3)
timestamp.Timer()

在这个简单的脚本中,你从 mymodularity 包中调用 timestamp 模块两次。从包中导入模块时,通常的语法是从包中导入你所需的模块,然后使用 模块名称 + 一个点 + 要调用的函数名(例如 timestamp.Timer())。

你调用了两次 Timer() 函数,所以如果你的 timestamp 模块比这个简单的例子复杂些,那么你将节省大量重复代码。

保存文件并运行:

$ python3 ./sleeptest.py
Testing Python sleep()...
Time is 1560711266.1526039
Time is 1560711269.1557732

根据测试,Python 中的 sleep 函数非常准确:在三秒钟等待之后,时间戳成功且正确地增加了 3,在微秒单位上差距很小。

Python 库的结构看起来可能令人困惑,但其实它并不是什么魔法。Python 被编程 为一个包含 Python 代码的目录,并附带一个 __init__.py 文件,那么这个目录就会被当作一个包,并且 Python 会首先在当前目录中查找可用模块。这就是为什么语句 from mymodularity import timestamp 有效的原因:Python 在当前目录查找名为 mymodularity 的目录,然后查找 timestamp.py 文件。

你在这个例子中所做的功能和以下这个非模块化的版本是一样的:

#!/usr/bin/env python3

import time
from mymodularity import timestamp

print("Testing Python sleep()...")

# no modularity
print("Time is " + str(time.time() ) )
time.sleep(3)
print("Time is " + str(time.time() ) )

对于这样一个简单的例子,其实没有必要以这种方式编写测试,但是对于编写自己的模块来说,最佳实践是你的代码是通用的,可以将它重用于其他项目。

通过在调用函数时传递信息,可以使代码更通用。例如,假设你想要使用模块来测试的不是 系统sleep 函数,而是 用户自己实现sleep 函数,更改 timestamp 代码,使它接受一个名为 msg 的传入变量,它将是一个字符串,控制每次调用 timestamp 时如何显示:

#!/usr/bin/env python3

import time

# 更新代码
def Timer(msg):
    print(str(msg) + str(time.time() ) )

现在函数比以前更抽象了。它仍会打印时间戳,但是它为用户打印的内容 msg 还是未定义的。这意味着你需要在调用函数时定义它。

Timer 函数接受的 msg 参数是随便命名的,你可以使用参数 mmessagetext,或是任何对你来说有意义的名称。重要的是,当调用 timestamp.Timer 函数时,它接收一个文本作为其输入,将接收到的任何内容放入 msg 变量中,并使用该变量完成任务。

以下是一个测试测试用户正确感知时间流逝能力的新程序:

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press the RETURN key. Count to 3, and press RETURN again.")

input()
timestamp.Timer("Started timer at ")

print("Count to 3...")

input()
timestamp.Timer("You slept until ")

将你的新程序保存为 response.py,运行它:

$ python3 ./response.py
Press the RETURN key. Count to 3, and press RETURN again.

Started timer at 1560714482.3772075
Count to 3...

You slept until 1560714484.1628013

函数和所需参数

新版本的 timestamp 模块现在 需要 一个 msg 参数。这很重要,因为你的第一个应用程序将无法运行,因为它没有将字符串传递给 timestamp.Timer 函数:

$ python3 ./sleeptest.py
Testing Python sleep()...
Traceback (most recent call last):
  File "./sleeptest.py", line 8, in <module>
    timestamp.Timer()
TypeError: Timer() missing 1 required positional argument: 'msg'

你能修复你的 sleeptest.py 应用程序,以便它能够与更新后的模块一起正确运行吗?

变量和函数

通过设计,函数限制了变量的范围。换句话说,如果在函数内创建一个变量,那么这个变量 在这个函数内起作用。如果你尝试在函数外部使用函数内部出现的变量,就会发生错误。

下面是对 response.py 应用程序的修改,尝试从 timestamp.Timer() 函数外部打印 msg 变量:

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press the RETURN key. Count to 3, and press RETURN again.")

input()
timestamp.Timer("Started timer at ")

print("Count to 3...")

input()
timestamp.Timer("You slept for ")

print(msg)

试着运行它,查看错误:

$ python3 ./response.py
Press the RETURN key. Count to 3, and press RETURN again.

Started timer at 1560719527.7862902
Count to 3...

You slept for 1560719528.135406
Traceback (most recent call last):
  File "./response.py", line 15, in <module>
    print(msg)
NameError: name 'msg' is not defined

应用程序返回一个 NameError 消息,因为没有定义 msg。这看起来令人困惑,因为你编写的代码定义了 msg,但你对代码的了解比 Python 更深入。调用函数的代码,不管函数是出现在同一个文件中,还是打包为模块,都不知道函数内部发生了什么。一个函数独立地执行它的计算,并返回你想要它返回的内容。这其中所涉及的任何变量都只是 本地的:它们只存在于函数中,并且只存在于函数完成其目的所需时间内。

Return 语句

如果你的应用程序需要函数中特定包含的信息,那么使用 return 语句让函数在运行后返回有意义的数据。

时间就是金钱,所以修改 timestamp 函数,以使其用于一个虚构的收费系统:

#!/usr/bin/env python3

import time

def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge

现在,timestamp 模块每次调用都收费 2 美分,但最重要的是,它返回每次调用时所收取的金额。

以下一个如何使用 return 语句的演示:

#!/usr/bin/env python3

from mymodularity import timestamp

print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")

total = 0

while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = timestamp.Timer("Time is ")
        total = total+charge

在这个示例代码中,变量 chargetimestamp.Timer() 函数的返回,它接收函数返回的任何内容。在本例中,函数返回一个数字,因此使用一个名为 total 的新变量来跟踪已经进行了多少更改。当应用程序收到要退出的信号时,它会打印总花费:

$ python3 ./charge.py
Press RETURN for the time (costs 2 cents).
Press Q RETURN to quit.

Time is 1560722430.345412

Time is 1560722430.933996

Time is 1560722434.6027434

Time is 1560722438.612629

Time is 1560722439.3649364
q
You owe $0.1

内联函数

函数不必在单独的文件中创建。如果你只是针对一个任务编写一个简短的脚本,那么在同一个文件中编写函数可能更有意义。唯一的区别是你不必导入自己的模块,但函数的工作方式是一样的。以下是时间测试应用程序的最新迭代:

#!/usr/bin/env python3

import time

total = 0

def Timer(msg):
    print(str(msg) + str(time.time() ) )
    charge = .02
    return charge

print("Press RETURN for the time (costs 2 cents).")
print("Press Q RETURN to quit.")

while True:
    kbd = input()
    if kbd.lower() == "q":
        print("You owe $" + str(total) )
        exit()
    else:
        charge = Timer("Time is ")
        total = total+charge

它没有外部依赖(Python 发行版中包含 time 模块),产生与模块化版本相同的结果。它的优点是一切都位于一个文件中,缺点是你不能在其他脚本中使用 Timer() 函数,除非你手动复制和粘贴它。

全局变量

在函数外部创建的变量没有限制作用域,因此它被视为 全局 变量。

全局变量的一个例子是在 charge.py 中用于跟踪当前花费的 total 变量。total 是在函数之外创建的,因此它绑定到应用程序而不是特定函数。

应用程序中的函数可以访问全局变量,但要将变量传入导入的模块,你必须像发送 msg 变量一样将变量传入模块。

全局变量很方便,因为它们似乎随时随地都可用,但也很难跟踪它们,很难知道哪些变量不再需要了但是仍然在系统内存中停留(尽管 Python 有非常好的垃圾收集机制)。

但是,全局变量很重要,因为不是所有的变量都可以是函数或类的本地变量。现在你知道了如何向函数传入变量并获得返回,事情就变得容易了。

总结

你已经学到了很多关于函数的知识,所以开始将它们放入你的脚本中 —— 如果它不是作为单独的模块,那么作为代码块,你不必在一个脚本中编写多次。在本系列的下一篇文章中,我将介绍 Python 类。


via: https://opensource.com/article/19/7/get-modular-python-functions

作者:Seth Kenlon 选题:lujun9972 译者:MjSeven 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这些资源将帮助你入门并熟练掌握 Python。

Python 是世界上最受欢迎的编程语言之一,它受到了全世界各地的开发者和创客的欢迎。大多数 Linux 和 MacOS 计算机都预装了某个版本的 Python,现在甚至一些 Windows 计算机供应商也开始安装 Python 了。

也许你尚未学会它,想学习但又不知道在哪里入门。这里的 12 个资源将帮助你入门并熟练掌握 Python。

课程、书籍、文章和文档

1、Python 软件基金会提供了出色的信息和文档,可帮助你迈上编码之旅。请务必查看 Python 入门指南。它将帮助你得到最新版本的 Python,并提供有关编辑器和开发环境的有用提示。该组织还有可以来进一步指导你的优秀文档

2、我的 Python 旅程始于海龟模块。我首先在 Bryson Payne 的《教你的孩子编码》中找到了关于 Python 和海龟的内容。这本书是一个很好的资源,购买这本书可以让你看到几十个示例程序,这将激发你的编程好奇心。Payne 博士还在 Udemy 上以相同的名称开设了一门便宜的课程。

3、Payne 博士的书激起了我的好奇心,我渴望了解更多。这时我发现了 Al Sweigart 的《用 Python 自动化无聊的东西》。你可以购买这本书,也可以使用它的在线版本,它与印刷版完全相同且可根据知识共享许可免费获得和分享。Al 的这本书让我学习到了 Python 的基础知识、函数、列表、字典和如何操作字符串等等。这是一本很棒的书,我已经购买了许多本捐赠给了当地图书馆。Al 还提供 Udemy 课程;使用他的网站上的优惠券代码,只需 10 美元即可参加。

4、Eric Matthes 撰写了《Python 速成》,这是由 No Starch Press 出版的 Python 的逐步介绍(如同上面的两本书)。Matthes 还有一个很棒的伴侣网站,其中包括了如何在你的计算机上设置 Python 以及一个用以简化学习曲线的速查表

5、Python for Everybody 是另一个很棒的 Python 学习资源。该网站可以免费访问 Charles Severance 的 Coursera 和 edX 认证课程的资料。该网站分为入门、课程和素材等部分,其中 17 个课程按从安装到数据可视化的主题进行分类组织。Severance(@drchuck on Twitter),是密歇根大学信息学院的临床教授。

6、Seth Kenlon,我们 Opensource.com 的 Python 大师,撰写了大量关于 Python 的文章。Seth 有很多很棒的文章,包括“用 JSON 保存和加载 Python 数据”,“用 Python 学习面向对象编程”,“在 Python 游戏中用 Pygame 放置平台”,等等。

在设备上使用 Python

7、最近我对 Circuit Playground Express 非常感兴趣,这是一个运行 CircuitPython 的设备,CircuitPython 是为微控制器设计的 Python 编程语言的子集。我发现 Circuit Playground Express 和 CircuitPython 是向学生介绍 Python(以及一般编程)的好方法。它的制造商 Adafruit 有一个很好的系列教程,可以让你快速掌握 CircuitPython。

8、BBC:Microbit 是另一种入门 Python 的好方法。你可以学习如何使用 MicroPython 对其进行编程,这是另一种用于编程微控制器的 Python 实现。

9、学习 Python 的文章如果没有提到树莓派单板计算机那是不完整的。一旦你有了舒适而强大的树莓派,你就可以在 Opensource.com 上找到成吨的使用它的灵感,包括“7 个值得探索的树莓派项目”,“在树莓派上复活 Amiga”,和“如何使用树莓派作为 VPN 服务器”。

10、许多学校为学生提供了 iOS 设备以支持他们的教育。在尝试帮助这些学校的老师和学生学习用 Python 编写代码时,我发现了 Trinket.io。Trinket 允许你在浏览器中编写和执行 Python 3 代码。 Trinket 的 Python 入门教程将向你展示如何在 iOS 设备上使用 Python。

播客

11、我喜欢在开车的时候听播客,我在 Kelly Paredes 和 Sean Tibor 的 Teaching Python 播客上找到了大量的信息。他们的内容很适合教育领域。

12、如果你正在寻找一些更通用的东西,我推荐 Michael Kennedy 的 Talk Python to Me 播客。它提供了有关 Python 及相关技术的最佳信息。

你学习 Python 最喜欢的资源是什么?请在评论中分享。

计算机编程可能是一个有趣的爱好,正如我以前在 Apple II 计算机上编程时所学到的……


via: https://opensource.com/article/19/8/dozen-ways-learn-python

作者:Don Watkins 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你仍未使用过 Autotools,那么这篇文章将改变你递交代码的方式。

你有没有下载过流行的软件项目的源代码,要求你输入几乎是仪式般的 ./configure; make && make install 命令序列来构建和安装它?如果是这样,你已经使用过 GNU Autotools 了。如果你曾经研究过这样的项目所附带的一些文件,你可能会对这种构建系统的显而易见的复杂性感到害怕。

好的消息是,GNU Autotools 的设置要比你想象的要简单得多,GNU Autotools 本身可以为你生成这些上千行的配置文件。是的,你可以编写 20 或 30 行安装代码,并免费获得其他 4,000 行。

Autotools 工作方式

如果你是初次使用 Linux 的用户,正在寻找有关如何安装应用程序的信息,那么你不必阅读本文!如果你想研究如何构建软件,欢迎阅读它;但如果你只是要安装一个新应用程序,请阅读我在在 Linux 上安装应用程序的文章。

对于开发人员来说,Autotools 是一种管理和打包源代码的快捷方式,以便用户可以编译和安装软件。 Autotools 也得到了主要打包格式(如 DEB 和 RPM)的良好支持,因此软件存储库的维护者可以轻松管理使用 Autotools 构建的项目。

Autotools 工作步骤:

  1. 首先,在 ./configure 步骤中,Autotools 扫描宿主机系统(即当前正在运行的计算机)以发现默认设置。默认设置包括支持库所在的位置,以及新软件应放在系统上的位置。
  2. 接下来,在 make 步骤中,Autotools 通常通过将人类可读的源代码转换为机器语言来构建应用程序。
  3. 最后,在 make install 步骤中,Autotools 将其构建好的文件复制到计算机上(在配置阶段检测到)的相应位置。

这个过程看起来很简单,和你使用 Autotools 的步骤一样。

Autotools 的优势

GNU Autotools 是我们大多数人认为理所当然的重要软件。与 GCC(GNU 编译器集合)一起,Autotools 是支持将自由软件构建和安装到正在运行的系统的脚手架。如果你正在运行 POSIX 系统,可以毫不保守地说,你的计算机上的操作系统里大多数可运行软件都是这些这样构建的。

即使是你的项目是个玩具项目不是操作系统,你可能会认为 Autotools 对你的需求来说太过分了。但是,尽管它的名气很大,Autotools 有许多可能对你有益的小功能,即使你的项目只是一个相对简单的应用程序或一系列脚本。

可移植性

首先,Autotools 考虑到了可移植性。虽然它无法使你的项目在所有 POSIX 平台上工作(这取决于你,编码的人),但 Autotools 可以确保你标记为要安装的文件安装到已知平台上最合理的位置。而且由于 Autotools,高级用户可以轻松地根据他们自己的系统情况定制和覆盖任何非最佳设定。

使用 Autotools,你只要知道需要将文件安装到哪个常规位置就行了。它会处理其他一切。不需要可能破坏未经测试的操作系统的定制安装脚本。

打包

Autotools 也得到了很好的支持。将一个带有 Autotools 的项目交给一个发行版打包者,无论他们是打包成 RPM、DEB、TGZ 还是其他任何东西,都很简单。打包工具知道 Autotools,因此可能不需要修补、魔改或调整。在许多情况下,将 Autotools 项目结合到流程中甚至可以实现自动化。

如何使用 Autotools

要使用 Autotools,必须先安装它。你的发行版可能提供一个单个的软件包来帮助开发人员构建项目,或者它可能为每个组件提供了单独的软件包,因此你可能需要在你的平台上进行一些研究以发现需要安装的软件包。

Autotools 的组件是:

  • automake
  • autoconf
  • automake
  • make

虽然你可能需要安装项目所需的编译器(例如 GCC),但 Autotools 可以很好地处理不需要编译的脚本或二进制文件。实际上,Autotools 对于此类项目非常有用,因为它提供了一个 make uninstall 脚本,以便于删除。

安装了所有组件之后,现在让我们了解一下你的项目文件的组成结构。

Autotools 项目结构

GNU Autotools 有非常具体的预期规范,如果你经常下载和构建源代码,可能大多数都很熟悉。首先,源代码本身应该位于一个名为 src 的子目录中。

你的项目不必遵循所有这些预期规范,但如果你将文件放在非标准位置(从 Autotools 的角度来看),那么你将不得不稍后在 Makefile 中对其进行调整。

此外,这些文件是必需的:

  • NEWS
  • README
  • AUTHORS
  • ChangeLog

你不必主动使用这些文件,它们可以是包含所有信息的单个汇总文档(如 README.md)的符号链接,但它们必须存在。

Autotools 配置

在你的项目根目录下创建一个名为 configure.ac 的文件。autoconf 使用此文件来创建用户在构建之前运行的 configure shell 脚本。该文件必须至少包含 AC_INITAC_OUTPUT M4 宏。你不需要了解有关 M4 语言的任何信息就可以使用这些宏;它们已经为你编写好了,并且所有与 Autotools 相关的内容都在该文档中定义好了。

在你喜欢的文本编辑器中打开该文件。AC_INIT 宏可以包括包名称、版本、报告错误的电子邮件地址、项目 URL 以及可选的源 TAR 文件名称等参数。

AC\_OUTPUT 宏更简单,不用任何参数。

AC_INIT([penguin], [2019.3.6], [[[email protected]][8]])
AC_OUTPUT

如果你此刻运行 autoconf,会依据你的 configure.ac 文件生成一个 configure 脚本,它是可以运行的。但是,也就是能运行而已,因为到目前为止你所做的就是定义项目的元数据,并要求创建一个配置脚本。

你必须在 configure.ac 文件中调用的下一个宏是创建 Makefile 的函数。 Makefile 会告诉 make 命令做什么(通常是如何编译和链接程序)。

创建 Makefile 的宏是 AM_INIT_AUTOMAKE,它不接受任何参数,而 AC_CONFIG_FILES 接受的参数是你要输出的文件的名称。

最后,你必须添加一个宏来考虑你的项目所需的编译器。你使用的宏显然取决于你的项目。如果你的项目是用 C++ 编写的,那么适当的宏是 AC_PROG_CXX,而用 C 编写的项目需要 AC_PROG_CC,依此类推,详见 Autoconf 文档中的 Building Programs and Libraries 部分。

例如,我可能会为我的 C++ 程序添加以下内容:

AC_INIT([penguin], [2019.3.6], [[[email protected]][8]])
AC_OUTPUT
AM_INIT_AUTOMAKE
AC_CONFIG_FILES([Makefile])
AC_PROG_CXX

保存该文件。现在让我们将目光转到 Makefile

生成 Autotools Makefile

Makefile 并不难手写,但 Autotools 可以为你编写一个,而它生成的那个将使用在 ./configure 步骤中检测到的配置选项,并且它将包含比你考虑要包括或想要自己写的还要多得多的选项。然而,Autotools 并不能检测你的项目构建所需的所有内容,因此你必须在文件 Makefile.am 中添加一些细节,然后在构造 Makefile 时由 automake 使用。

Makefile.am 使用与 Makefile 相同的语法,所以如果你曾经从头开始编写过 Makefile,那么这个过程将是熟悉和简单的。通常,Makefile.am 文件只需要几个变量定义来指示要构建的文件以及它们的安装位置即可。

_PROGRAMS 结尾的变量标识了要构建的代码(这通常被认为是 原语 primary 目标;这是 Makefile 存在的主要意义)。Automake 也会识别其他原语,如 _SCRIPTS_ DATA_LIBRARIES,以及构成软件项目的其他常见部分。

如果你的应用程序在构建过程中需要实际编译,那么你可以用 bin_PROGRAMS 变量将其标记为二进制程序,然后使用该程序名称作为变量前缀引用构建它所需的源代码的任何部分(这些部分可能是将被编译和链接在一起的一个或多个文件):

bin_PROGRAMS = penguin
penguin_SOURCES = penguin.cpp

bin_PROGRAMS 的目标被安装在 bindir 中,它在编译期间可由用户配置。

如果你的应用程序不需要实际编译,那么你的项目根本不需要 bin_PROGRAMS 变量。例如,如果你的项目是用 Bash、Perl 或类似的解释语言编写的脚本,那么定义一个 _SCRIPTS 变量来替代:

bin_SCRIPTS = bin/penguin

Automake 期望源代码位于名为 src 的目录中,因此如果你的项目使用替代目录结构进行布局,则必须告知 Automake 接受来自外部源的代码:

AUTOMAKE_OPTIONS = foreign subdir-objects

最后,你可以在 Makefile.am 中创建任何自定义的 Makefile 规则,它们将逐字复制到生成的 Makefile 中。例如,如果你知道一些源代码中的临时值需要在安装前替换,则可以为该过程创建自定义规则:

all-am: penguin
        touch bin/penguin.sh
       
penguin: bin/penguin.sh
        @sed "s|__datadir__|@datadir@|" $< >bin/$@

一个特别有用的技巧是扩展现有的 clean 目标,至少在开发期间是这样的。make clean 命令通常会删除除了 Automake 基础结构之外的所有生成的构建文件。它是这样设计的,因为大多数用户很少想要 make clean 来删除那些便于构建代码的文件。

但是,在开发期间,你可能需要一种方法可靠地将项目返回到相对不受 Autotools 影响的状态。在这种情况下,你可能想要添加:

clean-local:
        @rm config.status configure config.log
        @rm Makefile
        @rm -r autom4te.cache/
        @rm aclocal.m4
        @rm compile install-sh missing Makefile.in

这里有很多灵活性,如果你还不熟悉 Makefile,那么很难知道你的 Makefile.am 需要什么。最基本需要的是原语目标,无论是二进制程序还是脚本,以及源代码所在位置的指示(无论是通过 _SOURCES 变量还是使用 AUTOMAKE_OPTIONS 告诉 Automake 在哪里查找源代码)。

一旦定义了这些变量和设置,如下一节所示,你就可以尝试生成构建脚本,并调整缺少的任何内容。

生成 Autotools 构建脚本

你已经构建了基础结构,现在是时候让 Autotools 做它最擅长的事情:自动化你的项目工具。对于开发人员(你),Autotools 的接口与构建代码的用户的不同。

构建者通常使用这个众所周知的顺序:

$ ./configure
$ make
$ sudo make install

但是,要使这种咒语起作用,你作为开发人员必须引导构建这些基础结构。首先,运行 autoreconf 以生成用户在运行 make 之前调用的 configure 脚本。使用 -install 选项将辅助文件(例如符号链接)引入到 depcomp(这是在编译过程中生成依赖项的脚本),以及 compile 脚本的副本(一个编译器的包装器,用于说明语法,等等)。

$ autoreconf --install
configure.ac:3: installing './compile'
configure.ac:2: installing './install-sh'
configure.ac:2: installing './missing'

使用此开发构建环境,你可以创建源代码分发包:

$ make dist

dist 目标是从 Autotools “免费”获得的规则。这是一个内置于 Makefile 中的功能,它是通过简单的 Makefile.am 配置生成的。该目标可以生成一个 tar.gz 存档,其中包含了所有源代码和所有必要的 Autotools 基础设施,以便下载程序包的人员可以构建项目。

此时,你应该仔细查看存档文件的内容,以确保它包含你要发送给用户的所有内容。当然,你也应该尝试自己构建:

$ tar --extract --file penguin-0.0.1.tar.gz
$ cd penguin-0.0.1
$ ./configure
$ make
$ DESTDIR=/tmp/penguin-test-build make install

如果你的构建成功,你将找到由 DESTDIR 指定的已编译应用程序的本地副本(在此示例的情况下为 /tmp/penguin-test-build)。

$ /tmp/example-test-build/usr/local/bin/example
hello world from GNU Autotools

去使用 Autotools

Autotools 是一个很好的脚本集合,可用于可预测的自动发布过程。如果你习惯使用 Python 或 Bash 构建器,这个工具集对你来说可能是新的,但它为你的项目提供的结构和适应性可能值得学习。

而 Autotools 也不只是用于代码。Autotools 可用于构建 Docbook 项目,保持媒体有序(我使用 Autotools 进行音乐发布),文档项目以及其他任何可以从可自定义安装目标中受益的内容。


via: https://opensource.com/article/19/7/introduction-gnu-autotools

作者:Seth Kenlon 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Webhook 可用于外部系统通知你的系统发生了某个事件或更新。可能最知名的 Webhook 类型是支付服务提供商(PSP)通知你的系统支付状态有了更新。

它们通常以监听的预定义 URL 的形式出现,例如 http://example.com/webhooks/payment-update。同时,另一个系统向该 URL 发送具有特定有效载荷的 POST 请求(例如支付 ID)。一旦请求进入,你就会获得支付 ID,可以通过 PSP 的 API 用这个支付 ID 向它们询问最新状态,然后更新你的数据库。

其他例子可以在这个对 Webhook 的出色的解释中找到:https://sendgrid.com/blog/whats-webhook/

只要系统可通过互联网公开访问(这可能是你的生产环境或可公开访问的临时环境),测试这些 webhook 就相当顺利。而当你在笔记本电脑上或虚拟机内部(例如,Vagrant 虚拟机)进行本地开发时,它就变得困难了。在这些情况下,发送 webhook 的一方无法公开访问你的本地 URL。此外,监视发送的请求也很困难,这可能使开发和调试变得困难。

因此,这个例子将解决:

  • 测试来自本地开发环境的 webhook,该环境无法通过互联网访问。从服务器向 webhook 发送数据的服务无法访问它。
  • 监控发送的请求和数据,以及应用程序生成的响应。这样可以更轻松地进行调试,从而缩短开发周期。

前置需求:

  • 可选:如果你使用虚拟机(VM)进行开发,请确保它正在运行,并确保在 VM 中完成后续步骤。
  • 对于本教程,我们假设你定义了一个 vhost:webhook.example.vagrant。我在本教程中使用了 Vagrant VM,但你可以自由选择 vhost。
  • 按照这个安装说明安装 ngrok。在 VM 中,我发现它的 Node 版本也很有用:https://www.npmjs.com/package/ngrok,但你可以随意使用其他方法。

我假设你没有在你的环境中运行 SSL,但如果你使用了,请将在下面的示例中的端口 80 替换为端口 433,http:// 替换为 https://

使 webhook 可测试

我们假设以下示例代码。我将使用 PHP,但请将其视作伪代码,因为我留下了一些关键部分(例如 API 密钥、输入验证等)没有编写。

第一个文件:payment.php。此文件创建一个 $payment 对象,将其注册到 PSP。然后它获取客户需要访问的 URL,以便支付并将用户重定向到客户那里。

请注意,此示例中的 webhook.example.vagrant 是我们为开发设置定义的本地虚拟主机。它无法从外部世界进入。

<?php
/*
 * This file creates a payment and tells the PSP what webhook URL to use for updates
 * After creating the payment, we get a URL to send the customer to in order to pay at the PSP
 */
$payment = [
    'order_id' => 123,
    'amount' => 25.00,
    'description' => 'Test payment',
    'redirect_url' => 'http://webhook.example.vagrant/redirect.php',
    'webhook_url' => 'http://webhook.example.vagrant/webhook.php',
];

$payment = $paymentProvider->createPayment($payment);
header("Location: " . $payment->getPaymentUrl());

第二个文件:webhook.php。此文件等待 PSP 调用以获得有关更新的通知。

<?php
/*
 * This file gets called by the PSP and in the $_POST they submit an 'id'
 * We can use this ID to get the latest status from the PSP and update our internal systems afterward
 */
 
$paymentId = $_POST['id'];
$paymentInfo = $paymentProvider->getPayment($paymentId);
$status = $paymentInfo->getStatus();

// Perform actions in here to update your system
if ($status === 'paid') {
    ..
}
elseif ($status === 'cancelled') {
    ..
}

我们的 webhook URL 无法通过互联网访问(请记住它:webhook.example.vagrant)。因此,PSP 永远不可能调用文件 webhook.php,你的系统将永远不会知道付款状态,这最终导致订单永远不会被运送给客户。

幸运的是,ngrok 可以解决这个问题。 ngrok 将自己描述为:

ngrok 通过安全隧道将 NAT 和防火墙后面的本地服务器暴露给公共互联网。

让我们为我们的项目启动一个基本的隧道。在你的环境中(在你的系统上或在 VM 上)运行以下命令:

ngrok http -host-header=rewrite webhook.example.vagrant:80
阅读其文档可以了解更多配置选项:https://ngrok.com/docs

会出现这样的屏幕:

ngrok 输出

我们刚刚做了什么?基本上,我们指示 ngrok 在端口 80 建立了一个到 http://webhook.example.vagrant 的隧道。同一个 URL 也可以通过 http://39741ffc.ngrok.iohttps://39741ffc.ngrok.io 访问,它们能被任何知道此 URL 的人通过互联网公开访问。

请注意,你可以同时获得 HTTP 和 HTTPS 两个服务。这个文档提供了如何将此限制为 HTTPS 的示例:https://ngrok.com/docs#bind-tls

那么,我们如何让我们的 webhook 现在工作起来?将 payment.php 更新为以下代码:

<?php
/*
 * This file creates a payment and tells the PSP what webhook URL to use for updates
 * After creating the payment, we get a URL to send the customer to in order to pay at the PSP
 */
$payment = [
    'order_id' => 123,
    'amount' => 25.00,
    'description' => 'Test payment',
    'redirect_url' => 'http://webhook.example.vagrant/redirect.php',
    'webhook_url' => 'https://39741ffc.ngrok.io/webhook.php',
];

$payment = $paymentProvider->createPayment($payment);
header("Location: " . $payment->getPaymentUrl());

现在,我们告诉 PSP 通过 HTTPS 调用此隧道 URL。只要 PSP 通过隧道调用 webhook,ngrok 将确保使用未修改的有效负载调用内部 URL。

如何监控对 webhook 的调用?

你在上面看到的屏幕截图概述了对隧道主机的调用,这些数据相当有限。幸运的是,ngrok 提供了一个非常好的仪表板,允许你检查所有调用:

我不会深入研究这个问题,因为它是不言自明的,你只要运行它就行了。因此,我将解释如何在 Vagrant 虚拟机上访问它,因为它不是开箱即用的。

仪表板将允许你查看所有调用、其状态代码、标头和发送的数据。你将看到应用程序生成的响应。

仪表板的另一个优点是它允许你重放某个调用。假设你的 webhook 代码遇到了致命的错误,开始新的付款并等待 webhook 被调用将会很繁琐。重放上一个调用可以使你的开发过程更快。

默认情况下,仪表板可在 http://localhost:4040 访问。

虚拟机中的仪表盘

为了在 VM 中完成此工作,你必须执行一些额外的步骤:

首先,确保可以在端口 4040 上访问 VM。然后,在 VM 内创建一个文件已存放此配置:

web_addr: 0.0.0.0:4040

现在,杀死仍在运行的 ngrok 进程,并使用稍微调整过的命令启动它:

ngrok http -config=/path/to/config/ngrok.conf -host-header=rewrite webhook.example.vagrant:80

尽管 ID 已经更改,但你将看到类似于上一屏幕截图的屏幕。之前的网址不再有效,但你有了一个新网址。 此外,Web Interface URL 已更改:

现在将浏览器指向 http://webhook.example.vagrant:4040 以访问仪表板。另外,对 https://e65642b5.ngrok.io/webhook.php 做个调用。这可能会导致你的浏览器出错,但仪表板应显示正有一个请求。

最后的备注

上面的例子是伪代码。原因是每个外部系统都以不同的方式使用 webhook。我试图基于一个虚构的 PSP 实现给出一个例子,因为可能很多开发人员在某个时刻肯定会处理付款。

请注意,你的 webhook 网址也可能被意图不好的其他人使用。确保验证发送给它的任何输入。

更好的的,可以向 URL 添加令牌,该令牌对于每个支付是唯一的。只有你的系统和发送 webhook 的系统才能知道此令牌。

祝你测试和调试你的 webhook 顺利!

注意:我没有在 Docker 上测试过本教程。但是,这个 Docker 容器看起来是一个很好的起点,并包含了明确的说明:https://github.com/wernight/docker-ngrok


via: https://medium.freecodecamp.org/testing-webhooks-while-using-vagrant-for-development-98b5f3bedb1d

作者:Stefan Doorn 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

设计模式可以帮助消除冗余代码。学习如何利用 Java 使用单例模式、工厂模式和观察者模式。

如果你是一名正在致力于计算机科学或者相关学科的程序员或者学生,很快,你将会遇到一条术语 “ 软件设计模式 software design pattern ”。根据维基百科,“软件设计模式是在平常的软件设计工作中所遭遇的问题的一种通用的、可重复使用的解决方案”。我对该定义的理解是:当在从事于一个编码项目时,你经常会思考,“嗯,这里貌似是冗余代码,我觉得是否能改变这些代码使之更灵活和便于修改?”因此,你会开始考虑怎样分割那些保持不变的内容和需要经常改变的内容。

设计模式是一种通过分割那些保持不变的部分和经常变化的部分,让你的代码更容易修改的方法。

不出意外的话,每个从事编程项目的人都可能会有同样的思考。特别是那些工业级别的项目,在那里通常工作着数十甚至数百名开发者;协作过程表明必须有一些标准和规则来使代码更加优雅并适应变化。这就是为什么我们有了 面向对象编程(OOP)和 软件框架工具。设计模式有点类似于 OOP,但它通过将变化视为自然开发过程的一部分而进一步发展。基本上,设计模式利用了一些 OOP 的思想,比如抽象和接口,但是专注于改变的过程。

当你开始开发项目时,你经常会听到这样一个术语重构,它意味着通过改变代码使它变得更优雅和可复用;这就是设计模式耀眼的地方。当你处理现有代码时(无论是由其他人构建还是你自己过去构建的),了解设计模式可以帮助你以不同的方式看待事物,你将发现问题以及改进代码的方法。

有很多种设计模式,其中单例模式、工厂模式和观察者模式三种最受欢迎,在这篇文章中我将会一一介绍它们。

如何遵循本指南

无论你是一位有经验的编程工作者还是一名刚刚接触的新手,我想让这篇教程让每个人都很容易理解。设计模式概念并不容易理解,减少开始旅程时的学习曲线始终是首要任务。因此,除了这篇带有图表和代码片段的文章外,我还创建了一个 GitHub 仓库,你可以克隆仓库并在你的电脑上运行这些代码来实现这三种设计模式。你也可以观看我创建的 YouTube视频

必要条件

如果你只是想了解一般的设计模式思想,则无需克隆示例项目或安装任何工具。但是,如果要运行示例代码,你需要安装以下工具:

  • Java 开发套件(JDK):我强烈建议使用 OpenJDK
  • Apache Maven:这个简单的项目使用 Apache Maven 构建;好的是许多 IDE 自带了Maven。
  • 交互式开发编辑器(IDE):我使用 社区版 IntelliJ,但是你也可以使用 Eclipse IDE 或者其他你喜欢的 Java IDE。
  • Git:如果你想克隆这个工程,你需要 Git 客户端。

安装好 Git 后运行下列命令克隆这个工程:

git clone https://github.com/bryantson/OpensourceDotComDemos.git

然后在你喜欢的 IDE 中,你可以将 TopDesignPatterns 仓库中的代码作为 Apache Maven 项目导入。

我使用的是 Java,但你也可以使用支持抽象原则)的任何编程语言来实现设计模式。

单例模式:避免每次创建一个对象

单例模式 singleton pattern 是非常流行的设计模式,它的实现相对来说很简单,因为你只需要一个类。然而,许多开发人员争论单例设计模式的是否利大于弊,因为它缺乏明显的好处并且容易被滥用。很少有开发人员直接实现单例;相反,像 Spring Framework 和 Google Guice 等编程框架内置了单例设计模式的特性。

但是了解单例模式仍然有巨大的用处。单例模式确保一个类仅创建一次且提供了一个对它的全局访问点。

单例模式:确保仅创建一个实例且避免在同一个项目中创建多个实例。

下面这幅图展示了典型的类对象创建过程。当客户端请求创建一个对象时,构造函数会创建或者实例化一个对象并调用方法返回这个类给调用者。但是每次请求一个对象都会发生这样的情况:构造函数被调用,一个新的对象被创建并且它返回了一个独一无二的对象。我猜面向对象语言的创建者有每次都创建一个新对象的理由,但是单例过程的支持者说这是冗余的且浪费资源。

 title=

下面这幅图使用单例模式创建对象。这里,构造函数仅当对象首次通过调用预先设计好的 getInstance() 方法时才会被调用。这通常通过检查该值是否为 null 来完成,并且这个对象被作为私有变量保存在单例类的内部。下次 getInstance() 被调用时,这个类会返回第一次被创建的对象。而没有新的对象产生;它只是返回旧的那一个。

 title=

下面这段代码展示了创建单例模式最简单的方法:

package org.opensource.demo.singleton;

public class OpensourceSingleton {

    private static OpensourceSingleton uniqueInstance;

    private OpensourceSingleton() {
    }

    public static OpensourceSingleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new OpensourceSingleton();
        }
        return uniqueInstance;
    }

}

在调用方,这里展示了如何调用单例类来获取对象:

Opensource newObject = Opensource.getInstance();

这段代码很好的验证了单例模式的思想:

  1. getInstance() 被调用时,它通过检查 null 值来检查对象是否已经被创建。
  2. 如果值为 null,它会创建一个新对象并把它保存到私有域,返回这个对象给调用者。否则直接返回之前被创建的对象。

单例模式实现的主要问题是它忽略了并行进程。当多个进程使用线程同时访问资源时,这个问题就产生了。对于这种情况有对应的解决方案,它被称为双重检查锁,用于多线程安全,如下所示:

package org.opensource.demo.singleton;

public class ImprovedOpensourceSingleton {

    private volatile static ImprovedOpensourceSingleton uniqueInstance;

    private ImprovedOpensourceSingleton() {}

    public static ImprovedOpensourceSingleton getInstance() {
        if (uniqueInstance == null) {
            synchronized (ImprovedOpensourceSingleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new ImprovedOpensourceSingleton();
                }
            }
        }
        return uniqueInstance;
    }

}

再强调一下前面的观点,确保只有在你认为这是一个安全的选择时才直接实现你的单例模式。最好的方法是通过使用一个制作精良的编程框架来利用单例功能。

工厂模式:将对象创建委派给工厂类以隐藏创建逻辑

工厂模式 factory pattern 是另一种众所周知的设计模式,但是有一小点复杂。实现工厂模式的方法有很多,而下列的代码示例为最简单的实现方式。为了创建对象,工厂模式定义了一个接口,让它的子类去决定实例化哪一个类。

工厂模式:将对象创建委派给工厂类,因此它能隐藏创建逻辑。

下列的图片展示了最简单的工厂模式是如何实现的。

 title=

客户端请求工厂类创建类型为 x 的某个对象,而不是客户端直接调用对象创建。根据其类型,工厂模式决定要创建和返回的对象。

在下列代码示例中,OpensourceFactory 是工厂类实现,它从调用者那里获取类型并根据该输入值决定要创建和返回的对象:

package org.opensource.demo.factory;

public class OpensourceFactory {

    public OpensourceJVMServers getServerByVendor(String name) {
        if(name.equals("Apache")) {
            return new Tomcat();
        }
        else if(name.equals("Eclipse")) {
            return new Jetty();
        }
        else if (name.equals("RedHat")) {
            return new WildFly();
        }
        else {
            return null;
        }
    }
}

OpenSourceJVMServer 是一个 100% 的抽象类(即接口类),它指示要实现的是什么,而不是怎样实现:

package org.opensource.demo.factory;

public interface OpensourceJVMServers {
    public void startServer();
    public void stopServer();
    public String getName();
}

这是一个 OpensourceJVMServers 类的实现示例。当 RedHat 被作为类型传递给工厂类,WildFly 服务器将被创建:

package org.opensource.demo.factory;

public class WildFly implements OpensourceJVMServers {
    public void startServer() {
        System.out.println("Starting WildFly Server...");
    }

    public void stopServer() {
        System.out.println("Shutting Down WildFly Server...");
    }

    public String getName() {
        return "WildFly";
    }
}

观察者模式:订阅主题并获取相关更新的通知

最后是 观察者模式 observer pattern 。像单例模式那样,很少有专业的程序员直接实现观察者模式。但是,许多消息队列和数据服务实现都借用了观察者模式的概念。观察者模式在对象之间定义了一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将被自动地通知和更新。

观察者模式:如果有更新,那么订阅了该话题/主题的客户端将被通知。

理解观察者模式的最简单方法是想象一个邮件列表,你可以在其中订阅任何主题,无论是开源、技术、名人、烹饪还是您感兴趣的任何其他内容。每个主题维护者一个它的订阅者列表,在观察者模式中它们相当于观察者。当某一个主题更新时,它所有的订阅者(观察者)都将被通知这次改变。并且订阅者总是能取消某一个主题的订阅。

如下图所示,客户端可以订阅不同的主题并添加观察者以获得最新信息的通知。因为观察者不断的监听着这个主题,这个观察者会通知客户端任何发生的改变。

 title=

让我们来看看观察者模式的代码示例,从主题/话题类开始:

package org.opensource.demo.observer;

public interface Topic {

    public void addObserver(Observer observer);
    public void deleteObserver(Observer observer);
    public void notifyObservers();
}

这段代码描述了一个为不同的主题去实现已定义方法的接口。注意一个观察者如何被添加、移除和通知的。

这是一个主题的实现示例:

package org.opensource.demo.observer;

import java.util.List;
import java.util.ArrayList;

public class Conference implements Topic {
    private List<Observer> listObservers;
    private int totalAttendees;
    private int totalSpeakers;
    private String nameEvent;

    public Conference() {
        listObservers = new ArrayList<Observer>();
    }

    public void addObserver(Observer observer) {
        listObservers.add(observer);
    }

    public void deleteObserver(Observer observer) {
        int i = listObservers.indexOf(observer);
        if (i >= 0) {
            listObservers.remove(i);
        }
    }

    public void notifyObservers() {
        for (int i=0, nObservers = listObservers.size(); i < nObservers; ++ i) {
            Observer observer = listObservers.get(i);
            observer.update(totalAttendees,totalSpeakers,nameEvent);
        }
    }

    public void setConferenceDetails(int totalAttendees, int totalSpeakers, String nameEvent) {
        this.totalAttendees = totalAttendees;
        this.totalSpeakers = totalSpeakers;
        this.nameEvent = nameEvent;
        notifyObservers();
    }
}

这段代码定义了一个特定主题的实现。当发生改变时,这个实现调用它自己的方法。注意这将获取观察者的数量,它以列表方式存储,并且可以通知和维护观察者。

这是一个观察者类:

package org.opensource.demo.observer;

public interface Observer {
    public void update(int totalAttendees, int totalSpeakers, String nameEvent);
}

这个类定义了一个接口,不同的观察者可以实现该接口以执行特定的操作。

例如,实现了该接口的观察者可以在会议上打印出与会者和发言人的数量:

package org.opensource.demo.observer;

public class MonitorConferenceAttendees implements Observer {
    private int totalAttendees;
    private int totalSpeakers;
    private String nameEvent;
    private Topic topic;

    public MonitorConferenceAttendees(Topic topic) {
        this.topic = topic;
        topic.addObserver(this);
    }

    public void update(int totalAttendees, int totalSpeakers, String nameEvent) {
        this.totalAttendees = totalAttendees;
        this.totalSpeakers = totalSpeakers;
        this.nameEvent = nameEvent;
        printConferenceInfo();
    }

    public void printConferenceInfo() {
        System.out.println(this.nameEvent + " has " + totalSpeakers + " speakers and " + totalAttendees + " attendees");
    }
}

接下来

现在你已经阅读了这篇对于设计模式的介绍引导,你还可以去寻求了解其他设计模式,例如外观模式,模版模式和装饰器模式。也有一些并发和分布式系统的设计模式如断路器模式和锚定模式。

可是,我相信最好的磨砺你的技能的方式首先是通过在你的业余项目或者练习中实现这些设计模式。你甚至可以开始考虑如何在实际项目中应用这些设计模式。接下来,我强烈建议你查看 OOP 的 SOLID 原则。之后,你就准备好了解其他设计模式。


via: https://opensource.com/article/19/7/understanding-software-design-patterns

作者:Bryant Son 选题:lujun9972 译者:arrowfeng 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出