行囊

大佬闫的博客


  • 首页

  • 归档

  • 分类

  • 标签

  • 留言

  • 关于

  • 示例

  • 搜索
  • e && s.height < n && s.width > t && s.width < r } function m(e) { var t = e; var n = 0; while (!!t) { n += t.offsetTop; t = t.offsetParent } return n } function g() { var e = document.documentElement; if (!!window.innerWidth) { return window.innerHeight } else if (e && !isNaN(e.clientHeight)) { return e.clientHeight } return 0 } function y() { if (window.pageYOffset) { return window.pageYOffset } return Math.max(document.documentElement.scrollTop, document.body.scrollTop) } function E(e) { var t = m(e); return t >= w && t <= b + w } function S() { var e = document.getElementById("audio_element_id"); if(e != null){ var index = parseInt(e.getAttribute("curSongIndex")); if(index > songs.length - 2) { index = 0; } else { index++; } e.setAttribute("curSongIndex", index); N(); } e.src = i; e.play() } function x(e) { e.className += " " + s + " " + o } function T(e) { e.className += " " + s + " " + u[Math.floor(Math.random() * u.length)] } function N() { var e = document.getElementsByClassName(s); var t = new RegExp("\\b" + s + "\\b"); for (var n = 0; n < e.length; ) { e[n].className = e[n].className.replace(t, "") } } function initAudioEle() { var e = document.getElementById("audio_element_id"); if(e === null){ e = document.createElement("audio"); e.setAttribute("class", l); e.setAttribute("curSongIndex", 0); e.id = "audio_element_id"; e.loop = false; e.bgcolor = 0; e.addEventListener("canplay", function() { setTimeout(function() { x(k) }, 500); setTimeout(function() { N(); p(); for (var e = 0; e < O.length; e++) { T(O[e]) } }, 15500) }, true); e.addEventListener("ended", function() { N(); h(); go(); }, true); e.innerHTML = "

    If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.

    "; document.body.appendChild(e); } } initAudioEle(); var e = 30; var t = 30; var n = 350; var r = 350; var curSongIndex = parseInt(document.getElementById("audio_element_id").getAttribute("curSongIndex")); var i = songs[curSongIndex]; var s = "mw-harlem_shake_me"; var o = "im_first"; var u = ["im_drunk", "im_baked", "im_trippin", "im_blown"]; var a = "mw-strobe_light"; /* harlem-shake-style.css,替换成你的位置,也可以直接使用://s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css */ var f = "//s3.amazonaws.com/moovweb-marketing/playground/harlem-shake-style.css"; var l = "mw_added_css"; var b = g(); var w = y(); var C = document.getElementsByTagName("*"); var k = null; for (var L = 0; L < C.length; L++) { var A = C[L]; if (v(A)) { if (E(A)) { k = A; break } } } if (A === null) { console.warn("Could not find a node of the right size. Please try a different page."); return } c(); S(); var O = []; for (var L = 0; L < C.length; L++) { var A = C[L]; if (v(A)) { O.push(A) } } })()"> High一下

close

【翻译】敏捷管理过程框架Scrum

发表于 2019-12-14   |   分类于 敏捷

【翻译】敏捷管理过程框架Scrum

文章翻译自Mike Cohn的博客

Scrum是一种敏捷的项目管理方式,通常用于软件开发项目。使用Scrum进行敏捷软件开发通常被视为一种方法论;但与其将Scrum视为方法论,不如将其视为管理过程的框架。

什么是Scrum?

在敏捷Scrum世界中,与其提供关于如何在项目中完成所有事情的完整、详细的描述,还不如将其中的大部分留给Scrum软件开发团队。这是因为团队将最清楚如何解决他们提出的问题。

这就是为什么在Scrum开发中,例如,Sprint计划会议以期望的结果(对下一个冲刺中要开发的一组特征的承诺)来代替,而不是在大多数方法学中提供的一组入口标准、任务定义、验证标准、退出标准(ETVX文章末尾有解释说明)等等。

Scrum依赖于一个自组织、跨职能的团队。scrum团队是自组织的,因为没有一个整体的团队领导来决定哪个人将执行哪个任务或者如何解决一个问题。这些问题是由整个团队决定的。

在Scrum中,团队是跨职能的,这意味着每个人都需要从一个想法到实现一个特性。

在敏捷开发中,Scrum团队由两个特定的角色支持。第一个是ScrumMaster,他可以被认为是团队的教练,帮助团队成员使用Scrum流程在最高级别上执行任务。

产品所有者(product owner,PO)在国内通常叫做产品经理,是Scrum软件开发中的另一个角色,代表业务、客户或用户,并指导团队构建正确的产品。

Scrum开发:涉及到什么?

Scrum模型建议项目通过一系列的sprint进行。根据敏捷方法,sprint的时间限制不超过一个月,通常是两周。

Scrum方法论提倡在sprint开始时召开计划会议,在会议上,团队成员计算出他们可以提交多少用户故事/User Storie,然后创建sprint backlog——sprint期间要执行的任务列表。

在敏捷Scrum冲刺过程中,Scrum团队从idea到编码和测试的功能都是特性/Features的一小部分。最后,这些特性完成了,意味着编码、测试并集成到不断发展的产品或系统中。

在sprint的每一天,所有团队成员都应该参加Scrum的站会,包括ScrumMaster和PO(产品经理)。会议时间限制在15分钟以内。在此期间,团队成员分享他们在前一天所做的工作,并将在这一天工作,并记录和核对过程遇到的问题障碍和风险。Scrum模型将每日站会视为一种同步团队成员sprint工作的方式。

在sprint结束时,团队进行sprint评审,在此期间,团队向PO或希望提供反馈以影响下一次sprint的任何其他干系人演示新功能。Scrum软件开发中的这个反馈循环可能会导致新交付功能的更改,也可能会导致修改或向产品backlog添加项。

Scrum项目管理的另一个活动是在每个sprint结束时的sprint回顾。整个团队都参加了这次会议,包括ScrumMaster和PO。回顾会是一个反思已结束的sprint并确定改进项的机会。

Scrum过程:主要工件

Scrum开发中的主要工件当然是产品本身。Scrum模型期望团队在每个Scrum sprint结束时将产品或系统带到一个潜在的可交付状态。

产品backlog是Scrum的另一个工件。这是要添加到产品中的功能的完整列表。PO优先处理积压工作,这样团队总是首先处理最有价值的特性。

使用Scrum方法创建产品backlog的最流行和最成功的方法是用用户故事填充它,用户故事是从用户或客户的角度描述功能的简短描述。

在Scrum项目管理中,在sprint的第一天和计划会议期间,团队成员创建sprint backlog。sprint backlog可以看作是团队的sprint待办事项列表,而产品backlog则是要构建的特性列表(以用户故事的形式编写)。

sprint backlog是团队需要执行的任务列表,显示在sprint期间交付的功能。

Scrum敏捷方法产生的其他工件是sprint burndown图表和release burndown图表。Burndown图表即燃尽图显示了sprint或release中剩余的工作量,是Scrum软件开发中的一个有效工具,用于确定sprint或release是否按计划在预期日期完成所有计划工作。

敏捷Scrum项目:主要角色

即使你是Scrum新手,你也可能听说过ScrumMaster这个角色。ScrumMaster是团队的教练,帮助Scrum实践者达到最高的绩效水平。

在Scrum过程中,ScrumMaster在许多方面与传统的项目经理不同,包括这个角色不向团队提供日常指导,也不向个人分配任务。

一个好的ScrumMaster可以保护团队不受外界干扰,让团队成员在冲刺过程中疯狂地专注于他们选择的目标。

当ScrumMaster专注于帮助团队尽其所能做到最好时,PO会努力引导团队达到正确的目标。PO通过创建一个令人信服的产品远景,然后通过产品backlog将该远景传达给团队来实现这一点。

PO(产品经理)负责对Scrum开发期间的待办列表的优先级进行排序,以保证系统的构成,被其用户、开发团队如期望的被了解。
上面一句话原文是这样的,感觉比较难翻译。The product owner is responsible for prioritizing the backlog during Scrum development, to ensure it’s up to par as more is learned about the system being built, its users, the team and so on.
我请教几位敏捷大神,给出的翻译如下,贴出来供大家参考。
在Scrum开发里,PO负责待办列表的优先级排序,以建立对系统,用户群体以及团队等问题的共识基础。

Scrum项目管理的第三个也是最后一个角色是Scrum团队本身。虽然在Scrum中,个人可能以不同的职称加入团队,但这些职称并不重要。Scrum方法论指出,每个人都尽其所能地为完成每个sprint的工作做出贡献。

这并不意味着测试人员将被期望重新构建系统;在采用敏捷Scrum模型之前,个人将把大部分(有时是全部)时间花在他们工作的任何规程上。但是,在Scrum中,人们希望个人能够超越他们喜欢的学科,只要这样做是为了团队的利益。

在敏捷方法论中,考虑这三个角色相互关联的一种方式是作为一辆赛车。

Scrum团队就是汽车本身,随时准备沿着它所指的任何方向前进。PO是司机,确保汽车总是朝着正确的方向行驶。ScrumMaster是主要的机械师,它能使汽车保持良好的调教和最佳性能。

ETVX补充说明

ETVX(Entry / Task / Verification / eXit)模式。
IBM公司于80年代提出一个结构化的描述架构-ETVX ( Entry / Task / Verification / eXit )模式来叙述整个软体发展的生命周期模式与每个阶段相对应的作业活动,让研读的人员能够非常清楚整个专案的软体发展架构。
ETVX模式可以说是以质量为基础的角度来建立流程和活动的模式,它将PDCA循环的思路直接融入到了软件项目计划和软件开发生命周期模型中。在CMMI的标准体系的建立中,针对高端流程图到各个PA域,都可以借鉴ETVX模式的方法来进行各种规程的建立。

【翻译】敏捷中的Scrum Master

发表于 2019-12-14   |   分类于 敏捷

公司在2018年年初开始推行敏捷,在公司高层领导的大力支持下我们开展了敏捷转型。我有幸参与和见证了公司敏捷转型的全过程。在我们的敏捷团队中我是Scrum Master的角色。为了能更好的胜任SM的角色,也为了能够更好的为公司敏捷转型尽一分力,我一直都在努力地做好本职工作并不断地学习敏捷知识。在这期间我曾多次问自己什么是Scrum Master,只有你知道什么是Scrum Master你才能更好的去成为Scrum Master。
那么,什么是Scrum Master?希望大家能在下面这篇翻文中找到答案。

一、【翻译】什么是Scrum Master。

下文翻译自Mike Cohn的博客

什么是Scrum Master?ScrumMaster负责确保Scrum团队遵循Scrum的价值观和实践。ScrumMaster通常被认为是团队的教练,帮助团队尽可能地做好工作。ScrumMaster也可以被看作是团队的过程所有者,它与项目的关键干系人product owner(称为产品所有者)建立了一种平衡。

ScrumMaster尽一切可能帮助团队在最高水平上表现。这包括消除任何阻碍进度的因素,促进会议的召开,以及与产品负责人合作以确保产品待办事项处于良好状态并为下一个sprint做好准备。ScrumMaster角色通常由前项目经理或技术团队领导担任,但可以是任何人。
ScrumMaster也经常被视为团队的保护者。最常见的例子是ScrumMaster通过确保团队不会由于来自过分激进的产品所有者的压力而过度承诺在sprint期间能够实现的目标来保护团队。然而,一个好的ScrumMaster也能保持团队不自满。

什么是Scrum Master角色,它是如何融入到项目中的?许多新加入ScrumMaster角色的人都在与ScrumMaster明显的矛盾斗争,ScrumMaster既是团队的服务型领导,又是没有权力的人。

当我们意识到尽管ScrumMaster无权控制Scrum团队成员,但ScrumMaster确实有权控制这个过程时,表面上的矛盾就消失了。尽管ScrumMaster可能不能说“你被解雇了”,但ScrumMaster可以说,“我决定下个月我们要尝试两周的冲刺。”

ScrumMaster帮助团队使用Scrum。把ScrumMaster的帮助想象成一个私人教练,他帮助你坚持锻炼方案,以正确的形式进行所有的锻炼。一个好的教练将提供动力,同时确保你不会通过跳过一个艰苦的锻炼来作弊。然而,教练的权限是有限的。

教练不能强迫你做你不想做的运动。相反,教练会提醒你你的目标以及你选择如何实现这些目标。如果教练有权,则由客户授予。scrummaster是一样的:他们有权限,但是这个权限是由团队授予的。

ScrumMaster可以对团队说,“听着,我们应该在每个sprint结束时交付潜在的可交付软件。我们这次没有那样做。我们能做些什么来确保我们在下一次冲刺中做得更好?“这是ScrumMaster对流程行使权限;如果团队未能交付可能交付的东西,那么流程就出了问题。

但是由于ScrumMaster的权限是在对流程流程的控制内,所以同一个ScrumMaster不应该说,“因为我们在最后一个sprint中未能交付一些可能可以交付的东西,所以我希望在提交所有代码之前要让托德(人名)进行review(检查)。”让托德review代码可能是一个好主意,但是这个决定不是ScrumMaster的权限。这样做超出了流程的权限,并干涉了团队的工作方式。

由于权限仅限于确保团队遵循流程,ScrumMaster的角色可能比典型项目经理的角色更难做。项目经理通常可以这么讲,那就是“就按我说的做”。而ScrumMaster可以这样说的场景是有限的,并且只限于确保Scrum得到遵循。

阅读更多关于ScrumMaster相关的信息。

Java建造者模式解读

发表于 2019-12-13   |   分类于 后台

Builder模式是代码编写过程中经常会用到的一类设计模式。最近重读了《Effective Java》和《设计模式》的builder章节,同时也读了其他人对于builder模式的理解和应用,在此记录我自己对Builder模式的一些理解。

一、意图

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

二、场景

1.当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
2.当构造过程必须允许被构造的对象有不同的表示时。

三、实例

遇到多个构造器参数时要考虑用构建器。静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。
考虑这样的一个场景:用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。
程序员一向习惯采用重叠构造器模式,在这种模式下,你提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推,最后一个构造器包含所有可选参数。重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然难以阅读。一长串类型相同的参数会导致一些微妙的错误。如果客户端不小心颠倒了其中两个参数的顺序,编译器也不会出错,但是程序在运行时会出现错误的行为。

下面是Builder模式的代码:

1
2
3
public interface IBuilder<T> {
public T build();
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

private NutritionFacts(final Builder builder) {
this.servingSize = builder.servingSize;
this.servings = builder.servings;
this.calories = builder.calories;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.carbohydrate = builder.carbohydrate;
}

@Override
public String toString() {
return "NutritionFacts{" + "servingSize=" + this.servingSize + ", servings=" + this.servings + ", calories="
+ this.calories + ", fat=" + this.fat + ", sodium=" + this.sodium + ", carbohydrate="
+ this.carbohydrate + '}';
}

public static class Builder implements IBuilder<NutritionFacts> {
// Required parameters
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public Builder(final int servingSize, final int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

public Builder calories(final int val) {
this.calories = val;
return this;
}

public Builder fat(final int val) {
this.fat = val;
return this;
}

public Builder carbohydrate(final int val) {
this.carbohydrate = val;
return this;
}

public Builder sodium(final int val) {
this.sodium = val;
return this;
}

@Override
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
}

NutritionFacts是不可变的。builder的setter方法返回builder本身,以便可以把调用链接起来。下面就是客户端代码:

1
2
3
4
5
6
7
8
9
10
11
public class BuilderDemo {
public static void main(final String[] args) {
final IBuilder<NutritionFacts> builder = new NutritionFacts.Builder(5, 3).calories(1000);
final NutritionFacts nutritionFacts = createNutritionFacts(builder);
System.out.println(nutritionFacts);
}

private static NutritionFacts createNutritionFacts(final IBuilder<NutritionFacts> builder) {
return builder.build();
}
}

这样的客户端代码很容易编写,更重要的是,易于阅读。

四、个人理解

builder模式要点

1.用于分步骤构建一个复杂的对象。builder提供几种固定的步骤接口和获取最终对象接口,这些步骤接口的调用次数、调用顺序由Director决定,最终通过获取最终对象接口得到最终产品。

2.支持多种构建算法。不同构件算法可以创建出不同的表现。Builder模式封装了构建算法,调用者无需知道具体的构建算法细节。

3.拥有稳定的部件类型及部件装配方式。builder接受的组件类型和组件装配方式稳定不变,构建算法和它的组件互不影响。

builder模式优点

1.将一个“复杂对象的构建算法”与它的“部件及组装方式”分离,使得构建算法可以独立应对变化;

2.不同构件算法可以复用相同的部件和部件组装方式;

3.不同构件算法,可以生成不同表现的对象;

4.“部件类型“及其”组装方式“相对稳定,不随着构建算法改变而改变。

总结

理解builder模式重点在两个词,“创建对象”和“一变一不变”。
“创建对象”指明builder模式的最终目的为创建对象,是大前提。“一变一不变”即构建算法多变,但是部件和部件装配方式稳定不变,既可以用于描述builder模式使用场景,也是builder模式的特点。
案例代码是可以运行的,自己敲一敲。至少要考下来放到ide中运行下,分析下来帮助你理解和吸收。

参考:
1.《大话设计模式》
2.《Effective Java 第二版》

敏捷团队建设之守破离

发表于 2019-12-12   |   分类于 敏捷

敬请期待

基于Vue、ElementUI的换肤解决方案

发表于 2019-07-01   |   分类于 前端

写在前面

换肤这个功能,不能算是很常见,但是也是有需求的,所以这里提供几种前端的换肤解决方案,供大家参考。

本文将介绍几种基于Vue、Element-UI的换肤实现方案,力争通俗易懂,易上手,希望大家喜欢~

阅读全文 »

【转】如何优雅处理前端异常

发表于 2019-05-01   |   分类于 前端

前端一直是距离用户最近的一层,随着产品的日益完善,我们会更加注重用户体验,而前端异常却如鲠在喉,甚是烦人。

一、为什么要处理异常?

异常是不可控的,会影响最终的呈现结果,但是我们有充分的理由去做这样的事情。

1.增强用户体验;
2.远程定位问题;
3.未雨绸缪,及早发现问题;
4.无法复线问题,尤其是移动端,机型,系统都是问题;
5.完善的前端方案,前端监控系统;

阅读全文 »

手把手教你 Vue 服务端渲染

发表于 2019-03-31   |   分类于 前端

序

在写这篇文章之前,我有写一篇 Vue 预渲染的教程 以及 在线示例,有需要的可以看一下~


【下面开始 Vue 服务端渲染】
阅读全文 »

使用 Travis CI 自动更新 GitHub Pages

发表于 2019-02-05   |   分类于 前端

Travis CI 提供的是持续集成服务(Continuous Integration,简称 CI)。我们在软件开发过程中,有构建、测试、部署这些必不可少的步骤,而这些会花掉我们很多的时间。为了提高软件开发的效率,现在涌现了很多自动化工具。Travis CI 是目前市场份额最大的一个,而且有很详细的文档以及可以和 Github 很好的对接。

阅读全文 »

什么是REST/RESTful API?

发表于 2019-01-17   |   分类于 后台

文章内容及目的(CONTENT & OBJECTIVE)

1.了解什么是REST
2.了解如何使用REST范式/风格设计各端

REST(REPRESENTATIONAL STATE TRANSFER)

REST(英文:Representational State Transfer)是一种无状态资源传输体系范式,用在服务端和客户端之间提供标准,从而使系统之间的通信更容易。与REST兼容的系统,通常称为RESTful系统,其特点是它们是无状态的,并且分离了客户端和服务端的关注点。我们将讨论这些术语的含义以及它们为什么是Web等服务实现前后端分离的有益特性。

客户端与服务端分离(SEPARATION OF CLIENT AND SERVER)

在REST架构范式中,客户端的实现和服务端的实现可以独立完成,而彼此不必知道对方。这意味着客户端的代码可以在不影响服务端操作的情况下随时更改,服务端的代码可以在不影响客户端操作的情况下更改。
只要双方都知道要向对方发送什么格式的消息,它们就可以保持模块化和分离。将用户界面关注点与数据存储关注点分开,通过简化服务端组件,我们改进了跨平台的接口的灵活性,并提高了可伸缩性。此外,分离允许每个组件独立地发展。

通过使用一个REST接口,不同的客户端访问相同的REST端点,执行相同的操作,并接收相同的响应。

无状态性(STATELESSNESS)

遵循REST范式的系统是无状态的,这意味着服务端不需要知道客户端处于什么状态,反之亦然。这样,服务端和客户端都可以理解接收到的任何消息,即使没有看到以前的消息。这种无状态限制是通过使用资源而不是命令来实现的。资源是网络中的名词-它们描述任何对象、文档或东西,您可能需要存储或发送到其他服务。

因为REST系统通过对资源的标准操作进行交互,所以它们不依赖于接口的实现。

这些约束有助于RESTful应用程序实现可靠性、快速性能和可伸缩性,因为这些组件可以在不影响整个系统的情况下进行管理、更新和重用,甚至在系统运行期间也是如此。

现在,我们将探讨在实现RESTful接口时,客户端和服务端之间的通信实际上是如何发生的。

客户端和服务端之间的通信(COMMUNICATION BETWEEN CLIENT AND SERVER)

在REST体系结构中,客户端发送请求以检索或修改资源,而服务端则向这些请求发送响应。让我们看看发出请求和发送响应的标准方法。

提交请求(MAKING REQUESTS)

REST要求客户端向服务端发出请求,以便检索或修改服务端上的数据。请求通常包括:

  • 一个HTTP动词,定义要执行的操作类型.
  • HTTP的头域,允许客户端传递有关请求的头信息。
  • 资源的路径
  • 包含数据的可选消息正文

1.HTTP动词(HTTP VERBS)

我们在请求中使用5个基本HTTP动词来与REST系统中的资源交互:

  • GET (SELECT):从服务端检索特定资源,或资源列表。
  • POST (CREATE):在服务端上创建一个新的资源。
  • PUT (UPDATE):更新服务端上的资源,提供整个资源。
  • PATCH (UPDATE):更新服务端上的资源,仅提供更改的属性。
  • DELETE (DELETE):从服务端删除资源。
    这里有两个较少知名的HTTP动词:
    • HEAD - 检索有关资源的元数据,例如数据的哈希或上次更新时间。
    • OPTIONS - 检索关于客户端被允许对资源做什么的信息。
    • 一个好的RESTful API将使用四个半HTTP动词,允许第三方与其数据进行交互,并且不会将动作/动词作为URL段。

通常,GET请求可以被缓存(通常是!)在浏览器,例如将缓存请求头用于第二次用户的POST请求。 HEAD请求基本上是一个没有响应主体的GET,并且也可以被缓存。

2.头信息和接收参数(HEADERS AND ACCEPT PARAMETERS)

在请求头中,客户端发送它能够从服务端接收的内容类型。这称为Accept字段,它确保服务端不会发送客户端无法理解或处理的数据。
例如,包含HTML的文本文件将使用text/HTML类型指定。如果此文本文件包含CSS,则将其指定为text/CSS。通用文本文件将被表示为文本/纯文本。但是,这个默认值text/plain并不是一个全部捕获的值。如果客户端需要文本/css并接收到文本/plain,则它将无法识别内容。内容类型的选项是MIME类型。

MIME类型,MIME(Multipurpose Internet Mail Extensions,多功能因特网邮件扩展)原来是用来判断电子邮件附件的格式而设计的一个字符串,后来演变为网络文档,及企业网和Internet上的其他应用程序中的文件格式的规范。
MIME类型是由一个媒体类型和一个子类型组成。媒体类型和子类型用一个斜杠(/)分隔开,例如text/css,它会告诉浏览器文件是纯文本文件,也是一个CSS样式表。每一个媒体类型都表示一种文件类型,媒体类型及说明见下表。

其他类型和常用子类型:

  • image — image/png, image/jpeg, image/gif
  • audio — audio/wav, image/mpeg
  • video — video/mp4, video/ogg
  • application — application/json, application/pdf,
  • application/xml, application/octet-stream

例如,访问服务端上项目资源中id为23的资源的客户端可能会发送如下GET请求:

1
2
GET /articles/23
Accept: text/html, application/xhtml

在本例中,Accept header字段表示客户机将接受text/html或application/xhtml格式的内容。

3.资源路径(PATHS)

请求必须包含应对其执行操作的资源的路径。在restfulapi中,路径的设计应该帮助客户端知道发生了什么。

按照惯例,路径的第一部分应该是资源的复数形式。这使得嵌套路径易于阅读和理解。

像fashionboutique.com/customers/223/orders/12这样的路径在它指向什么方面是清楚的,即使您以前从未见过这种特定的路径,因为它是层次化的描述明确的。我们可以看到,对于id为223的客户,我们正在访问id为12的订单。

路径应包含定位所需资源的特定信息。当引用资源的列表或集合时,无需向fashionboutique.com/customers路径中的POST请求添加id,因为服务器将为新对象生成id。

如果我们试图访问一个资源,我们需要在路径上附加一个id。例如:GET fashionboutique.com/customers/:id-检索客户资源中具有指定id的项目。DELETE fashionboutique.com/customers/:id-删除客户资源中具有指定id的项。

发送响应(SENDING RESPONSES)

1.内容类型(CONTENT TYPES)

在服务端向客户端返回数据的情况下,服务端必须在响应的头中包含一个内容类型。此内容类型标题字段提醒客户端它在响应正文中发送的数据类型。这些内容类型是MIME类型,就像它们在请求头的accept字段中一样。服务端在响应中发回的内容类型应该是客户端在请求的accept字段中指定的选项之一。

例如,当客户端使用此GET请求访问项目资源中id为23的资源时:

1
2
GET /articles/23 HTTP/1.1
Accept: text/html, application/xhtml

1
2
HTTP/1.1 200 (OK)
Content-Type: text/html

这意味着请求的内容正以text/html的内容类型返回到响应体中,客户端表示可以接受。

2.响应状态码(RESPONSE CODES)

服务端的响应包含状态码,用于提醒客户端有关操作成功的信息。作为一名开发人员,您不需要知道每个状态码(其中有许多),但您应该知道最常见的状态代码以及如何使用它们:

  • 200 OK - [GET]
    客户端从服务器请求数据,服务器为它们找到它(等幂)
  • 201 CREATED - [POST / PUT / PATCH]
    客户端提供了服务器数据,并且服务器创建了一个资源
  • 204 无内容 - [删除]
    客户端要求服务器删除资源,并且服务器将其删除
  • 400 无效请求 - [POST / PUT / PATCH]
    客户端给服务器的数据不良,服务器没有做任何事情(幂等)
  • 404错误
    客户端引用了一个不存在的资源或集合,并且服务器什么也不做(幂等)
  • 500内部服务器错误
    服务器遇到错误,并且客户端不知道请求是否成功

3.响应状态码范围(RESPONSE CODES RANGE)

  • 1xx 范围保留用于底层HTTP的东西,你很可能永远也用不到。
  • 2xx 范围保留用于成功消息,尽可能确保您的服务器尽可能多地向客户端发送这些消息。
  • 3xx 范围保留用于重定向。大多数API不使用这些请求很多(不像SEO人使用它们那么频繁),然而,较新的超媒体风格API将更多地使用这些请求。
  • 4xx 范围保留用于响应客户端做出的错误,例如。他们提供不良数据或要求不存在的东西。这些请求应该是幂等的,而不是更改服务器的状态。
  • 5xx 范围的状态码是保留给服务器端错误用的。这些错误常常是从底层的函数抛出来的,甚至开发人员也通常没法处理,发送这类状态码的目的以确保客户端获得某种响应。当收到5xx响应时,客户端不可能知道服务器的状态,所以这类状态码是要尽可能的避免。

(EXAMPLES OF REQUESTS AND RESPONSES)请求和响应示例

假设我们有一个应用程序,允许您查看、创建、编辑和删除fashionboutique.com上的一家小型服装店的客户和订单。我们可以创建一个HTTP API,允许客户端执行以下功能:
如果我们想查看所有客户,请求如下:

1
2
GET http://fashionboutique.com/customers
Accept: application/json

可能的响应头如下所示:

1
2
Status Code: 200 (OK)
Content-type: application/json

然后是以application/json格式请求的客户数据。
通过提交据创建新客户:

1
2
3
4
5
6
7
8
POST http://fashionboutique.com/customers
Body:
{
“customer”: {
“name” = “Scylla Buss”
“email” = “scylla.buss@codecademy.org”
}
}

然后,服务器为该对象生成一个id,并将其返回给客户端,其响应头类似于:

1
2
201 (CREATED)
Content-type: application/json

要查看单个客户,我们需要指定该客户的id:

1
2
GET http://fashionboutique.com/customers/123
Accept: application/json

可能的响应头如下所示:

1
2
Status Code: 200 (OK)
Content-type: application/json

后面是application/json格式的id为23的客户资源的数据。
我们可以通过输入新数据来更新该客户:

1
2
3
4
5
6
7
8
PUT http://fashionboutique.com/customers/123
Body:
{
“customer”: {
“name” = “Scylla Buss”
“email” = “scyllabuss1@codecademy.com”
}
}

可能的响应头的状态代码为:200(OK),用于通知客户端id为123的项已被修改。
我们还可以通过指定该客户的id来删除该客户:

1
DELETE http://fashionboutique.com/customers/123

响应将有一个包含状态代码204(无内容)的头,通知客户端id为123的项已被删除,而正文中没有任何内容。

课后练习(PRACTICE WITH REST)

让我们想象一下,我们正在建立一个照片收集网站,要做一个API来跟踪不同用户、用户空间和用户空间的照片。此网站有index.html和style.css。每个用户都有用户名和密码。每张照片都有一个路径和一个所有者(即拍摄照片的用户)。每个空间都有名字和文件路径。您能否设计一个可以容纳以下内容的REST系统:

  • 存储用户、照片和路径。
  • 访问空间和访问某个空间的某些照片。首先给出:
    1.我们想提出什么样的要求
    2.服务器应该返回什么响应
    3.每个响应的内容类型应该是什么

1.可能的解决方案-模型(POSSIBLE SOLUTION - MODELS)

1
2
3
4
5
6
7
{
“user”: {
"id": <Integer>,
“username”: <String>,
“password”: <String>
}
}
1
2
3
4
5
6
7
{
“photo”: {
"id": <Integer>,
“venue_id”: <Integer>,
“author_id”: <Integer>
}
}
1
2
3
4
5
6
7
{
“venue”: {
"id": <Integer>,
“name”: <String>,
“address”: <String>
}
}

2.可能的解决方案-请求/响应(POSSIBLE SOLUTION - REQUESTS/RESPONSES)

GET方式请求(GET REQUESTS)

Request- GET /index.html Accept: text/html Response- 200 (OK) Content-type: text/html

Request- GET /style.css Accept: text/css Response- 200 (OK) Content-type: text/css

Request- GET /venues Accept:application/json Response- 200 (OK) Content-type: application/json

Request- GET /venues/:id Accept: application/json Response- 200 (OK) Content-type: application/json

Request- GET /venues/:id/photos/:id Accept: application/json Response- 200 (OK) Content-type: image/png

POST方式请求(POST REQUESTS)

Request- POST /users Response- 201 (CREATED) Content-type: application/json

Request- POST /venues Response- 201 (CREATED) Content-type: application/json

Request- POST /venues/:id/photos Response- 201 (CREATED) Content-type: application/json

PUT方式请求(PUT REQUESTS)

Request- PUT /users/:id Response- 200 (OK)

Request- PUT /venues/:id Response- 200 (OK)

Request- PUT /venues/:id/photos/:id Response- 200 (OK)

DELETE方式请求(DELETE REQUESTS)

Request- DELETE /venues/:id Response- 204 (NO CONTENT)

Request- DELETE /venues/:id/photos/:id Response- 204 (NO CONTENT)

一起讨论学习

欢迎您一起学习和留言讨论RESTFUL,让我们一起写出真正的RESTFUL API。

Java单例模式的最佳实现-单元素枚举实现单例

发表于 2018-12-16   |   分类于 后台

单例模式

单例模式(Singleton Pattern)是Java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
Singleton指仅仅被实例化一次的类,Singleton通常被用来代表那些本质上唯一的系统组件,比如窗口管理器或文件系统。使类成为Singleton会使得对它的测试变得十分困难,因为无法给Singleton替换模拟实现,除非它实现一个充当其类型的接口。

单例模式作用及优缺点

作用

单例模式的意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点。这样能解决一个被全局使用的类频繁地创建与销毁的问题,从而节省系统资源。

优点

1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理平台首页页面缓存)。
2.避免对资源的多重占用(比如写文件操作)。

缺点

1.没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
2.一般单例类的模拟实现比较困难,会使得对它的测试变得十分困难。

使用场景及注意事项

场景

1.要求生产唯一序列号。
2.WEB中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
3.创建的一个对象需要消耗的资源过多,比如 I/O。
4.数据库的连接等。

注意事项

1.getInstance()方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance被多次实例化。而单元素枚举实现单例的方法,可以避免序列化,和反射攻击导致的多次实例化的可能。这也是单元素枚举实现单例方法被视为单例实现的最佳方法的重要原因。

实现方法

1.单元素枚举实现单例

直接上代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class SingletonElvisDemo {
public static void main(final String[] args) {
final Thread[] threads = new Thread[50];
startThreads(threads);
joinThreads(threads);
}

private static void startThreads(final Thread[] threads) {
IntStream.range(0, threads.length).forEach(i -> {
threads[i] = createThread();
threads[i].start();
});
}

private static Thread createThread() {
final Thread thread = new Thread(() -> {
System.out.println("Got a singleton instance: " + Elvis.INSTANCE);
});

return thread;
}

private static void joinThreads(final Thread[] threads) {
Stream.of(threads).forEach(thread -> {
join(thread);
});
}

private static void join(final Thread thread) {
try {
thread.join();
} catch (final InterruptedException e) {
e.printStackTrace();
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
public enum Elvis {
INSTANCE;

public void sing() {
System.out.println("Singleton Elvis using enum: I am signing!");
}

@Override
public String toString() {
return this.getClass().getSimpleName() + "@" + hashCode();
}
}

在上述单元素枚举实现单例的例子中,在功能上与下面会介绍到的实现方式相同。但它更加简洁,无偿的提供了序列化机制,绝对防止多次实例化,即使是在复杂的序列化或者反射攻击的时候。反射攻击是指:单例的调用者借助AccessibleObejct.setAccessible方法,通过反射机制调用私有构造器。

2.懒汉式,线程不安全

是否 Lazy 初始化:是
是否多线程安全:否
实现难度:易
描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

1
2
3
4
5
6
7
8
9
10
11
public class Singleton {  
private static Singleton instance;
private Singleton (){}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

3.懒汉式,线程安全

是否 Lazy 初始化:是
是否多线程安全:是
实现难度:易
描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。

1
2
3
4
5
6
7
8
9
10
public class Singleton {  
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

4.饿汉式

是否 Lazy 初始化:否
是否多线程安全:是
实现难度:易
描述:这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。

1
2
3
4
5
6
7
public class Singleton {  
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}

5.登记式/静态内部类

是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。

1
2
3
4
5
6
7
8
9
public class Singleton {  
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}

经验之谈:一般情况下,不建议使用第2种和第3种懒汉方式,建议使用第4种饿汉方式。只有在要明确实现lazy loading效果时,才会使用第5种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第1种枚举方式。

123…6
dalaoyan

dalaoyan

你的行囊,是充实,还是丢弃?看你的!

52 日志
6 分类
19 标签
RSS
Github Blog
Creative Commons
友情链接
  • 淘宝优惠券app
  • 个人首页
  • 建站日志
您是第个小伙伴 本站总浏览次
© 2016 - 2021 dalaoyan
由 Hexo 强力驱动
主题 - NexT.Pisces
全站共 281k 字