如何在代码层实现六种质量属性战术之易用性
除了特定需求特性在易用性方面的要求在场景分析中完成,软件在易用性方面有一些公共的设计约束或要求需要梳理,包括:
呈现风格要求:如采用网页的平铺式呈现风格,或传统的窗口式风格等。
呈现信息要求:确定呈现信息的风格,如左树右表方式或者其它的风格;页面刷新速率等。
使用操作要求:如采用导航式组织操作、提供功能快捷入口、操作响应延迟、操作步骤、操作集中还是分散等。
可扩展性要求:信息和和操作功能的组织和呈现,能够方便软件未来功能的扩展,例如要求支持UI可定制或可配置驱动页面呈现等。
易用性战术
运行时战术
一旦系统执行,就可以通过为用户提供关于系统正在做什么的反馈,以及用于提供发出基于易用性命令的能力来增强易用性。例如,在纠错或更高效的操作中,“取消”、“撤销”、“聚合”和“显示多个视图”均为用户提供支持。
1.维持任务的一个模型。这种情况下,所维持的模型是关于任务的信息。任务模型用于确定上下文,以使该系统了解用户试图做什么,并提供各种协助。例如,知道句子通常以大写字母开头能够使应用程序纠正该位置的小写字母。
2.维持用户的一个模型。维持的模型是关于用户的信息。它确定了用户对该系统的了解,用户在期望的响应时间方面的行为,以及特定于某个用户或某类用户的其他方面。例如,维持用户模型能够使系统以用户可以阅读页面的速度滚动页面。
3.维持系统的一个模型。所维持的模型就是关于系统的信息。它确定了期望的系统行为,以便为用户提供适当的反馈。系统模型反馈预测了诸如完成当前活动所需要时间的项目。
设计时战术
- 模型-视图-控制器
- 表示-抽象-控制
- Seeheim
- Arch/Slinky
易用性关注的是对用户来说完成某个期望任务的容易程度和系统所提供的用户支持的种类。可以将易用性分为如下几个方面:
-
学习系统的特性
如果用户不熟悉某个特定的系统或该系统的某一特定方面,那么,系统可以如何使学习任务变得更容易? -
有效地使用系统
系统如何能提高用户的操作效率? -
将错误的影响降到最低
系统怎样使用户所犯的错误造成的影响最小? -
使系统适应用户的需要
用户(或系统本身)可以如何使用户的任务变得更轻松? -
提高自信和满意度
系统可如何使用户确信采取了正确的行动?
举例:组件对外接口设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
FormBody formBody = new FormBody.Builder() .add( "pay_fee" , String.valueOf(product.getProductPrice())) .add( "product_name" , String.valueOf(product.getProductName())) .add( "access_token" , "" ) .build(); Request request = new Request.Builder() .post(formBody) .url(createOrderURL) .build(); OkHttpClient okHttpClient = new OkHttpClient(); Call call = okHttpClient.newCall(request); call.enqueue( null ); |
对外接口
OkHttpClient, Builder用于全局的请求配置和作为Call的工厂方法
Request,Builder, RequestBody用于构建请求数据
Call可执行的网络请求
底层接口执行网络请求,比如Dispather请求分发,Interceptor,Chain等类被上层接口封装,不暴露给client。
Glide的图片加载:
1
2
3
4
5
6
7
8
|
Glide.with( this ) .load(sampledPhotoPath) .asBitmap() .format(DecodeFormat.ALWAYS_ARGB_8888) .skipMemoryCache( true ) .transform( new FilterEffectBitmapTransformation( this , mFilterInfo, sampledPhotoPath)) .into(mBinding.appBeautyFilterMarkPhotoIv); |
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
|
( function ($) { 'use strict' ; var cache = { data: {}, count: 0, addData: function (key, data) { if (! this .data[key]) { this .data[key] = data; this .count++; } }, readData: function (key) { return this .data[key]; }, deleteDataByKey: function (key) { delete this .data[key]; this .count--; }, deleteDataByOrder: function (num) { var count = 0; for ( var p in this .data) { if (count >= num) { break ; } count++; this .deleteDataByKey(p); } } }; function Search($elem, options) { this .$elem = $elem; this .options = options; this .$form = this .$elem.find( '.search-form' ); this .$input = this .$elem.find( '.search-inputbox' ); this .$layer = this .$elem.find( '.search-layer' ); this .loaded = false ; this .$elem.on( 'click' , '.search-btn' , $.proxy( this .submit, this )); if ( this .options.autocomplete) { this .autocomplete(); } } Search.DEFAULTS = { autocomplete: false , url: 'https://suggest.taobao.com/sug?code=utf-8&_ksTS=1484204931352_18291&callback=jsonp18292&k=1&area=c2c&bucketid=6&q=' , css3: false , js: false , animation: 'fade' , getDataInterval: 200 }; Search.prototype.submit = function () { if ( this .getInputVal() === '' ) { return false ; } this .$form.submit(); }; Search.prototype.autocomplete = function () { var timer = null , self = this ; this .$input .on( 'input' , function () { if (self.options.getDataInterval) { clearTimeout(timer); timer = setTimeout( function () { self.getData(); }, self.options.getDataInterval); } else { self.getData(); } }) .on( 'focus' , $.proxy( this .showLayer, this )) .on( 'click' , function () { return false ; }); this .$layer.showHide( this .options); $(document).on( 'click' , $.proxy( this .hideLayer, this )); }; Search.prototype.getData = function () { var self = this ; var inputVal = this .getInputVal(); if (inputVal == '' ) return self.$elem.trigger( 'search-noData' ); if (cache.readData(inputVal)) return self.$elem.trigger( 'search-getData' ,[cache.readData(inputVal)]); if ( this .jqXHR) this .jqXHR.abort(); this .jqXHR = $.ajax({ url: this .options.url + inputVal, dataType: 'jsonp' }).done( function (data) { console.log(data); cache.addData(inputVal, data); console.log(cache.data); console.log(cache.count); self.$elem.trigger( 'search-getData' , [data]); }).fail( function () { self.$elem.trigger( 'search-noData' ); }).always( function () { self.jqXHR = null ; }); }; Search.prototype.showLayer = function () { if (! this .loaded) return ; // if (this.$layer.children().length === 0) return; this .$layer.showHide( 'show' ); }; Search.prototype.hideLayer = function () { this .$layer.showHide( 'hide' ); }; Search.prototype.getInputVal = function () { return $.trim( this .$input.val()); }; Search.prototype.setInputVal = function (val) { this .$input.val(removeHtmlTags(val)); function removeHtmlTags(str) { return str.replace(/<(?:[^> '"]|"[^"]*"|' [^ ']*' )*>/g, '' ); } }; Search.prototype.appendLayer = function (html) { this .$layer.html(html); this .loaded = !!html; }; $.fn.extend({ search: function (option, value) { return this .each( function () { var $ this = $( this ), search = $ this .data( 'search' ), options = $.extend({}, Search.DEFAULTS, $( this ).data(), typeof option === 'object' && option); if (!search) { $ this .data( 'search' , search = new Search($ this , options)); } if ( typeof search[option] === 'function' ) { search[option](value); } }); } }); })(jQuery); |