状态模式全解析--JavaScript
1 初级电灯例子 , 状态仅仅用字符串表示,没有封装到对象
class Light{ constructor(){ this.state = 'off'; let button = document.createElement( 'button' ); button.innerHTML = '开关'; this.button = document.body.appendChild( button ); this.button.onclick = ()=>{ this.buttonWasPressed(); } } buttonWasPressed(){ if ( this.state === 'off' ){ console.log( '开灯' ); this.state = 'on'; }else if ( this.state === 'on' ){ console.log( '关灯' ); this.state = 'off'; } } } new Light();
// 状态模式----面向对象版本
实现的关键
1 状态用对象表示
2 状态对应的行为 抽象成一个统一的方法(buttonWasPressed),可以实现委托。这个行为可以放到状态类里,也可以放到context里,
3 状态内部 会自己修改当前的状态,(也就是下一个状态是什么,在每一个状态对象内部已经明确,)
4 当触发操作开始时,会用当前状态调用当前状态对应的行为,从而成功避免了使用丑陋的if else 分支,
1 class State{ 2 buttonWasPressed(){ 3 throw new Error( '父类的 buttonWasPressed 方法必须被重写' ) 4 } 5 } 6 7 class StrongLightState extends State{ 8 constructor(light){ 9 super(); 10 this.light = light 11 } 12 // buttonWasPressed(){ 13 // console.log('-->关灯'); 14 // this.light.setState(this.light.offLightState) 15 // } 16 } 17 18 class OffLightState extends State{ 19 constructor(light){ 20 super(); 21 this.light = light 22 } 23 buttonWasPressed(){ 24 console.log('-->弱光'); 25 this.light.setState(this.light.weakLightState) 26 } 27 } 28 29 class WeakLightState extends State{ 30 constructor(light){ 31 super(); 32 this.light = light 33 } 34 buttonWasPressed(){ 35 console.log('-->强光'); 36 this.light.setState(this.light.strongLightState) 37 } 38 } 39 40 class Light{ 41 constructor(){ 42 this.strongLightState = new StrongLightState(this); 43 this.offLightState = new OffLightState(this); 44 this.weakLightState = new WeakLightState(this); 45 this.currState = this.offLightState; // 设置当前状态 46 47 let button = document.createElement( 'button' ); 48 button.innerHTML = '开关'; 49 this.button = document.body.appendChild( button ); 50 this.button.onclick = ()=>{ 51 this.currState.buttonWasPressed(); 52 } 53 } 54 55 setState(newState){ 56 this.currState = newState 57 } 58 }
// 状态模式----闭包版本
1 var delegate = function (client, delegation) { 2 return { 3 buttonWasPressed: function () { // 将客户的操作委托给 delegation 对象 4 return delegation.buttonWasPressed.apply(client, arguments); 5 } 6 } 7 }; 8 var FSM = { 9 off: { 10 buttonWasPressed: function () { 11 console.log('关灯'); 12 this.button.innerHTML = '下一次按我是开灯'; 13 this.currState = this.onState; 14 } 15 }, 16 on: { 17 buttonWasPressed: function () { 18 console.log('开灯'); 19 this.button.innerHTML = '下一次按我是关灯'; 20 this.currState = this.offState; 21 } 22 } 23 }; 24 var Light = function () { 25 this.offState = delegate(this, FSM.off); 26 this.onState = delegate(this, FSM.on); 27 this.currState = this.offState; // 设置初始状态为关闭状态 28 this.button = null; 29 }; 30 Light.prototype.init = function () { 31 var button = document.createElement('button'), 32 self = this; 33 button.innerHTML = '已关灯'; 34 this.button = document.body.appendChild(button); 35 this.button.onclick = function () { 36 self.currState.buttonWasPressed(); 37 38 } 39 40 }; 41 42 var light = new Light(); 43 light.init();
//文件上传下载 初级版本
1 /** 2 * Created by shangkuikui on 2017/5/8. 3 */ 4 5 var plugin = (function () { 6 var plugin = document.createElement('embed'); 7 plugin.style.display = 'none'; 8 plugin.type = 'application/txftn-webkit'; 9 plugin.sign = function () { 10 console.log('开始文件扫描'); 11 } 12 plugin.pause = function () { 13 console.log('暂停文件上传'); 14 }; 15 plugin.uploading = function () { 16 console.log('开始文件上传'); 17 }; 18 plugin.del = function () { 19 console.log('删除文件上传'); 20 } 21 plugin.done = function () { 22 console.log('文件上传完成'); 23 } 24 document.body.appendChild(plugin); 25 return plugin; 26 })(); 27 28 class Upload { 29 constructor(fileName) { 30 this.plugin = plugin; 31 this.fileName = fileName; 32 this.state = 'sign'; // 设置初始状态为 waiting 33 var that = this; 34 this.dom = document.createElement('div'); 35 this.dom.innerHTML = 36 '<span>文件名称:' + this.fileName + '</span>\ 37 <button data-action="button1">扫描中</button>\ 38 <button data-action="button2">删除</button>'; 39 document.body.appendChild(this.dom); 40 this.button1 = this.dom.querySelector( '[data-action="button1"]' ); // 第一个按钮 41 this.button2 = this.dom.querySelector( '[data-action="button2"]' ); // 第二个按钮 42 this.bindEvent(); 43 } 44 bindEvent(){ 45 var self = this; 46 this.button1.onclick = function(){ 47 if ( self.state === 'sign' ){ // 扫描状态下,任何操作无效 48 console.log( '扫描中,点击无效...' ); 49 }else if ( self.state === 'uploading' ){ // 上传中,点击切换到暂停 50 self.changeState( 'pause' ); 51 }else if ( self.state === 'pause' ){ // 暂停中,点击切换到上传中 52 self.changeState( 'uploading' ); 53 }else if ( self.state === 'done' ){ 54 console.log( '文件已完成上传, 点击无效' ); 55 }else if ( self.state === 'error' ){ 56 console.log( '文件上传失败, 点击无效' ); 57 } 58 }; 59 this.button2.onclick = function(){ 60 if ( self.state === 'done' || self.state === 'error' 61 || self.state === 'pause' ){ 62 // 上传完成、上传失败和暂停状态下可以删除 63 self.changeState( 'del' ); 64 }else if ( self.state === 'sign' ){ 65 console.log( '文件正在扫描中,不能删除' ); 66 }else if ( self.state === 'uploading' ){ 67 console.log( '文件正在上传中,不能删除' ); 68 } 69 }; 70 } 71 changeState(state){ 72 switch( state ){ 73 case 'sign': 74 this.plugin.sign(); 75 this.button1.innerHTML = '扫描中,任何操作无效'; 76 break; 77 case 'uploading': 78 this.plugin.uploading(); 79 this.button1.innerHTML = '正在上传,点击暂停'; 80 break; 81 case 'pause': 82 this.plugin.pause(); 83 this.button1.innerHTML = '已暂停,点击继续上传'; 84 break; 85 case 'done': 86 this.plugin.done(); 87 this.button1.innerHTML = '上传完成'; 88 break; 89 case 'error': 90 this.button1.innerHTML = '上传失败'; 91 break; 92 case 'del': 93 this.plugin.del(); 94 this.dom.parentNode.removeChild( this.dom ); 95 console.log( '删除完成' ); 96 break; 97 } 98 this.state = state; 99 } 100 } 101 102 window.external.upload = function (state) { 103 uploadObj.changeState( state ); // 可能为 sign、 uploading、 done、 error 104 }; 105 var uploadObj = new Upload( 'JavaScript 设计模式与开发实践' ); 106 window.external.upload( 'sign' ); // 文件开始扫描 107 setTimeout(function(){ 108 window.external.upload( 'uploading' ); // 1 秒后开始上传 109 }, 1000 ); 110 setTimeout(function(){ 111 window.external.upload( 'done' ); // 5 秒后上传完成 112 }, 5000 );
//享元模式版本
1 /** 2 * Created by shangkuikui on 2017/5/8. 3 */ 4 let plugin = (function () { 5 let plugin = document.createElement('embed'); 6 plugin.style.display = 'none'; 7 plugin.type = 'application/txftn-webkit'; 8 plugin.sign = function () { 9 console.log('开始文件扫描'); 10 } 11 plugin.pause = function () { 12 console.log('暂停文件上传'); 13 }; 14 plugin.uploading = function () { 15 console.log('开始文件上传'); 16 }; 17 plugin.del = function () { 18 console.log('删除文件上传'); 19 } 20 plugin.done = function () { 21 console.log('文件上传完成'); 22 } 23 document.body.appendChild(plugin); 24 return plugin; 25 })(); 26 window.external.upload = function (state) { 27 uploadObj[state](); // 此处可以看到策略模式的影子 28 }; 29 30 var StateFactory = (function () { 31 var State = function () { 32 }; 33 State.prototype.clickHandler1 = function () { 34 throw new Error('子类必须重写父类的 clickHandler1 方法'); 35 } 36 State.prototype.clickHandler2 = function () { 37 throw new Error('子类必须重写父类的 clickHandler2 方法'); 38 } 39 return function (param) { 40 var F = function (uploadObj) { 41 this.uploadObj = uploadObj; 42 }; 43 F.prototype = new State(); 44 for (var i in param) { 45 F.prototype[i] = param[i]; 46 } 47 return F; 48 } 49 })(); 50 var SignState = StateFactory({ 51 clickHandler1: function () { 52 console.log('扫描中,点击无效...'); 53 }, 54 clickHandler2: function () { 55 console.log('文件正在上传中,不能删除'); 56 } 57 }); 58 var UploadingState = StateFactory({ 59 clickHandler1: function(){ 60 this.uploadObj.pause(); 61 }, 62 clickHandler2: function(){ 63 console.log( '文件正在上传中,不能删除' ); 64 } 65 }); 66 var PauseState = StateFactory({ 67 clickHandler1: function(){ 68 this.uploadObj.uploading(); 69 }, 70 clickHandler2: function(){ 71 this.uploadObj.del(); 72 } 73 }); 74 var DoneState = StateFactory({ 75 clickHandler1: function(){ 76 console.log( '文件已完成上传, 点击无效' ); 77 }, 78 clickHandler2: function(){ 79 this.uploadObj.del(); 80 } 81 }); 82 var ErrorState = StateFactory({ 83 clickHandler1: function(){ 84 console.log( '文件上传失败, 点击无效' ); 85 }, 86 clickHandler2: function(){ 87 this.uploadObj.del(); 88 } 89 }); 90 91 92 93 class Upload { 94 constructor(fileName) { 95 this.plugin = plugin; 96 this.fileName = fileName; 97 98 //此处是享元模式的优化点 99 this.signState = new SignState(this); 100 this.pauseState = new PauseState(this); 101 this.uploadingState = new UploadingState(this); 102 this.doneState = new DoneState(this); 103 this.errorState = new ErrorState(this); 104 this.currState = this.signState; // 设置当前状态 105 106 var that = this; 107 this.dom = document.createElement('div'); 108 this.dom.innerHTML = 109 '<span>文件名称:' + this.fileName + '</span>\ 110 <button data-action="button1">扫描中</button>\ 111 <button data-action="button2">删除</button>'; 112 document.body.appendChild(this.dom); 113 this.button1 = this.dom.querySelector('[data-action="button1"]'); // 第一个按钮 114 this.button2 = this.dom.querySelector('[data-action="button2"]'); // 第二个按钮 115 this.bindEvent(); 116 } 117 118 bindEvent() { 119 var self = this; 120 this.button1.onclick = () => { 121 this.currState.clickHandler1();//状态模式能够使用的关键地方,就是每一个状态对象都有一个相同方法,才可以成功实现委托 122 } 123 this.button2.onclick = () => { 124 this.currState.clickHandler2(); 125 } 126 } 127 128 setState(newState) { 129 this.currState = newState 130 } 131 132 sign() { 133 this.plugin.sign(); 134 this.currState = this.signState; 135 } 136 137 uploading() { 138 this.button1.innerHTML = '正在上传,点击暂停'; 139 this.plugin.uploading(); 140 this.currState = this.uploadingState; 141 } 142 143 pause() { 144 this.button1.innerHTML = '已暂停,点击继续上传'; 145 this.plugin.pause(); 146 this.currState = this.pauseState; 147 } 148 149 done() { 150 this.button1.innerHTML = '上传完成'; 151 this.plugin.done(); 152 this.currState = this.doneState; 153 }; 154 155 error() { 156 this.button1.innerHTML = '上传失败'; 157 this.currState = this.errorState; 158 }; 159 160 del() { 161 this.plugin.del(); 162 this.dom.parentNode.removeChild(this.dom); 163 }; 164 } 165 166 var uploadObj = new Upload('JavaScript 设计模式与开发实践'); 167 setTimeout(function () { 168 window.external.upload('uploading'); 169 }, 1000); 170 setTimeout(function () { 171 window.external.upload('done'); 172 }, 5000);
//最终版本加入享元模式的 状态模式版本
/** * Created by shangkuikui on 2017/5/10. */ let plugin = (function () { let plugin = document.createElement('embed'); plugin.style.display = 'none'; plugin.type = 'application/txftn-webkit'; plugin.sign = function () { console.log('开始文件扫描'); } plugin.pause = function () { console.log('暂停文件上传'); }; plugin.uploading = function () { console.log('开始文件上传'); }; plugin.del = function () { console.log('删除文件上传'); } plugin.done = function () { console.log('文件上传完成'); } document.body.appendChild(plugin); return plugin; })(); var StateFactory = (function () { var State = function () { }; State.prototype.clickHandler1 = function () { throw new Error('子类必须重写父类的 clickHandler1 方法'); } State.prototype.clickHandler2 = function () { throw new Error('子类必须重写父类的 clickHandler2 方法'); } return function (param) { var F = function (uploadObj) { this.uploadObj = uploadObj; }; F.prototype = new State(); for (var i in param) { F.prototype[i] = param[i]; } return F; } })(); var SignState = StateFactory({ clickHandler1: function () { console.log('扫描中,点击无效...'); }, clickHandler2: function () { console.log('文件正在上传中,不能删除'); } }); var UploadingState = StateFactory({ clickHandler1: function () { this.uploadObj.pause(); }, clickHandler2: function () { console.log('文件正在上传中,不能删除'); } }); var PauseState = StateFactory({ clickHandler1: function () { this.uploadObj.uploading(); }, clickHandler2: function () { this.uploadObj.del(); } }); var DoneState = StateFactory({ clickHandler1: function () { console.log('文件已完成上传, 点击无效'); }, clickHandler2: function () { this.uploadObj.del(); } }); var ErrorState = StateFactory({ clickHandler1: function () { console.log('文件上传失败, 点击无效'); }, clickHandler2: function () { this.uploadObj.del(); } }); let UploadFactory = (function () { let upload; return { create: function () { if (upload) { return upload; } return upload = new Upload(); } } })(); let UploadManager = (function () { let outerstate = {}; return { add(id, fileName){ let uploadObj = UploadFactory.create(); let dom = document.createElement('div'); dom.innerHTML = '<span>文件名称:' + fileName + '</span>\ <button data-action="button1">扫描中</button>\ <button data-action="button2">删除</button>'; document.body.appendChild(dom); let button1 = dom.querySelector('[data-action="button1"]'); // 第一个按钮 let button2 = dom.querySelector('[data-action="button2"]'); // 第二个按钮 button1.onclick = function () { uploadObj.clickhandler1(id); } button2.onclick = function () { uploadObj.clickhandler2(id); } outerstate[id] = { fileName: fileName, dom: dom, button1: button1, button2: button2 }; return uploadObj }, setExternalState: function (id, flyWeightObj) { let uploadData = outerstate[id]; for (let i in uploadData) { flyWeightObj[i] = uploadData[i]; } } } })() class Upload { constructor() { //this.fileName = fileName; //此处是享元模式的优化点 this.plugin = plugin; this.signState = new SignState(this); this.pauseState = new PauseState(this); this.uploadingState = new UploadingState(this); this.doneState = new DoneState(this); this.errorState = new ErrorState(this); this.currState = this.signState; // 设置当前状态 此处有问题 因为当上传多个文件时候 currentstate 不应该共享,而是每一个文件有自己的currentstate } clickhandler1(id) { UploadManager.setExternalState(id, this); this.currState.clickHandler1(); } clickhandler2(id) { UploadManager.setExternalState(id, this); this.currState.clickHandler2(); } setState(newState) { this.currState = newState } sign() { this.plugin.sign(); this.currState = this.signState; } uploading() { this.button1.innerHTML = '正在上传,点击暂停'; this.plugin.uploading(); this.currState = this.uploadingState; } pause() { this.button1.innerHTML = '已暂停,点击继续上传'; this.plugin.pause(); this.currState = this.pauseState; } done() { this.button1.innerHTML = '上传完成'; this.plugin.done(); this.currState = this.doneState; }; error() { this.button1.innerHTML = '上传失败'; this.currState = this.errorState; }; del() { this.plugin.del(); this.dom.parentNode.removeChild(this.dom); }; } /*var uploadObj = UploadManager.add(0, 'JavaScript 设计模式与开发实践'); window.external.upload = function (state) { uploadObj[state](); // 此处可以看到策略模式的影子 }; setTimeout(function () { UploadManager.setExternalState(0, uploadObj); window.external.upload('uploading'); }, 1000); setTimeout(function () { UploadManager.setExternalState(0, uploadObj); window.external.upload('done'); }, 5000);*/ //test let uploadObj; window.external.upload = function (state) { uploadObj[state](); // 此处可以看到策略模式的影子 }; let books = ['JavaScript 设计模式与开发实践','活着','母猪的产后处理','颈椎病治疗大全']; books.forEach(function (item,index) { uploadObj = UploadManager.add(index, item); setTimeout(function () { UploadManager.setExternalState(index, uploadObj); window.external.upload('uploading'); }, 1000*(index+1)); setTimeout(function () { UploadManager.setExternalState(index, uploadObj); window.external.upload('done'); }, 3000*(index+1)); })
//上面代码有问题 下面是正解
1 /** 2 * Created by shangkuikui on 2017/5/10. 3 */ 4 let plugin = (function () { 5 let plugin = document.createElement('embed'); 6 plugin.style.display = 'none'; 7 plugin.type = 'application/txftn-webkit'; 8 plugin.sign = function (id) { 9 console.log('开始文件扫描'); 10 } 11 plugin.pause = function (id) { 12 console.log(id+'号暂停文件上传'); 13 }; 14 plugin.uploading = function (id) { 15 console.log(id+'开始文件上传'); 16 }; 17 plugin.del = function (id) { 18 console.log('删除文件上传'); 19 } 20 plugin.done = function (id) { 21 console.log('文件上传完成'); 22 } 23 document.body.appendChild(plugin); 24 return plugin; 25 })(); 26 27 28 var StateFactory = (function () { 29 var State = function () { 30 }; 31 State.prototype.clickHandler1 = function () { 32 throw new Error('子类必须重写父类的 clickHandler1 方法'); 33 } 34 State.prototype.clickHandler2 = function () { 35 throw new Error('子类必须重写父类的 clickHandler2 方法'); 36 } 37 return function (param) { 38 var F = function (uploadObj) { 39 this.uploadObj = uploadObj; 40 }; 41 F.prototype = new State(); 42 for (var i in param) { 43 F.prototype[i] = param[i]; 44 } 45 return F; 46 } 47 })(); 48 var SignState = StateFactory({ 49 clickHandler1: function (id) { 50 console.log('扫描中,点击无效...'); 51 }, 52 clickHandler2: function (id) { 53 console.log('文件正在上传中,不能删除'); 54 } 55 }); 56 var UploadingState = StateFactory({ 57 clickHandler1: function (id) { 58 this.uploadObj.pause(id); 59 }, 60 clickHandler2: function (id) { 61 console.log('文件正在上传中,不能删除'); 62 } 63 }); 64 var PauseState = StateFactory({ 65 clickHandler1: function (id) { 66 this.uploadObj.uploading(id); 67 }, 68 clickHandler2: function (id) { 69 this.uploadObj.del(id); 70 } 71 }); 72 var DoneState = StateFactory({ 73 clickHandler1: function (id) { 74 console.log('文件已完成上传, 点击无效'); 75 }, 76 clickHandler2: function (id) { 77 this.uploadObj.del(id); 78 } 79 }); 80 var ErrorState = StateFactory({ 81 clickHandler1: function (id) { 82 console.log('文件上传失败, 点击无效'); 83 }, 84 clickHandler2: function (id) { 85 this.uploadObj.del(id); 86 } 87 }); 88 89 let UploadFactory = (function () { 90 let upload; 91 return { 92 create: function () { 93 if (upload) { 94 return upload; 95 } 96 return upload = new Upload(); 97 } 98 } 99 })(); 100 101 let UploadManager = (function () { 102 let outerstate = {}; 103 return { 104 add(id, fileName){ 105 let uploadObj = UploadFactory.create(); 106 let dom = document.createElement('div'); 107 dom.innerHTML = 108 '<span>文件名称:' + fileName + '</span>\ 109 <button data-action="button1">扫描中</button>\ 110 <button data-action="button2">删除</button>'; 111 document.body.appendChild(dom); 112 let button1 = dom.querySelector('[data-action="button1"]'); // 第一个按钮 113 let button2 = dom.querySelector('[data-action="button2"]'); // 第二个按钮 114 button1.onclick = function () { 115 uploadObj.clickhandler1(id); 116 } 117 button2.onclick = function () { 118 uploadObj.clickhandler2(id); 119 } 120 121 outerstate[id] = { 122 fileName: fileName, 123 dom: dom, 124 button1: button1, 125 button2: button2, 126 currState : uploadObj.signState // 设置当前状态 127 }; 128 return uploadObj 129 }, 130 setExternalState: function (id, flyWeightObj) { 131 let uploadData = outerstate[id]; 132 for (let i in uploadData) { 133 flyWeightObj[i] = uploadData[i]; 134 } 135 }, 136 changeExternalState:function (id, objstate) { 137 let uploadData = outerstate[id]; 138 uploadData.currState = objstate 139 } 140 } 141 })() 142 143 class Upload { 144 constructor() { 145 //this.fileName = fileName; 146 //此处是享元模式的优化点 147 this.plugin = plugin; 148 this.signState = new SignState(this); 149 this.pauseState = new PauseState(this); 150 this.uploadingState = new UploadingState(this); 151 this.doneState = new DoneState(this); 152 this.errorState = new ErrorState(this); 153 154 } 155 156 clickhandler1(id) { 157 UploadManager.setExternalState(id, this); 158 this.currState.clickHandler1(id); 159 } 160 161 clickhandler2(id) { 162 UploadManager.setExternalState(id, this); 163 this.currState.clickHandler2(id); 164 } 165 166 setState(newState) { 167 this.currState = newState 168 } 169 170 sign(id) { 171 this.plugin.sign(id); 172 this.currState = this.signState; 173 } 174 175 uploading(id) { 176 this.button1.innerHTML = '正在上传,点击暂停'; 177 this.plugin.uploading(id); 178 this.currState = this.uploadingState; 179 } 180 181 pause(id) { 182 this.button1.innerHTML = '已暂停,点击继续上传'; 183 this.plugin.pause(id); 184 this.currState = this.pauseState; 185 } 186 187 done(id) { 188 this.button1.innerHTML = '上传完成'; 189 this.plugin.done(id); 190 this.currState = this.doneState; 191 }; 192 193 error(id) { 194 this.button1.innerHTML = '上传失败'; 195 this.currState = this.errorState; 196 }; 197 198 del(id) { 199 this.plugin.del(id); 200 this.dom.parentNode.removeChild(this.dom); 201 }; 202 } 203 204 /*var uploadObj = UploadManager.add(0, 'JavaScript 设计模式与开发实践'); 205 window.external.upload = function (state) { 206 uploadObj[state](); // 此处可以看到策略模式的影子 207 }; 208 setTimeout(function () { 209 UploadManager.setExternalState(0, uploadObj); 210 window.external.upload('uploading'); 211 }, 1000); 212 setTimeout(function () { 213 UploadManager.setExternalState(0, uploadObj); 214 window.external.upload('done'); 215 }, 5000);*/ 216 217 //test 218 let uploadObj; 219 220 window.external.upload = function (state) { 221 uploadObj[state](); // 此处可以看到策略模式的影子 222 }; 223 224 let books = ['JavaScript 设计模式与开发实践','活着','母猪的产后处理','颈椎病治疗大全']; 225 books.forEach(function (item,index) { 226 uploadObj = UploadManager.add(index, item); 227 let timer1 = setTimeout(function () { 228 UploadManager.changeExternalState(index,uploadObj.uploadingState); 229 UploadManager.setExternalState(index, uploadObj); 230 window.external.upload('uploading'); 231 }, 1000*(index+1)); 232 let timer2 = setTimeout(function () { 233 UploadManager.changeExternalState(index,uploadObj.doneState); 234 UploadManager.setExternalState(index, uploadObj); 235 window.external.upload('done'); 236 }, 3000*(index+1)); 237 })
最后补一张 我(丑)精(陋)心绘(瞎)制(画)的示意图