【Careerbuilder 源码】【本港台源码】【图片展示源码】源码设计装饰
1.Sweet Home 3D 开放源码室内设计
2.çè±ä»£ç ç¼ç¨python(ç«ç°è±ä»£ç ç¼ç¨python)
3.请问谁有在线统计的源码源码提供一下,最好能帮设计像以下的设计效果:在此先谢谢了。 好像有JS和HTML网页,装饰我
4.淘宝店铺装修代码(在线等! )
5.2万多行MyBatis源码,源码你知道里面用了多少种设计模式吗?
6.Golang 设计模式之装饰器模式
Sweet Home 3D 开放源码室内设计
在实际购置家具前,设计Sweet Home 3D 能让你在虚拟环境中预先尝试各种室内设计,装饰Careerbuilder 源码包括家具布局和配色方案,源码正如 Seth Kenlon 所说。设计
Sweet Home 3D 是装饰一款开源的室内设计工具,采用GPLv2协议,源码专为专业级的设计视觉预览而设计。它让你能够轻松地绘制房屋平面图,装饰精确调整家具尺寸,源码甚至达到厘米级的设计精确度,只需简单拖拽操作,装饰无需复杂的数学计算。
无论是需要为远程工作添置办公设备,如实木书桌和椅子,还是在设计之初就考虑家具的尺寸与空间匹配,Sweet Home 3D 都能提供专业的帮助,避免了传统家居改造中的尴尬问题。
作为一款Java程序,Sweet Home 3D 可在多种操作系统上运行,包括Linux、Windows、MacOS和BSD。下载链接可以在官方网站上找到。初次使用时,确保测量好实际空间尺寸,因为精确的尺寸是设计成功的关键。
软件界面直观,例如,本港台源码通过菜单中的3D视图模式,你可以实时控制渲染内容,确保设计效率。创建房间时,无论是先画外部边界再细分,还是直接定义每个房间,Sweet Home 3D 都提供了灵活的选项。
家具添加、门窗布局和装饰细节的调整都十分便捷,你可以使用可视化工具精细操控。最后,Sweet Home 3D 还支持SVG和OBJ格式导出,让你的蓝图和3D模型能在其他设计软件中继续编辑。
总的来说,Sweet Home 3D 是一款实用且有趣的室内设计工具,无论你是装修新手还是专业设计师,都能在其中找到满足自己需求的功能,帮助你实现理想的家居梦想。
çè±ä»£ç ç¼ç¨python(ç«ç°è±ä»£ç ç¼ç¨python)
æ¾çè±ç代ç
#-*-coding:utf-8-*-importmath,random,timeimportthreadingimporttkinterastkimportreuuidFireworks=[]maxFireworks=8height,width=,classfirework(object):def__init__(self,color,speed,width,height):=uuid.uuid1()self.radius=random.randint(2,4)~4åç´ self.color=colorself.speed=speed.5-3.5ç§self.status=0ï¼status=0ï¼çç¸åï¼status=1ï¼å½statusæ¶ï¼çè±ççå½æç»æ¢self.nParticle=random.randint(,)self.center=[random.randint(0,width-1),random.randint(0,height-1)]self.oneParticle=[]ï¼%ç¶ææ¶ï¼self.rotTheta=random.uniform(0,2*math.pi)ï¼x=a*cos(theta),y=b*sin(theta)=[a,b]
pythonç«é ·çè±è¡¨ç½æºä»£ç æ¯å¤å°ï¼å¦å®æ¬æç¨åï¼ä½ ä¹è½ååºè¿æ ·ççè±ç§ã
å¦ä¸å¾ç¤ºï¼æ们è¿ééè¿è®©ç»é¢ä¸ä¸ä¸ªç²ååè£ä¸ºXæ°éçç²åæ¥æ¨¡æçç¸ææãç²åä¼åçï¼è¨èâï¼æææ¯å®ä»¬ä¼ä»¥æé移å¨ä¸ç¸äºä¹é´çè§åº¦ç¸çãè¿æ ·å°±è½è®©æ们以ä¸ä¸ªåå¤è¨èçååå½¢å¼æ¨¡æåºçè±ç»½æ¾çç»é¢ã
ç»è¿ä¸å®æ¶é´åï¼ç²åä¼è¿å ¥ï¼èªç±è½ä½âé¶æ®µï¼ä¹å°±æ¯ç±äºéåå ç´ å®ä»¬å¼å§å è½å°å°é¢ï¼ä»¿è¥ç»½æ¾åççççè±ã
åºæ¬ç¥è¯ï¼ç¨PythonåTkinter设计çè±ã
è¿éä¸åä¸è¡èææ°å¦ç¥è¯å ¨ä¸¢åºæ¥ï¼æ们边å代ç 边说ç论ãé¦å ï¼ç¡®ä¿ä½ å®è£ åå¯¼å ¥äºTkinterï¼å®æ¯Pythonçæ åGUIåºï¼å¹¿æ³åºç¨äºåç§åæ ·ç项ç®åç¨åºå¼åï¼å¨Pythonä¸ä½¿ç¨Tkinterå¯ä»¥å¿«éçå建GUIåºç¨ç¨åºã
importtkinterastk
fromPILimportImage,ImageTk
fromtimeimporttime,sleep
fromrandomimportchoice,uniform,randint
frommathimportsin,cos,radians
é¤äºTkinterä¹å¤ï¼ä¸ºäºè½è®©çé¢ææ¼äº®çèæ¯ï¼æ们ä¹å¯¼å ¥PILç¨äºå¾åå¤çï¼ä»¥åå¯¼å ¥å ¶å®ä¸äºå ï¼æ¯å¦timeï¼randomåmathãå®ä»¬è½è®©æ们æ´å®¹æçæ§å¶çè±ç²åçè¿å¨è½¨è¿¹ã
Tkinteråºç¨çåºæ¬è®¾ç½®å¦ä¸ï¼
root=tk.Tk()
为äºè½åå§åTkinterï¼æä»¬å¿ é¡»å建ä¸ä¸ªTk()æ ¹é¨ä»¶ï¼rootwidgetï¼ï¼å®æ¯ä¸ä¸ªçªå£ï¼å¸¦ææ é¢æ åç±çªå£ç®¡çå¨æä¾çå ¶å®è£ 饰ç©ãè¯¥æ ¹é¨ä»¶å¿ é¡»å¨æ们åå»ºå ¶å®å°é¨ä»¶ä¹åå°±å建å®æ¯ï¼èä¸åªè½æä¸ä¸ªæ ¹é¨ä»¶ã
w=tk.Label(root,text="HelloTkinter!")
è¿ä¸è¡ä»£ç å å«äºLabelé¨ä»¶ã该Labelè°ç¨ä¸ç第ä¸ä¸ªåæ°å°±æ¯ç¶çªå£çååï¼å³æ们è¿éç¨çï¼æ ¹âãå ³é®ååæ°ï¼textâæææ¾ç¤ºçæåå 容ãä½ ä¹å¯ä»¥è°ç¨å ¶å®å°é¨ä»¶ï¼Buttonï¼Canvasççã
w.pack()
root.mainloop()
æ¥ä¸æ¥çè¿ä¸¤è¡ä»£ç å¾éè¦ãè¿éçæå æ¹æ³æ¯åè¯Tkinterè°æ´çªå£å¤§å°ä»¥éåºæç¨çå°é¨ä»¶ãçªå£ç´å°æ们è¿å ¥Tkinteräºä»¶å¾ªç¯ï¼è¢«root.mainloop()è°ç¨æ¶æä¼åºç°ãå¨æä»¬å ³éçªå£åï¼èæ¬ä¼ä¸ç´å¨åçå¨äºä»¶å¾ªç¯ã
å°çè±ç»½æ¾è½¬è¯æ代ç
ç°å¨æ们设计ä¸ä¸ªå¯¹è±¡ï¼è¡¨ç¤ºçè±äºä»¶ä¸çæ¯ä¸ªç²åãæ¯ä¸ªç²åé½ä¼æä¸äºéè¦çå±æ§ï¼æ¯é äºå®çå¤è§å移å¨ç¶åµï¼å¤§å°ï¼é¢è²ï¼ä½ç½®ï¼é度ççã
跨年çè±ä»£ç ï½ç¨Pythonéä½ ä¸åºè·¨å¹´çè±ç§å·²ç»æ¥è¿å°¾å£°äºï¼å³å°å°æ¥ï¼æ¬ææ们ç¨Pythonéä½ ä¸åºè·¨å¹´çè±ç§ã
æ们ç¨å°çPython模åå æ¬ï¼tkinterãPILãtimeãrandomãmathï¼å¦æ第ä¸æ¹æ¨¡å没æè£ çè¯ï¼pipinstallä¸ä¸å³å¯ï¼ä¸é¢çä¸ä¸ä»£ç å®ç°ã
导åº
çè±é¢è²
å®ä¹çè±ç±»
çæ¾çè±
å¯å¨
çä¸ä¸ææï¼
年跨年çè±ä»£ç å¯å¤å¶
çè±ä»£ç å¦ä¸ï¼
packagelove;
importjava.applet.Applet;
importjava.awt.Color;
importjava.awt.Graphics;
importjava.net.URL;
importjava.util.Random;
çè±
@authorenjoy
@SuppressWarnings("serial")
publicclassQextendsAppletimplementsRunnable
publicintspeed,variability,Max_Number,Max_Energy,Max_Patch,
Max_Length,G;
publicStringsound;
privateintwidth,height;
privateThreadthread=null;
privateBeaClassDemobcd[];
publicvoidinit()
inti;
this.setSize(,);
width=getSize().width-1;
height=getSize().height-1;
speed=1;//çè±ç»½æ¾çé度
variability=;
Max_Number=;//å¯ååºçè±çæ大æ°ç®
Max_Energy=width+;
Max_Patch=;//æ大çæç¹æ°
Max_Length=;//æç¹çæ大è·ç¦»
G=;//åå°é¢å¼¯æ²çå度
bcd=newBeaClassDemo[Max_Number];
for(i=0;iMax_Number;i++)
bcd[i]=newBeaClassDemo(width,height,G);
}
publicvoidstart(){
if(thread==null){
thread=newThread(this);
thread.start();
}
}
@SuppressWarnings("deprecation")
publicvoidstop(){
if(thread!=null){
thread.stop();
thread=null;
}
}
@SuppressWarnings({ "unused","static-access"})
publicvoidrun(){
inti;
intE=(int)(Math.random()*Max_Energy*3/4)+Max_Energy/4+1;
intP=(int)(Math.random()*Max_Patch*3/4)//çè±çæç¹æ°
+Max_Patch/4+1;
intL=(int)(Math.random()*Max_Length*3/4)//çè±å¯åå°åºçè·ç¦»
+Max_Length/4+1;
longS=(long)(Math.random()*);
booleansleep;
Graphicsg=getGraphics();
URLu=null;
while(true){
try{
thread.sleep(/speed);
catch(InterruptedExceptionx){
sleep=true;
for(i=0;iMax_Number;i++)
sleep=sleepbcd[i].sleep;
if(sleepMath.random()*variability){
E=(int)(Math.random()*Max_Energy*3/4)+Max_Energy/4
+1;
P=(int)(Math.random()*Max_Patch*3/4)+Max_Patch/4
+1;
L=(int)(Math.random()*Max_Length*3/4)+Max_Length/4
+1;
S=(long)(Math.random()*);
for(i=0;iMax_Number;i++){
if(bcd[i].sleepMath.random()*Max_Number*L1)
bcd[i].init(E,P,L,S);
bcd[i].start();
bcd[i].show(g);
publicvoidpaint(Graphicsg)?
g.setColor(Color.black);
g.fillRect(0,0,width+1,height+1);
classBeaClassDemo
publicbooleansleep=true;
privateintenergy,patch,length,width,height,G,Xx,Xy,Ex[],Ey[],x,
y,Red,Blue,Green,t;
privateRandomrandom;
publicBeaClassDemo(inta,intb,intg)
width=a;
height=b;
G=g;
publicvoidinit(inte,intp,intl,longseed)?
inti;
energy=e;
patch=p;
length=l;
//å建ä¸ä¸ªå¸¦ç§åçéæºæ°çæå¨
random=newRandom(seed);
Ex=newint[patch];
Ey=newint[patch];
Red=(int)(random.nextDouble()*)+;
Blue=(int)(random.nextDouble()*)+;
Green=(int)(random.nextDouble()*)+;
Xx=(int)(Math.random()*width/2)+width/4;
Xy=(int)(Math.random()*height/2)+height/4;
for(i=0;ipatch;i++){
Ex[i]=(int)(Math.random()*energy)-energy/2;
Ey[i]=(int)(Math.random()*energy*7/8)-energy/8;
publicvoidstart
t=0;
sleep=false;
publicvoidshow(Graphicsg)
if(!sleep)?
if(tlength)
inti,c;
doubles;
Colorcolor;
c=(int)(random.nextDouble()*)-+Red;
if(c=0c)
Red=c;
c=(int)(random.nextDouble()*)-+Blue;
if(c=0c)
Blue=c;
c=(int)(random.nextDouble()*)-+Green;
if(c=0c)
Green=c;
color=newColor(Red,Blue,Green);
for(i=0;ipatch;i++)
s=(double)t/;
x=(int)(Ex[i]*s);
y=(int)(Ey[i]*s-G*s*s);
g.setColor(color);
g.drawLine(Xx+x,Xy-y,Xx+x,Xy-y);
if(t=length/2)
intj;
for(j=0;j2;j++)
s=(double)((t-length/2)*2+j)/;
x=(int)(Ex[i]*s);
y=(int)(Ey[i]*s-G*s*s);
g.setColor(Color.black);
g.drawLine(Xx+x,Xy-y,Xx+x,Xy-y);
常ç¨çç¼ç¨è¯è¨ã
ç¼ç¨è¯è¨ä¸ï¼Cè¯è¨
Cè¯è¨æ¯ä¸çä¸ææµè¡ã使ç¨æ广æ³çé«çº§ç¨åºè®¾è®¡è¯è¨ä¹ä¸ãå¨æä½ç³»ç»åç³»ç»ä½¿ç¨ç¨åºä»¥åéè¦å¯¹ç¡¬ä»¶è¿è¡æä½çåºåï¼ç¨Cè¯è¨ææ¾ä¼äºå ¶å®é«çº§è¯è¨ï¼è®¸å¤å¤§ååºç¨è½¯ä»¶é½æ¯ç¨Cè¯è¨ç¼åçã
ç¼ç¨è¯è¨äº:java
Javaæ¯ä¸ç§å¯ä»¥æ°å跨平å°åºç¨è½¯ä»¶çé¢å对象çç¨åºè®¾è®¡è¯è¨ï¼æ¯ç±SunMicrosystemså ¬å¸äºå¹´5ææ¨åºçJavaç¨åºè®¾è®¡è¯è¨åJavaå¹³å°ï¼å³JavaSE,JavaEE,JavaMEï¼çæ»ç§°ã
ç¼ç¨è¯è¨ä¸:c++
C++è¿ä¸ªè¯å¨ä¸å½å¤§éçç¨åºåååä¸é常被读åâCå å âï¼è西æ¹çç¨åºåé常读åâCplusplus","CPPâãå®æ¯ä¸ç§ä½¿ç¨é常广æ³ç计ç®æºç¼ç¨è¯è¨ãC++æ¯ä¸ç§éææ°æ®ç±»åæ£æ¥çãæ¯æå¤éç¼ç¨èå¼çéç¨ç¨åºè®¾è®¡è¯è¨ã
请问谁有在线统计的源码提供一下,最好能帮设计像以下的效果:在此先谢谢了。 好像有JS和HTML网页,我
这个功能我已经用javascript实现了,但是个人对CSS不太了解,做出来的外观不太像,如果看不懂源代码,联系我。<!DOCTYPE htmlPUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script >
$(document).ready(function(){
//$("div").focus(function(){
// $("div").animate({ center:'px'});
//});
function preconditioning(content){
content = content.replace(/\n/g,""); // 去掉换行符
//处理纯空格的字符串
if(content.replace(/[" "]/g,"").length==0){
//alert("it has spaces");
content="";
}
//alert(content.length);
return content;
}
function generateObjectArray(content){
//词组的边界,默认为[ 和 ]
var size = content.length;
var fi=0;
var ei=0;
var entity;
var entitis = new Array();
var i = 0;
//字符串最后面有空格时,会出现异常,具体原因不明
//alert(size);
//所以,应该去掉这些多余的图片展示源码空格
size = content.lastIndexOf("]")+1;
while(i<size){
fi = content.indexOf("[",ei);
ei = content.indexOf("]",ei+1);
i=ei+1;
//alert(str[i]);
entity = content.substring(fi+1,ei);
// alert("the entity is :"+entity);
// alert("the current index is :"+i);
entitis.push(entity);
}
// alert("数组中的结果"+entitis);
return entitis;
}
$("#count").click(function(){
var draftContent = $("#draftContent").val();
var objectCotnent = $("#objectContent").val();
var errormessage ;
draftContent = preconditioning(draftContent);
objectCotnent = preconditioning(objectCotnent);
if(draftContent.length==0||objectCotnent.length==0){
alert("你妹的,你什么都不写,我怎么测试?");
return;
}
//alert("待统计的名单为:"+draftContent);
//alert("统计对象为:"+objectCotnent);
var draftArray = generateObjectArray(draftContent);
var objectArray = generateObjectArray(objectCotnent);
var result ="the result is ";
//alert(draftArray);
//alert(objectArray);
for(x in objectArray){
var object = objectArray[x];
var num =0;
for(y in draftArray){
var draft = draftArray[y];
if(object==draft){
num = ++num;
}
}
result = result+"\n"+ num+"次 :"+ object;
}
//alert(result);
$("#resultContent").val(result);
});
$("#btn_countInput").click(function(){
$("#draftContent").val("");
});
$("#btn_PatterInput").click(function(){
$("#objectContent").val("");
});
$("#btn_result").click(function(){
$("#resultContent").val("");
});
});
</script>
<title>在线统计器</title>
</head>
<body>
<div class="border" >
<div id="title">
<h1 >在线统计器</h1>
</div>
<div id="functionArea">
<div>
<p><input type="button" id="btn_countInput" value="请输入草稿名单"/></p>
<textarea id="draftContent">
</textarea>
</div>
<div >
<p><input type="button" id="btn_PatterInput" value="请输入统计对象"/></p>
<textarea id="objectContent">
</textarea>
<input id="count" type="button" value="进行统计"></input>
</div>
<div id="result">
<p><input type="button" id="btn_result" value="统计结果"/></p>
<textarea id="resultContent">
</textarea>
</div>
<div id="prompt">
<p>注意事项
<ul>
<li>本统计器用于统计草稿中,对应统计对象出现的次数</li>
<li>草稿和统计对象词组,每个词组都应该用[]括起来</li>
<li>后期会提供多样的选择,例如,对于中文,用做词组边界</li>
</ul>
</p>
</div>
</div>
</div>
</body>
</html>
淘宝店铺装修代码(在线等! )
淘宝旺铺装修代码(在线等! )本文目录一览: 淘宝旺铺装修代码(在线等!) 下面是淘宝店铺装修的一些代码: 插入代码:img src=链接地址 / 注:先把上传到网络相册网络地址,把它拷贝下来,放到下边一串代码里替代汉字部分;可以 应用于公告栏、分类栏及宝贝描述内。
淘宝店铺装修代码用什么软件做的淘宝美工助手淘宝店铺装修代码进入卖家中心-店铺装修-PC端找到要装修的首页点击“装修页面”,在左侧“自定义区”拖到右侧后点击“编辑”进行自定义描述内容后点发布生效,正确代码不需要修改。 (1)进入装修网,选择你喜欢的淘宝装修模板,并点击下载。(2)打开下载的压缩文件,解压找到txt文档,可以看到文档中的装修代码。(3)打开卖家中心---店铺装修,进入网店首页装修页面 (4)根据下载的模板进行布局设置并保存。 我们首先在网页上下载一个DW的软件,并且收集一些好看的,当然也可以用美图秀秀做一些图。这样会比较方便,美观。然后我们再去下载一些淘宝店铺的代码,可以直接去网上搜索。 淘宝店铺装修代码用什么软件做的 淘宝美工助手 淘宝美工助手是一款专门为在淘宝美工人员准备的一款辅助工具,软件we用户提供了一些常用的功能与大量优秀的特效代码,并且软件还自带了代码采集和代码预览功能。电子杂志源码 最主要用到的是photoshop的软件,其它的AI类软件相对用的少,电商网店设计都是位图设计,然后Dreamweaver这个网页编辑软件,要多少会一些。因为有时候一些页面需要用到代码。当然了,Illustrator、CorelDRAW这些都要会用。 我们首先在网页上下载一个DW的软件,并且收集一些好看的,当然也可以用美图秀秀做一些图。这样会比较方便,美观。然后我们再去下载一些淘宝店铺的代码,可以直接去网上搜索。 有淘宝装修自定义内容区的全屏代码吗。谢谢了 地址代码 问题四:淘宝店,自定义页面海报可以全屏吗? 可以的 只需要把代码写好了放入自定的代码里就OK 问题五:有淘宝装修自定义内容区的全屏代码吗。 回到装修后台,点击店招右上角的编辑,如下图所示。招牌类型选择自定义招牌,点击切换到源码,把复制的主店招的代码粘贴进去,高度设置为px,点击保存,如下图所示。 店铺首页全屏海报装修须要买店铺模板才行的,有些模板有全屏海报功能,有的抢票软件源码没。 看你的意思就是做了个全屏背景,想要把有内容的模块往下撑,使背景露出来是么?如果是这样的话,那非常简单,只要在最上方添加一个自定义区,切换到编辑源代码模式,然后填入 ,然后点确定就可以了。希望能帮到你。 装修=》样式管理=》背景设置=》页面设置=》更换背景图 保存。 淘宝的代码怎么写
卖家编码搜索宝贝1、里都可以通过此商家编码搜索宝贝,卖家出货时只需查看这个商家编码就能够迅速查找到这个商品存放的准确位置,节省人力和时间。商家编码是方便卖家管理自己的商品,可以用数字或字母显示,不强制要求卖主填写。
2、用dw做,做好以后放到自定义模块里就可以了,需要更详细的吗。这种是自己写的代码。
3、哎,这个告诉你,你用谷歌浏览器,登陆这个界面。
4、有个技巧,你大概懂了后,去别人的店 看到好的模板,在网页点右键,查看原代码。
设置direction的参数5、请设置direction的参数来设定活动字幕的滚动方向 down:向下滚动;left:向左滚动;right:向右滚动;up:向下滚动。
土巴兔在线免费为大家提供“各家装修报价、1-4家本地装修公司、3套装修设计方案”,还有装修避坑攻略!点击此链接:/yezhu/zxbj-cszy.php?to8to_from=seo_zhidao_m_jiare&wb,就能免费领取哦~2万多行MyBatis源码,你知道里面用了多少种设计模式吗?
在MyBatis的两万多行的框架源码中,设计模式的巧妙使用是整个框架的精华。
MyBatis中主要使用了以下设计模式:工厂模式、单例模式、建造者模式、适配器模式、代理模式、组合模式、装饰器模式、模板模式、策略模式和迭代器模式。
具体来说,工厂模式用于SqlSessionFactory的创建,单例模式用于Configuration的管理,建造者模式用于ResultMap的构建,适配器模式用于统一日志接口,代理模式用于MapperProxy的实现,组合模式用于SQL标签的组合,装饰器模式用于二级缓存操作,模板模式用于定义SQL执行流程,策略模式用于多类型处理器的实现,迭代器模式用于字段解析的实现。
通过运用这些设计模式,MyBatis成功地实现了复杂场景的解耦,并将问题合理切割为若干子问题,以提高理解和解决的效率。
总的来说,MyBatis大约运用了种左右的设计模式,这使得框架在处理复杂问题时能够更加高效和灵活。
学习源码不仅可以帮助我们更好地理解设计模式和设计原则,更能够扩展我们的编码思维,积累实际应用的经验。
希望本文的分享能够帮助到您,同时也推荐您阅读《手写MyBatis:渐进式源码实践》一书,了解更多关于MyBatis的知识。
Golang 设计模式之装饰器模式
本期和大家交流的是设计模式中的装饰器模式。
装饰器模式的基本定义是:在不变更原对象结构的基础上,动态地为对象增加附属能力。它与“继承”有一定的相似之处,但侧重点不同,可以将装饰器模式视为“继承”的一种补充手段。
为了更好地理解装饰器模式,以下是一个实际案例的分析:
通过编程的方式,我们可以还原上述场景问题。一种常见的实现方式是采用继承。然而,这种实现方式需要对子类的等级和种类进行枚举,包括一系列一级和二级子类。这种固定的等级架构存在一些问题。
因此,在这种“加料”场景中,使用继承的设计模式可能并不合适。我们可以改变思路,不再关注对所有组合种类的枚举,而是将注意力放在“加料”的过程中。
在这种实现思路下,就诞生了基于“装饰器模式”的实现架构。例如,一份鸡蛋培根盖浇饭可以由一份白米饭(核心类)加上一份鸡蛋(装饰器1)和一份培根(装饰器2)组成,其中鸡蛋和培根的装饰顺序不限制。这样,无论后续有多少种新的“菜品”加入,我们只需声明其对应的装饰器类即可。
比如,双份鸡蛋盖浇饭 = 一份白米饭(核心类)+ 一份鸡蛋(装饰器1)+一份鸡蛋(装饰器1);鸡蛋火腿青椒盖浇饭 = 一份白米饭(核心类)+ 一份鸡蛋(装饰器1)+一份青椒(装饰器2)+一份火腿(装饰器3);双份牛肉青椒盖浇饭 = 一份白米饭(核心类)+ 一份青椒(装饰器4)+一份牛肉(装饰器5)+一份牛肉(装饰器5)。
至此,问题得到了圆满解决。接下来,我们对装饰器模式和继承模式进行对比总结。
下面进入代码实战环节,通过编程实现一个搭配食材的案例,展示装饰器模式的实现细节。
这个案例非常简单,我们需要在主食的基础上添加配菜,最终搭配出美味可口的食物套餐。其中主食包括米饭 rice 和面条 noodle 两条,配菜包括老干妈 LaoGanMa(老干妈拌饭顶呱呱)、火腿肠 HamSausage 和煎蛋 FriedEgg 三类。
事实上,如果需要,主食和配菜也可以随时进行扩展。在装饰器模式中,这种扩展行为的成本并不高。
接下来,展示一下总体的 UML 类图。
首先是对应于装饰器模式中核心类的是原始的主食 Food,我们声明了一个 interface,其中包含两个核心方法,Eat 和 Cost,含义分别为食用主食以及计算出主食对应的花费。
接下来是装饰器部分,我们声明了一个 Decorate interface,它们本身是在强依附于核心类(主食)的基础上产生的,只能起到锦上添花的作用,因此在构造器函数中,需要传入对应的主食 Food。
接下来分别声明三个装饰器的具体实现类,对应为老干妈 LaoGanMaDecorator、火腿肠 HamSausageDecorator 和煎蛋 FriedEggDecorator。
每个装饰器类的作用是对食物进行一轮装饰增强,因此需要在构造器函数中传入待装饰的食物,然后通过重写食物的 Eat 和 Cost 方法,实现对应的增强装饰效果。
下面提供另一种闭包实现装饰增强函数的实现示例,其实现也是遵循着装饰器模式的思路,但在形式上会更加简洁直观一些。
其中核心的处理方法 handleFunc 对应的是装饰器模式中的核心类,Decorate 增强方法对应的则是装饰器类,每次在执行 Decorate 的过程中,都会在 handleFunc 前后增加一些额外的附属逻辑。
为了加深理解,以下摘出一个实际项目中应用到装饰器模式的使用案例进行分析。
这里给到的案例是 grpc-go 中对拦截器链 chainUnaryInterceptors 的实现。
在 grpc-go 服务端模块中,每次接收到来自客户端的 grpc 请求,会根据请求的 path 映射到对应的 service 和 handler 进行执行逻辑的处理,但在真正调用 handler 之前,会先经历一轮对拦截器链 chainUnaryInterceptors 的遍历调用。
下面我们来观察一下其中具体的源码细节。
首先,对于拦截器类 UnaryServerInterceptor,本身是一个函数的类型。
下面是生成拦截器链的方法 chainUnaryInterceptors。该方法入参是用户定义好的一系列拦截器 interceptors,内部会按照顺序对拦截器进行组装,最终通过层层装饰增强的方式,将整个执行链路压缩成一个拦截器 UnaryServerInterceptor 的形式进行方法。
在这个过程中,就体现了我们今天讨论的装饰器模式的设计思路。核心业务处理方法 handler 对应的就是装饰器模式中的核心类,每一轮通过拦截器 UnaryServerInterceptor 对 handler 进行增强的过程,对应的就是一次“装饰”的步骤。
下面给出一个具体实现的装饰器的代码示例,可以看到其中在核心方法 handler 前后分别执行了对应的附属逻辑,起到了装饰的效果。
如果各位读友们想了解更多关于 grpc-go 的内容,可以阅读我之前发表的相关话题文章。
本期和大家交流了设计模式中的装饰器模式。装饰器模式能够动态地为对象增加某种特定的附属能力,相比于继承模式显得更加灵活,且符合开闭原则,可以作为继承模式的一种有效补充手段。