前言

pion是一个纯用golang写的开源webrtc项目,目前github上大部分webrtc开源都是使用c++写的。本人认为pion对于一个webrtc入门者来说golang的语法相对简单,你这样可以更好的关注webrtc协议。如果业务场景不是特别高,业务上只做sfu转发服务器的话,可以尝试用golang来做。
读这篇文章你需要稍微了解webrtc的协议。

例子

pion也有提供不少的例子,我这边写了一个我自认为最简单,只能接受一个流的例子。这个例子平铺直叙,走了一个完整的webrtc流程,需要配合前端https://jsfiddle.net/1jc4go7v/当然需要注意的是前端只有publish能用。

整体下来就是:

  1. 创建peerconnection,设置stun服务器
  2. 设置对端的sdp
  3. 创建自己协商的answer,设置answer
  4. 等待candidate收集完成,输出序列化后的sdp信息

这边就是一个最简单的接受流的例子。


package mainimport ("encoding/base64""encoding/json""fmt""github.com/pion/webrtc/v3"
)func main() {//创建pc, 并且指定stun服务器pc, err := webrtc.NewPeerConnection(webrtc.Configuration{ICEServers: []webrtc.ICEServer{{URLs: []string{"stun:stun.l.google.com:19302"},},},})if err != nil {panic(err.Error())}sdp := webrtc.SessionDescription{}//todo:这边需要你自己设置对端的sdp信息sdpStr := "eyJ0eXBlIjoib2ZmZXIiLCJzZHAiOiJ2PTBcclxubz0tIDE2OTQ4Nzk1NDc5OTk0ODE3MDYgMiBJTiBJUDQgMTI3LjAuMC4xXHJcbnM9LVxyXG50PTAgMFxyXG5hPWdyb3VwOkJVTkRMRSAwXHJcbmE9ZXh0bWFwLWFsbG93LW1peGVkXHJcbmE9bXNpZC1zZW1hbnRpYzogV01TIE1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSlxyXG5tPXZpZGVvIDUzMzUxIFVEUC9UTFMvUlRQL1NBVlBGIDk2IDk3IDk4IDk5IDEwMCAxMDEgMTAyIDEyMSAxMjcgMTIwIDEyNSAxMDcgMTA4IDEwOSAzNSAzNiAxMjQgMTE5IDEyMyAxMTggMTE0IDExNSAxMTZcclxuYz1JTiBJUDQgNDcuOTEuMjE2LjE5XHJcbmE9cnRjcDo5IElOIElQNCAwLjAuMC4wXHJcbmE9Y2FuZGlkYXRlOjIwOTQwMjg4ODIgMSB1ZHAgMjEyMjI2MDIyMyAxMC4xMDYuMjEyLjc5IDUzMzUxIHR5cCBob3N0IGdlbmVyYXRpb24gMCBuZXR3b3JrLWlkIDEgbmV0d29yay1jb3N0IDEwXHJcbmE9Y2FuZGlkYXRlOjg0NDM0MjQzNCAxIHRjcCAxNTE4MjgwNDQ3IDEwLjEwNi4yMTIuNzkgOSB0eXAgaG9zdCB0Y3B0eXBlIGFjdGl2ZSBnZW5lcmF0aW9uIDAgbmV0d29yay1pZCAxIG5ldHdvcmstY29zdCAxMFxyXG5hPWNhbmRpZGF0ZTo0MjU0NjIxNDE0IDEgdWRwIDE2ODYwNTI2MDcgNDcuOTEuMjE2LjE5IDUzMzUxIHR5cCBzcmZseCByYWRkciAxMC4xMDYuMjEyLjc5IHJwb3J0IDUzMzUxIGdlbmVyYXRpb24gMCBuZXR3b3JrLWlkIDEgbmV0d29yay1jb3N0IDEwXHJcbmE9aWNlLXVmcmFnOjAxTDJcclxuYT1pY2UtcHdkOjlVOEs4bVp0R0hka0UzK2tHcVl6ODZ4dVxyXG5hPWljZS1vcHRpb25zOnRyaWNrbGVcclxuYT1maW5nZXJwcmludDpzaGEtMjU2IEM1OjM0OjQ4OjlEOjU3OjlCOjhDOkU5OjhCOjI0OjMxOkEwOkIzOjFCOkI1OjJEOjMyOkY0OkJFOjE2OjgxOjhCOjJDOkIyOkFBOjRDOjI5OjI3OkIwOjQ4OjNCOjgxXHJcbmE9c2V0dXA6YWN0cGFzc1xyXG5hPW1pZDowXHJcbmE9ZXh0bWFwOjEgdXJuOmlldGY6cGFyYW1zOnJ0cC1oZHJleHQ6dG9mZnNldFxyXG5hPWV4dG1hcDoyIGh0dHA6Ly93d3cud2VicnRjLm9yZy9leHBlcmltZW50cy9ydHAtaGRyZXh0L2Ficy1zZW5kLXRpbWVcclxuYT1leHRtYXA6MyB1cm46M2dwcDp2aWRlby1vcmllbnRhdGlvblxyXG5hPWV4dG1hcDo0IGh0dHA6Ly93d3cuaWV0Zi5vcmcvaWQvZHJhZnQtaG9sbWVyLXJtY2F0LXRyYW5zcG9ydC13aWRlLWNjLWV4dGVuc2lvbnMtMDFcclxuYT1leHRtYXA6NSBodHRwOi8vd3d3LndlYnJ0Yy5vcmcvZXhwZXJpbWVudHMvcnRwLWhkcmV4dC9wbGF5b3V0LWRlbGF5XHJcbmE9ZXh0bWFwOjYgaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvdmlkZW8tY29udGVudC10eXBlXHJcbmE9ZXh0bWFwOjcgaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvdmlkZW8tdGltaW5nXHJcbmE9ZXh0bWFwOjggaHR0cDovL3d3dy53ZWJydGMub3JnL2V4cGVyaW1lbnRzL3J0cC1oZHJleHQvY29sb3Itc3BhY2VcclxuYT1leHRtYXA6OSB1cm46aWV0ZjpwYXJhbXM6cnRwLWhkcmV4dDpzZGVzOm1pZFxyXG5hPWV4dG1hcDoxMCB1cm46aWV0ZjpwYXJhbXM6cnRwLWhkcmV4dDpzZGVzOnJ0cC1zdHJlYW0taWRcclxuYT1leHRtYXA6MTEgdXJuOmlldGY6cGFyYW1zOnJ0cC1oZHJleHQ6c2RlczpyZXBhaXJlZC1ydHAtc3RyZWFtLWlkXHJcbmE9c2VuZHJlY3ZcclxuYT1tc2lkOk1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSiAzMTU2MTk3Ny04MzI3LTQ4MzUtYmNkYi05NTk0MGY0YTE5NWFcclxuYT1ydGNwLW11eFxyXG5hPXJ0Y3AtcnNpemVcclxuYT1ydHBtYXA6OTYgVlA4LzkwMDAwXHJcbmE9cnRjcC1mYjo5NiBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjk2IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6OTYgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6OTYgbmFja1xyXG5hPXJ0Y3AtZmI6OTYgbmFjayBwbGlcclxuYT1ydHBtYXA6OTcgcnR4LzkwMDAwXHJcbmE9Zm10cDo5NyBhcHQ9OTZcclxuYT1ydHBtYXA6OTggVlA5LzkwMDAwXHJcbmE9cnRjcC1mYjo5OCBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjk4IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6OTggY2NtIGZpclxyXG5hPXJ0Y3AtZmI6OTggbmFja1xyXG5hPXJ0Y3AtZmI6OTggbmFjayBwbGlcclxuYT1mbXRwOjk4IHByb2ZpbGUtaWQ9MFxyXG5hPXJ0cG1hcDo5OSBydHgvOTAwMDBcclxuYT1mbXRwOjk5IGFwdD05OFxyXG5hPXJ0cG1hcDoxMDAgVlA5LzkwMDAwXHJcbmE9cnRjcC1mYjoxMDAgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMDAgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMDAgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTAwIG5hY2tcclxuYT1ydGNwLWZiOjEwMCBuYWNrIHBsaVxyXG5hPWZtdHA6MTAwIHByb2ZpbGUtaWQ9MlxyXG5hPXJ0cG1hcDoxMDEgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMDEgYXB0PTEwMFxyXG5hPXJ0cG1hcDoxMDIgSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTAyIGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTAyIHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTAyIGNjbSBmaXJcclxuYT1ydGNwLWZiOjEwMiBuYWNrXHJcbmE9cnRjcC1mYjoxMDIgbmFjayBwbGlcclxuYT1mbXRwOjEwMiBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0xO3Byb2ZpbGUtbGV2ZWwtaWQ9NDIwMDFmXHJcbmE9cnRwbWFwOjEyMSBydHgvOTAwMDBcclxuYT1mbXRwOjEyMSBhcHQ9MTAyXHJcbmE9cnRwbWFwOjEyNyBIMjY0LzkwMDAwXHJcbmE9cnRjcC1mYjoxMjcgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMjcgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMjcgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTI3IG5hY2tcclxuYT1ydGNwLWZiOjEyNyBuYWNrIHBsaVxyXG5hPWZtdHA6MTI3IGxldmVsLWFzeW1tZXRyeS1hbGxvd2VkPTE7cGFja2V0aXphdGlvbi1tb2RlPTA7cHJvZmlsZS1sZXZlbC1pZD00MjAwMWZcclxuYT1ydHBtYXA6MTIwIHJ0eC85MDAwMFxyXG5hPWZtdHA6MTIwIGFwdD0xMjdcclxuYT1ydHBtYXA6MTI1IEgyNjQvOTAwMDBcclxuYT1ydGNwLWZiOjEyNSBnb29nLXJlbWJcclxuYT1ydGNwLWZiOjEyNSB0cmFuc3BvcnQtY2NcclxuYT1ydGNwLWZiOjEyNSBjY20gZmlyXHJcbmE9cnRjcC1mYjoxMjUgbmFja1xyXG5hPXJ0Y3AtZmI6MTI1IG5hY2sgcGxpXHJcbmE9Zm10cDoxMjUgbGV2ZWwtYXN5bW1ldHJ5LWFsbG93ZWQ9MTtwYWNrZXRpemF0aW9uLW1vZGU9MTtwcm9maWxlLWxldmVsLWlkPTQyZTAxZlxyXG5hPXJ0cG1hcDoxMDcgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMDcgYXB0PTEyNVxyXG5hPXJ0cG1hcDoxMDggSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTA4IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTA4IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTA4IGNjbSBmaXJcclxuYT1ydGNwLWZiOjEwOCBuYWNrXHJcbmE9cnRjcC1mYjoxMDggbmFjayBwbGlcclxuYT1mbXRwOjEwOCBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0wO3Byb2ZpbGUtbGV2ZWwtaWQ9NDJlMDFmXHJcbmE9cnRwbWFwOjEwOSBydHgvOTAwMDBcclxuYT1mbXRwOjEwOSBhcHQ9MTA4XHJcbmE9cnRwbWFwOjM1IEFWMVgvOTAwMDBcclxuYT1ydGNwLWZiOjM1IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MzUgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjozNSBjY20gZmlyXHJcbmE9cnRjcC1mYjozNSBuYWNrXHJcbmE9cnRjcC1mYjozNSBuYWNrIHBsaVxyXG5hPXJ0cG1hcDozNiBydHgvOTAwMDBcclxuYT1mbXRwOjM2IGFwdD0zNVxyXG5hPXJ0cG1hcDoxMjQgSDI2NC85MDAwMFxyXG5hPXJ0Y3AtZmI6MTI0IGdvb2ctcmVtYlxyXG5hPXJ0Y3AtZmI6MTI0IHRyYW5zcG9ydC1jY1xyXG5hPXJ0Y3AtZmI6MTI0IGNjbSBmaXJcclxuYT1ydGNwLWZiOjEyNCBuYWNrXHJcbmE9cnRjcC1mYjoxMjQgbmFjayBwbGlcclxuYT1mbXRwOjEyNCBsZXZlbC1hc3ltbWV0cnktYWxsb3dlZD0xO3BhY2tldGl6YXRpb24tbW9kZT0xO3Byb2ZpbGUtbGV2ZWwtaWQ9NGQwMDMyXHJcbmE9cnRwbWFwOjExOSBydHgvOTAwMDBcclxuYT1mbXRwOjExOSBhcHQ9MTI0XHJcbmE9cnRwbWFwOjEyMyBIMjY0LzkwMDAwXHJcbmE9cnRjcC1mYjoxMjMgZ29vZy1yZW1iXHJcbmE9cnRjcC1mYjoxMjMgdHJhbnNwb3J0LWNjXHJcbmE9cnRjcC1mYjoxMjMgY2NtIGZpclxyXG5hPXJ0Y3AtZmI6MTIzIG5hY2tcclxuYT1ydGNwLWZiOjEyMyBuYWNrIHBsaVxyXG5hPWZtdHA6MTIzIGxldmVsLWFzeW1tZXRyeS1hbGxvd2VkPTE7cGFja2V0aXphdGlvbi1tb2RlPTE7cHJvZmlsZS1sZXZlbC1pZD02NDAwMzJcclxuYT1ydHBtYXA6MTE4IHJ0eC85MDAwMFxyXG5hPWZtdHA6MTE4IGFwdD0xMjNcclxuYT1ydHBtYXA6MTE0IHJlZC85MDAwMFxyXG5hPXJ0cG1hcDoxMTUgcnR4LzkwMDAwXHJcbmE9Zm10cDoxMTUgYXB0PTExNFxyXG5hPXJ0cG1hcDoxMTYgdWxwZmVjLzkwMDAwXHJcbmE9c3NyYy1ncm91cDpGSUQgMjY2MjI0MzgzNiAxMDM2NTM2NTlcclxuYT1zc3JjOjI2NjIyNDM4MzYgY25hbWU6STk3MEs2VDVSWGZHRkRxaFxyXG5hPXNzcmM6MjY2MjI0MzgzNiBtc2lkOk1PQ0xmd21HT1hwRG41dlB5R3lyZU5nclJ0Y2xQR2xCVmdjSiAzMTU2MTk3Ny04MzI3LTQ4MzUtYmNkYi05NTk0MGY0YTE5NWFcclxuYT1zc3JjOjI2NjIyNDM4MzYgbXNsYWJlbDpNT0NMZndtR09YcERuNXZQeUd5cmVOZ3JSdGNsUEdsQlZnY0pcclxuYT1zc3JjOjI2NjIyNDM4MzYgbGFiZWw6MzE1NjE5NzctODMyNy00ODM1LWJjZGItOTU5NDBmNGExOTVhXHJcbmE9c3NyYzoxMDM2NTM2NTkgY25hbWU6STk3MEs2VDVSWGZHRkRxaFxyXG5hPXNzcmM6MTAzNjUzNjU5IG1zaWQ6TU9DTGZ3bUdPWHBEbjV2UHlHeXJlTmdyUnRjbFBHbEJWZ2NKIDMxNTYxOTc3LTgzMjctNDgzNS1iY2RiLTk1OTQwZjRhMTk1YVxyXG5hPXNzcmM6MTAzNjUzNjU5IG1zbGFiZWw6TU9DTGZ3bUdPWHBEbjV2UHlHeXJlTmdyUnRjbFBHbEJWZ2NKXHJcbmE9c3NyYzoxMDM2NTM2NTkgbGFiZWw6MzE1NjE5NzctODMyNy00ODM1LWJjZGItOTU5NDBmNGExOTVhXHJcbiJ9"//前端页面会对sdp进行base64的encodeb, err := base64.StdEncoding.DecodeString(sdpStr)if err != nil {panic(err)}//反序列化为sdpif err = json.Unmarshal(b, &sdp); err != nil {panic(err.Error())}//设置远端的sdpif err = pc.SetRemoteDescription(sdp); err != nil {panic(err.Error())}//创建协商结果answer, err := pc.CreateAnswer(nil)if err != nil {panic(err.Error())}//设置自己的协商结果if err = pc.SetLocalDescription(answer); err != nil {panic(err)}//等待ice结束gatherCmp := webrtc.GatheringCompletePromise(pc)<-gatherCmp//将协商并且收集完candidate的answer,输出到控制台answerBytes, err := json.Marshal(*pc.LocalDescription())if err != nil {panic(err)}/*//这段代码可以省略,也可以放开。他可以清楚的让你知道你是否推流成功pc.OnTrack(func(remote *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {fmt.Println("one track = ", remote.PayloadType())rtpBuf := make([]byte, 1400)for {n, as, err := remote.Read(rtpBuf)fmt.Println("n = ", n, "   as = ", as, " err = ", err)}})*/fmt.Println(base64.StdEncoding.EncodeToString(answerBytes))select {}
}

流程

创建peerconnection

一开始我们需要创建一个peerconnection,这个会接受配置信息如下:

type Configuration struct {//stun或者turn服务器信息,为空只会有hostICEServers []ICEServer `json:"iceServers,omitempty"`//这个会规定//ICETransportPolicyAll 会收集所有的candidate,默认//ICETransportPolicyRelay 只会收集relay的candidateICETransportPolicy ICETransportPolicy `json:"iceTransportPolicy,omitempty"`//BundlePolicyBalanced 相当于“balanced“,这种情况下音频和视频只会建立两个传输的通道进行传输。如果对端不支持这种方式,那么会将各个轨道的音频分开。默认//BundlePolicyMaxCompat 相当于“max-compat”,这种情况下会将所有的媒体轨道分开不同的通道做传输,音频可能会有多条通道传输,视频可能一条。如果对端不支持blanced会回退到这种模式。//BundlePolicyMaxBundle 相当于“max-bundle”,这种情况下会将所有的媒体只放到一个通道传输。如果对端不支持单通道,可能会有丢失媒体轨道。BundlePolicy BundlePolicy `json:"bundlePolicy,omitempty"`//RTCPMuxPolicyNegotiate rtp和rtcp是否复用一个candidate,可以经过协商后得到。如果对端支持端口复用,那么就共用一个candidate。如果对端不支持一个candidate,那么就分开。默认。//RTCPMuxPolicyRequire rtp和rtcp必须要复用一个candidate,否则会失败RTCPMuxPolicy RTCPMuxPolicy `json:"rtcpMuxPolicy,omitempty"`//对端的身份标示,默认为空。PeerIdentity string `json:"peerIdentity,omitempty"`//DTLS建立时的证书,如果没有填,会自己生成。Certificates []Certificate `json:"certificates,omitempty"`//当指定大小会提前去收集size个candidate供你使用,就不用等到setLocalDescription()再去收集。默认是0。ICECandidatePoolSize uint8 `json:"iceCandidatePoolSize,omitempty"`//sdp交互的格式,以下3种值。//SDPSemanticsUnifiedPlan sdp的格式为unified plan //SDPSemanticsPlanB sdp格式为plan b//SDPSemanticsUnifiedPlanWithFallback 取决于offer的格式,如果unified plan会会unified plan,如果是plan b会回plan bSDPSemantics SDPSemantics `json:"sdpSemantics,omitempty"`
}

webrtc.NewPeerConnection函数会去初始化一些配置,做nack的处理等。

设置对端的sdp

sdp信息我们没有经过信令服务器做转发,做了一个最朴素的方法,直接写到代码里面,通过反序列化后直接SetRemoteDescription到pc中。pc会和默认支持的code做协商、设置remote Candidate、开始调用ICE的联通性检查等操作。但是值得注意的是,真正开始连通性检查时需要双端的candidate都有。

创建、设置answer

这个时候会根据协商的内容,创建不同的媒体流,并且在设置的时候会开始收集信息自己的candidate。

输出最终answer

在SetLocalDescription的时候会开始收集,gatherCmp管道会在收集完成的时候收到内容。当收集完成我们就可以将本地的Candidate发送到远端。当然现在已经有ticker ice了,并不需要全部收集完成再发送,我们这边为了简单用的还是比较旧的方式。当我们收集完Candidate,这个时候就可以将本地的sdp经过序列化发送给远端了,剩下的就是连通性检查,然后推流了。

pion最简单webrtc例子相关推荐

  1. 理解神经网络,从简单的例子开始(2)使用python建立多层神经网络

    这篇文章将讲解如何使用python建立多层神经网络.在阅读这篇文章之前,建议先阅读上一篇文章:理解神经网络,从简单的例子开始.讲解的是单层的神经网络.如果你已经阅读了上一篇文章,你会发现这篇文章的代码 ...

  2. 理解神经网络,从简单的例子开始(1)7行python代码构建神经网络

    理解神经网络,从简单的例子开始(1)7行python代码构建神经网络 前言 本文分为两个部分,第一个部分是一个简单的实例:9行Python代码搭建神经网络,这篇文章原文为:原文链接, 其中中文翻译版来 ...

  3. .net中的对象序列化(1): 序列化是什么, 以及一个简单的例子

    1. 为什么需要序列化,什么是序列化 对于一个程序来说, 使用到的对象都是存在于内存中的.如果想保存这些对象的运行时状态, 或者需要在不同进程或者网络间传递对象,就需要序列化. 序列化就是讲运行中的对 ...

  4. CSharpGL(1)从最简单的例子开始使用CSharpGL

    CSharpGL(1)从最简单的例子开始使用CSharpGL 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含20多个独立的Demo ...

  5. 一个简单的例子看java线程机制

    一个简单的例子看java线程机制 作者: zyf0808 发表日期: 2006-03-26 11:20 文章属性: 原创 复制链接 import java.util.*; public class T ...

  6. java ug二次开发_使用Java进行UG二次开发:简单的例子(上) | 学步园

    最近因为要进行TeamCenter二次开发(胖客户端定制),所有要开始研究Java了.因此突发奇想为什么不能使用Java进行二次开发呢?现在对UG进行的开发以C/C++应用为主.大多数使用的是原来的U ...

  7. blockchain 区块链200行代码:在JavaScript实现的一个简单的例子

    blockchain 区块链200行代码:在JavaScript实现的一个简单的例子 了解blockchain的概念很简单(区块链,交易链块):它是分布式的(即不是放置在同一台机器上,不同的网络设备上 ...

  8. pycharm安装scrapy失败_Scrapy ——环境搭配与一个简单的例子

    在我刚接触爬虫的时候就已经听过Scrapy大名了,据说是一个很厉害的爬虫框架,不过那个时候沉迷于Java爬虫.现在终于要来揭开它神秘的面纱了,来一起学习一下吧 欢迎关注公众号:老白和他的爬虫 1.环境 ...

  9. 用简单的例子说明提升可复用性的设计模式

    此文写给想要理解设计模式,但认为<设计模式>中所举的例子过于复杂的读者. 为了使程序具有更高的可复用性,前人推荐我们使用如下设计模式: 结构型模式:通过组合类和对象来获得更大的结构 1. ...

最新文章

  1. 实验4:正确的为各种属性赋值||实验7:通过abstract属性创建一个模板bean||实验8:bean之间的依赖||实验9:测试bean的作用域,分别创建单实例和多实例的bean★(测试)
  2. php受保护的成员方法,PHP继承和受保护的成员可见性
  3. 【linux】【tar】tar命令详解之czvf,xzvf
  4. Python3小知识
  5. [专栏精选]UnityWebRequest详解
  6. webform计算某几列结果_工业CAE案例实战精选|脱硫吸收塔工艺仿真计算系统
  7. jquery 下拉选择框/复选框常用操作
  8. ViewPager里面ImageView图片切换出现bug
  9. hive0.12安装
  10. RocketMQ源码-基于Netty的通信层设计
  11. 腾讯与新浪微博开放平台之比较
  12. arcgis地理空间数据库学习记录05-管理分布式数据库
  13. Qlikview常用VB脚本
  14. POJ 1359 Spacecraft Malfunction G++
  15. 鲸探发布点评:7月7日发售陈孟昕系列绘画数字藏品
  16. 谷歌浏览器设置打开新标签页为指定网页?
  17. 动物识别系统c语言编程,人工智能期末论文-简单动物识别系统的知识表示.doc
  18. python开发html_Python开发——16.HTML
  19. JavaScript 进阶篇的学习~
  20. 常见面试题整理:计算机网络篇,值得背诵收藏!

热门文章

  1. MathType批量修改公式字体和大小
  2. 服务器虚拟资源池,大型医院基于Hyper-V的虚拟化服务器资源池构建
  3. stm32之蓝牙无线超声波测距
  4. 开源多云应用平台 Choerodon猪齿鱼发布0.14版本
  5. IOS下localstorage不起作用提示
  6. ATA工厂测试AT_MODE下震动不振问题分析
  7. 静心解压,或许你需要一个电子木鱼
  8. 小米9es更新MIUI 11.0.3.0稳定版本,解决耗电问题
  9. 青柠开车Spring Cloud(六) —— Spring Cloud Gateway与zuul使用对比
  10. 报警器——基于arduino