一、基础概念

        桥接模式的本质是【分离抽象和实现】。

        桥接模式的定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。    

认识桥接模式
序号认识桥接模式说明
1什么是桥接通俗点说就是在不同的东西之间搭一个桥,让它们能够连接起来,可以相互通讯和使用。在桥接模式中是给什么东西搭桥呢?【是为被分离了的抽象部分和实现部分来搭桥】
         注意:在桥接模式中桥接是单向的,也就是只能是抽象部分的对象去使用具体实现部分的对象,而不能反过来,这就是单向桥。
2为何需要桥接为了达到让抽象部分和实现部分都可以独立变化的目的,在桥接模式中,是把抽象部分和实现部分分离开来。
        虽然从程序结构上是分开了,但是抽象部分实现的时候,还是需要使用具体的实现,这可怎么办?【抽象部分如何才能调用到具体实现部分的功能呢?】搭个桥就可以了,让抽象部分通过这个桥就可以调用到实现部分的功能了,因此需要桥接。
3如何桥接只要让抽象部分拥有实现部分的接口对象,就桥接上了,在抽象部分即可通过这个接口来调用具体实现部分的功能(即:桥接在程序上体现了在抽象部分拥有实现部分的接口对象,维护桥接就是维护这个关系)。
4独立变化桥接模式的意图是使得抽象和实现可以独立变化,都可以分别扩充。也就是说抽象部分和实现部分是一种非常松散的关系。从某个角度来讲,抽象部分和实现部分是可以完全分开的,独立的,抽象部分不过是一个使用实现部分对外接口的程序罢了。
         如果这么看桥接模式的话,就类似于策略模式了。抽象部分需要根据某个策略,来选择真实的实现,也就是说桥接模式的抽象部分相当于策略模式的上下文,更原始的就直接类似于面向接口编程,通过接口分离的两个部分而已。但是别忘了,桥接模式的抽象部分,是可以继续扩展和变化的,而策略模式只有上下文,是不存在所谓抽象部分的。
        抽象和实现为什么还要组合在一起呢?原因是在抽象部分和实现部分还是存在内部联系的,抽象部分的实现通常是需要调用实现部分的功能来实现的。
5动态变换功能由于桥接模式中的抽象部分和实现部分是完全分离的,因此可以在运行时动态组合具体的真实实现,从而达到动态变换功能的目的。
        从另外一个角度看,抽象部分和实现部分没有固定的绑定关系,因此同一个真实实现可以被不同的抽象对象使用;反过来,同一个抽象也可以有多个不同的实现。
6退化的桥接模式如果接口仅有一个实现,那么就没有必要创建接口了,这是一种桥接模式退化的情况(即:抽象类和接口是一对一的关系,虽然如此,但还是要保持它们的分离状态,这样,它们才不会相互影响,才可以分别扩展)
7桥接模式和继承继承是扩展对象功能的一种常见手段,通常情况下,继承扩展的功能变化纬度都是一纬的,也就是变化的因素只有一类。
        对于出现变化因素有两类:也就是有两个变化纬度的情况,继承实现就会比较痛苦。从理论上来说,如果用继承的方式来实现这种有两个变化纬度的情况,最后实际的实现类应该是两个维度上可变数量的乘积那么多个。如果要在任何一个纬度上进行扩展,都需要实现另外一个纬度上的可变数量那么多个实现类,这也是为何会感觉扩展起来很困难;且随着程序规模的加大,会越来越难以扩展和维护。
        【桥接模式】就是用来解决这种有两个变化纬度的情况下,如果灵活地扩展功能的一个很好的方案,其实,桥接模式主要是把继承改成了使用对象组合,从而把两个维度分开,让每一个纬度单独去变化,最后通过对象组合的方式,把两个维度组合起来,每一种组合的方式就相当于原来继承中的一种实现,这样就有效地减少了实际实现的类的个数【理论上,如果使用桥接模式的方式来实现这种有两种变化纬度的情况,最后实际的实现类应该是两个纬度上可变数量的和】。        
谁来桥接
序号谁来桥接说明
1由客户端来负责创建接口对象,并在创建抽象类对象的时候,把它设置到抽象部分的对象中去。
2可以在抽象部分对象构建的时候,由抽象部分的对象自己来创建相应的接口对象,也可以给它传递一些参数,根据参数来选择并创建具体的接口对象。
3可以在抽象类中选择并创建一个默认的接口对象,然后子类可以根据需要改变这个实现。
4也可以使用抽象工厂或者简单工厂来选择并创建具体的接口对象,抽象部分的类可以通过调用工厂的方法来获取接口对象。
5如果使用IOC/DI容器的话,还可以通过IOC/DI容器来创建具体的接口对象,并注入会到抽象类中。
桥接模式的优点
序号桥接模式的优点
1分离抽象和实现部分:桥接模式分离了抽象部分和实现部分,从而及大地提高了系统的灵活性。让抽象部分和实现部分独立开来,分别定义接口,这有助于对系统进行分层,从而产生更好的结构化的系统。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了。
2更好的扩展性:由于桥接模式把抽象部分和实现部分分离开了,而且分别定义接口,这就使得抽象部分和实现部分可以分别独立地扩展,而不会相互影响,从而大大提高了系统的可扩展性。
3可动态地切换实现:由于桥接模式把抽象部分和实现部分分离开了,所以在实现桥接的时候,就可以实现动态的选择和使用具体的实现。也就是说一个实现不再是固定的绑定在一个抽象接口上了,可以实现在运行期间动态地切换。
4可以减少子类的个数:对于两个变化纬度的情况,如果采用继承的实现方式,大约需要在两个纬度上的可变化数量的乘积个子类;而采用桥接模式来实现,大约需要两个纬度上的可变化数量的和个子类。可以明显的减少子类的个数。
思考桥接模式
序号说明
1桥接模式的本质是:分离抽象和实现;桥接模式最重要的工作就是分离抽象部分和实现部分,这是解决问题的关键。只有把抽象部分和实现部分分离开了,才能够让它们独立地变化;只有抽象部分和实现部分可以独立地变化,系统才会有更好的可扩展性和可维护性(还有其他好处如:可以动态地切换实现、可以减少子类个数等)。
2

对设计原则的体现:

        《1》桥接模式很好地实现了开闭原则(通常应用桥接模式的地方,抽象部分和实现部分都是可变化的,也就是应用会有两个变化纬度,桥接模式就是找到这两个变化,并分别封装起来,从而合理地实现OCP)。在使用桥接模式的时候,通常情况下,顶层的抽象类和接口是不变的,而继承抽象类的具体类是可变的。由于抽象类是通过接口来操作具体的实现类,因此具体的实现类是可以扩展的,并根据需要可以有多个具体的实现。
        《2》桥接模式还很好地体现了:多用对象组合,少用对象继承(如果使用继承来扩展功能,不但让对象之间有很强的耦合性,而且会需要很多的子类才能够完成相应的功能,需要两个纬度上的可变化数量的乘积个子类。而采用对象的组合,松散了对象之间的耦合性,不但使每个对象变得简单和可维护,还极大减少了子类的个数,大约要两个纬度上可变化数量的和个子类)。

何时选用桥接模式?
         1、如果不希望在抽象部分和实现部分采用固定的绑定关系,可以采用桥接模式,来把抽象部分和实现部分分开,然后在程序运行期间来动态地设置抽象部分需要用到的具体的实现,还可以动态地切换具体的实现。
         2、如果出现抽象部分和实现部分都能够扩展的情况,可以采用桥接模式,让抽象部分和实现部分独立地变化,从而灵活地进行单独扩展,而不是搅在一起,扩展一边会影响到另一边。
        3、如果希望实现部分的修改不会对客户产生影响,可以采用桥接模式。由于客户是面向抽象的接口在运行,实现部分的修改可以独立于抽象部分,并不会对客户产生影响,也可以说对客户是透明的。
        4、如果采用继承的方案,会导致产生很多子类,对于这种情况,可以考虑采用桥接模式,分析功能变化的原因,看看能否分离不同的纬度,然后通过桥接模式来分离它们,从而减少子类的数目。

二、桥接模式示例

        业务需求:发送提示消息(如某人有新的工作了,需要发送一条消息提示他)。从业务上看,消息由分为普通消息、加急消息和特急消息多种,不同的消息类型,业务功能处理是不一样的。如:加急消息是在消息上添加加急,而特急消息除了添加特急外,还会做一条催促的记录,多久不完成就会继续催促;从发送消息的手段上看:有系统内消息、手机短信消息、邮件消息等。

 2.1、不使用模式的示例

  2.1.1、实现简化版本

        我们先实现一个简单版本(如:消息只是实现发送普通消息,发送方式只是实现系统内消息和邮件)由于发送普通消息会有两种不同的实现方式,为了让外部能够统一操作,因此,把消息设计为接口,然后由两个不同的实现类分别实现系统内消息方式和邮件发送消息方式。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 消息统一接口/// </summary>internal interface IMessage{/// <summary>/// 发送消息/// </summary>/// <param name="message">需要发生的消息</param>/// <param name="toUser">消息要发送给的人员</param>void Send(string message,string toUser);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 站内消息的方式发送普通消息/// </summary>internal class CommonMsgSMS : IMessage{public void Send(string message, string toUser){Console.WriteLine($"现在使用【站内消息方式】发送消息【{message}】给【{toUser}】");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 使用Email的方式发送普通消息/// </summary>internal class CommonMsgEmail : IMessage{public void Send(string message, string toUser){Console.WriteLine($"现在使用【Email方式】发送消息【{message}】给【{toUser}】");}}//Class_end
}

  2.1.2、实现加急发送消息

        加急发送消息的实现不同于普通消息,需要在消息前加上加息,然后在发送消息;另外加急消息会提供监控的方法,让客户端可以随时通过这个方法来了解对于加急消息的处理进度(如:相应的人员是否接收到这个消息,相应的工作是否已经开展等)因此加急消息需要扩展新的接口,除了实现基本的发送消息功能外,还需要添加监控功能:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 加急消息接口/// </summary>internal interface IUrgencyMessage:IMessage{/// <summary>/// 监控某消息的处理/// </summary>/// <param name="messageId">被监控消息的编号</param>/// <returns>返回监控到的数据对象</returns>object Watch(string messageId);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 站内消息的方式发送加急消息/// </summary>internal class UrgencyMsgSMS : IUrgencyMessage{public void Send(string message, string toUser){message = $"[加急] {message}";Console.WriteLine($"现在使用【站内消息方式】发送消息【{message}】给【{toUser}】");}public object Watch(string messageId){//获取相应的数据,组织成为监控的数据对象,然后返回return null;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.NoPattern
{/// <summary>/// 使用Email的方式发送加急消息/// </summary>internal class UrgencyMsgEmail : IUrgencyMessage{public void Send(string message, string toUser){message = $"[加急] {message}";Console.WriteLine($"现在使用【Email方式】发送消息【{message}】给【{toUser}】");}public object Watch(string messageId){//获取相应的数据,组织成为监控的数据对象,然后返回return null;}}//Class_end
}

  2.1.3、客户端测试

namespace BridgePattern
{internal class Program{static void Main(string[] args){NoPatternTest();Console.ReadLine();}/// <summary>/// 不使用模式的示例/// </summary>private static void NoPatternTest(){Console.WriteLine("------不使用模式的示例------");//使用站内消息方式发送【普通】消息NoPattern.IMessage message = new NoPattern.CommonMsgSMS();message.Send("请你吃饭", "张三");//使用站内消息方式发送【加急】消息message = new NoPattern.UrgencyMsgSMS();message.Send("请你吃饭", "张三");Console.WriteLine();//使用邮件的方式发送【普通】消息message = new NoPattern.CommonMsgEmail();message.Send("请你吃饭", "张三");//使用邮件的方式发送【加急】消息message = new NoPattern.UrgencyMsgEmail();message.Send("请你吃饭", "张三");}}//Class_end
}

  2.1.4、运行结果

这个示例是满足了基本的功能要求,可是这么实现好不好呢?有没有什么问题呢?

        通过继承来扩展的实现方式,有个明显的缺点:扩展消息的种类不太容易;不同类型的消息具有不同的业务,也就是有不同的实现,在这种情况下,每个种类的消息,需要实现所有不同的消息发送方式。更可怕的是,如果要新加入一种消息的发送方式,那么会要求所有的消息种类都要加入这种新的发送方式的实现。要是考虑业务功能上再扩展一下呢?(如:群发消息,也就是一次可以发送多条消息)就意味着很多地方都要修改,这样的实现很明显是不灵活的

 2.2、桥接模式示例

        桥接模式就是用来解决上述问题的,将抽象部分与它的实现部分分离,使得它们都可以独立的变化。仔细分析上面的示例要求,示例的变化具有两个纬度,一个纬度是抽象的消息(包含普通消息、加急消息、特急消息);另一个纬度是具体的消息发送方式(包含:站内消息、Email消息、手机短信消息)这几个方式是平等的,可被切换方式。这两个纬度一共组合出9种可能性,如下图所示:

        出现问题的根本原因是:在与消息的抽象和实现是混合在一起的,这就导致了一个纬度的变化会引起另一个纬度进行相应的变化,从而使得程序扩展起来非常困难。要想解决这个问题,就必须把这两个纬度分开(即:将抽象部分和实现部分分开,让它们相互独立,这样就可以实现独立的变化,使扩展变得简单)。

        桥接模式通过引入实现的接口,把实现部分从系统中分离出去。那么,抽象这边如何使用具体的实现呢?肯定是用面向实现的接口来编程,为了让抽象这边能够很方便地与实现结合起来,把顶层的抽象接口改成抽象类,在其中持有一个具体的实现部分的实例。这样一来,对于需要发送消息的客户端来说,只需要创建相应的消息对象,然后调用这个消息对象的方法就可以了,这个消息对象会调用持有的真正消息发送法师来把消息发送出去(也就是说:客户端只是想要发送消息而已,并不想关心具体如何发送)。

  2.2.1、实现简单功能

我们先从简单的功能开始,实现普通消息和加急消息功能。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 发送消息的统一接口/// </summary>internal interface IMessage{/// <summary>/// 发送消息/// </summary>/// <param name="message">要发送的消息内容</param>/// <param name="toUser">消息发送给的人员</param>void Send(string message,string toUser);}//Interface_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息对象/// </summary>internal class AbstractMessage{//持有一个实现消息的对象protected IMessage message;/// <summary>/// 构造函数/// </summary>/// <param name="message">实现消息的对象</param>public AbstractMessage(IMessage message){this.message = message;}/// <summary>/// 发送消息/// </summary>/// <param name="message">需要发送的消息内容</param>/// <param name="toUser">消息发送的给的人员</param>public virtual void SendMsg(string message,string toUser){this.message.Send(message, toUser);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 站内消息/// </summary>internal class MsgSMS : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用站内消息的方式,发送消息【{message}】给【{toUser}】");}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 使用Email方式发送消息/// </summary>internal class MsgEmail : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用Email消息的方式,发送消息【{message}】给【{toUser}】");}}//Class_end
}

接下来就是扩展抽象消息接口

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 普通消息/// </summary>internal class CommonMsg : AbstractMessage{public CommonMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){//【普通消息】直接调用父类方法,把消息发送出去就可以了base.SendMsg(message, toUser);}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 加急消息/// </summary>internal class UrgencyMsg : AbstractMessage{public UrgencyMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){message = $"[加急] {message}";base.SendMsg(message, toUser);}/// <summary>/// 扩展新功能(监控消息的处理过程)/// </summary>/// <param name="messageId">被监控的消息的编号</param>/// <returns>返回被监控到的数据对象</returns>public object Watch(string messageId){//获取相应的数据,组织成监控的数据对象,然后返回return null;}}//Class_end
}

  2.2.2、添加新功能

        上面已经使用桥接模式实现了2种消息发送方式和2种消息类型消息;现在来看一下能够解决前面提出的问题,我们通过新添加还未实现的功能来看看(即:新添加特急消息处理;新增加使用手机发送消息的方式)该如何实现?

我们只需要在抽象部分新添加一个特急消息类,扩展抽象消息就可以把特急消息的处理功能加入系统中了;对于新增手机发送消息的方式也简单,只需要在新增一个类实现手机发送消息的方式即可。

《1》新增特急消息处理类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 特急消息/// </summary>internal class SpecialUrgencyMsg : AbstractMessage{public SpecialUrgencyMsg(IMessage message) : base(message){}public override void SendMsg(string message, string toUser){message = $"[特急] {message}";base.SendMsg(message, toUser);}public void Hurry(string messageId){//执行催促的业务,发出催促消息}}//Class_end
}

《2》新增的手机发送消息方式功能

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 手机短信的方式发送消息/// </summary>internal class MsgMobile : IMessage{public void Send(string message, string toUser){Console.WriteLine($"使用手机短信的方式,发送消息【{message}】给【{toUser}】");}}//Class_end
}

  2.2.3、客户端测试

namespace BridgePattern
{internal class Program{static void Main(string[] args){BridgeDemoOneTest();Console.ReadLine();}/// <summary>/// 桥接模式示例1/// </summary>private static void BridgeDemoOneTest(){Console.WriteLine("------桥接模式示例1------");/*把发送消息实现方式切换为站内信*///创建具体的实现对象BridgeDemoOne.IMessage messageStyle = new BridgeDemoOne.MsgSMS();//创建一个普通的消息对象BridgeDemoOne.AbstractMessage abstarctMessage=new BridgeDemoOne.CommonMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭","张三");//创建一个紧急消息对象abstarctMessage=new BridgeDemoOne.UrgencyMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭","张三");//创建一个特急消息对象abstarctMessage = new BridgeDemoOne.SpecialUrgencyMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭", "张三");Console.WriteLine();/*把发送消息实现方式切换为邮件*///创建具体的实现对象messageStyle=new BridgeDemoOne.MsgEmail();//创建一个普通的消息对象abstarctMessage = new BridgeDemoOne.CommonMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭", "张三");//创建一个紧急消息对象abstarctMessage = new BridgeDemoOne.UrgencyMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭", "张三");//创建一个特急消息对象abstarctMessage = new BridgeDemoOne.SpecialUrgencyMsg(messageStyle);abstarctMessage.SendMsg("请你吃饭", "张三");}}//Class_end
}

  2.2.4、运行结果

  2.2.5、谁来桥接

《1》由抽象部分的对象自己来创建相应的对象

这种情况又分为两种实现:一种是需要外部传入参数,另一种是不需要外部传入参数:

①外部传入参数:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息对象/// </summary>internal class AbstractMessage2{//持有一个实现部分的对象protected IMessage message;public AbstractMessage2(int type){switch (type){case 1:message = new MsgSMS();break;case 2:message = new MsgEmail();break;case 3:message = new MsgMobile();break;default:break;}}/// <summary>/// 发送消息/// </summary>/// <param name="message">需要发送的消息内容</param>/// <param name="toUser">消息发送的给的人员</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}}//Class_end
}

②不需要外部传入参数

        这种不需要外部传入参数的情况,那就说明在抽象类中,有可能在抽象类的构造函数中选择;也有可能在具体的方法中选择。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{/// <summary>/// 抽象的消息对象/// </summary>internal class AbstractMessage3{//持有一个实现部分的对象protected IMessage message;public AbstractMessage3(){}/// <summary>/// 发送消息/// </summary>/// <param name="message">需要发送的消息内容</param>/// <param name="toUser">消息发送的给的人员</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}//根据消息的长度来选择合适的实现protected IMessage GetImpl(string message){IMessage msg = null;if (string.IsNullOrEmpty(message)){//若没有任何消息则默认使用站内消息msg = new MsgSMS();}else if (message.Length < 100){//如消息长度在100以内,则使用手机短信msg = new MsgMobile();}else if (message.Length < 1000){//如消息长度在100-1000以内,则使用站内消息msg = new MsgSMS();}else{//如消息长度在1000以上,则使用Emailmsg = new MsgEmail();}return msg;}}//Class_end
}

《2》在抽象类的构造函数中创建默认实现对象

        直接在抽象类的构造方法中,创建一个默认的实现对象,然后子类根据需要,可以选择直接使用还是覆盖掉。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace BridgePattern.BridgeDemoOne
{internal class AbstractMessage4{//持有一个实现消息的对象protected IMessage message;/// <summary>/// 构造函数/// </summary>public AbstractMessage4(){//创建一个默认的实现this.message = new MsgSMS();}/// <summary>/// 发送消息/// </summary>/// <param name="message">需要发送的消息内容</param>/// <param name="toUser">消息发送的给的人员</param>public virtual void SendMsg(string message, string toUser){this.message.Send(message, toUser);}}//Class_end
}

三、项目源码工程

kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/93977.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/93977.shtml
英文地址,请注明出处:http://en.pswp.cn/web/93977.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Python 创建虚拟环境的两种方式

使用Python 创建虚拟环境的两种方式&#xff1a; 方式一&#xff1a;使用官方标准库 venv (Python 3.3 推荐) 创建&#xff1a; # 语法&#xff1a;python -m venv <虚拟环境名称> python -m venv my_project_env指定Python解释器版本&#xff08;如果你的系统有多个Pyth…

Android 开发问题:android:marginTop=“20px“ 属性不生效

android:marginTop"20px"在 Android 开发中&#xff0c;XML 布局文件中&#xff0c;上述属性不生效 问题原因 margin 系列的属性需要加上 layout_ 前缀layout_marginTop&#xff1a;顶部边距layout_marginBottom&#xff1a;底部边距layout_marginLeft&#xff1a;左…

【P18 3-10】OpenCV Python—— 鼠标控制,鼠标回调函数(鼠标移动、按下、。。。),鼠标绘制基本图形(直线、圆、矩形)

P18 3-10 1 鼠标回调函数2 鼠标绘制基本图形&#xff08;直线、圆、矩形&#xff09;2.1 图形绘制教程2.2 鼠标绘制基本图形&#xff08;直线、圆、矩形&#xff09;代码实现1 鼠标回调函数 import cv2 import numpy as npdef mouse_callback(event,x,y,flage,userdata):print(…

微服务如何集成swagger3

文章目录引言一、项目结构二、顶级pom依赖准备三、common-swagger模块四、gateway模块配置五、结果演示引言 我们在用springboot开发应用时&#xff0c;经常使用swagger来作为我们的接口文档可视化工具&#xff0c;方便前端同事调用&#xff0c;集成也是比较简单的&#xff0c…

特种行业许可证识别技术:通过图像处理、OCR和结构化提取,实现高效、准确的许可证核验与管理

在酒店、娱乐场所、典当行、危化品经营等特种行业管理中&#xff0c;许可证是合法经营的“生命线”。传统人工核验方式效率低下、易出错&#xff0c;且难以应对海量数据和复杂伪造手段。特种行业许可证识别技术应运而生&#xff0c;成为智慧监管和优化服务的关键工具。特种行业…

零售行业新店网络零接触部署场景下,如何选择SDWAN

一家连锁超市在新疆偏远地区的新店开业申请网络专线&#xff0c;市政审批和架设电线杆的流程花了半个月&#xff0c;成本高企——而它的竞争对手在隔壁新店部署SD-WAN&#xff0c;从开箱到业务上线仅用了10分钟。近年来&#xff0c;零售企业疯狂扩张与下沉市场的趋势愈演愈烈。…

python发布文章和同步文章到社区的工具小脚本

在开发过程中&#xff0c;开发者们往往需要频繁地在社区中分享文章、解决方案以及技术文章来交流与成长。为了简化这一过程&#xff0c;我将为你们介绍两个基于Python脚本的自动化工具&#xff0c;可以帮助你发布文章到开发者社区&#xff0c;提高效率。一、从Markdown文件批量…

23.CNN系列网络思考

为什么是卷积、池化的交替进行? 卷积做的是特征提取,池化做的是一种降采样。 早期学习的主要是:低级特征(边缘、角点、纹理、颜色)。这些特征分布相对局部且空间位置信息很重要。 卷积将这些特征学习出来,然后通过池化降采样,突出其位置信息。然后再卷积进行学习池化后…

MySQL 8.x的性能优化文档整理

一、内存与缓冲优化 # InnoDB缓冲池&#xff08;内存的60%-80%&#xff09; innodb_buffer_pool_size 12G # 核心参数 innodb_buffer_pool_instances 8 # 8核CPU建议分8个实例# 日志缓冲区与Redo日志 innodb_log_buffer_size 256M # 事务日志缓冲区 innodb_log_…

个人使用AI开发的《PlSqlRewrite4GaussDB(PLSQL自动转换工具)1.0.1 BETA》发布

个人使用AI开发的《PlSqlRewrite4GaussDB(PLSQL自动转换工具)1.0.1 BETA》发布 前言 基于语法树的SQL自动改写工具开发系列&#xff08;1&#xff09;-离线安装语法树解析工具antlr4 基于语法树的SQL自动改写工具开发系列&#xff08;2&#xff09;-使用PYTHON进行简单SQL改写…

python的校园研招网系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 摘要&…

如何高效撰写AI领域学术论文——学习笔记

最开始写的时候最好仿照着顶会来写1. 标题(Title)​标题是论文的"门面"&#xff0c;需要同时具备简洁性和信息量&#xff1a;采用"XX方法 for XXX任务"的标准格式&#xff0c;包含核心创新点和应用领域&#xff0c;避免使用模糊词汇&#xff0c;力求精准&a…

elasticsearch8.12.0安装分词

上篇说到&#xff0c;安装了es后正常运行es分词下载地址从 GitHub Release 下载&#xff08;推荐&#xff09; &#x1f449; https://github.com/medcl/elasticsearch-analysis-ik/releases或https://release.infinilabs.com/analysis-ik/stable/安装&#xff1a;选择与你 ES …

强化学习算法分类与介绍(含权重更新公式)

强化学习算法种类丰富&#xff0c;可按学习目标&#xff08;基于价值 / 基于策略 / 演员 - 评论家&#xff09;、数据使用方式&#xff08;在线 / 离线&#xff09;、是否依赖环境模型&#xff08;无模型 / 有模型&#xff09;等维度分类。以下按核心逻辑梳理常见算法&#xff…

基于STM32F103单片机智能门禁热释人体感应报警设计

1 系统功能介绍 本设计基于 STM32F103C8T6 单片机&#xff0c;通过多种传感器、执行器以及通信模块实现智能门禁和安防报警功能。其主要功能是检测门外人员情况&#xff0c;结合环境光照强度判断是否需要照明&#xff0c;同时结合 GSM 模块在异常情况下发送报警信息&#xff0c…

imx6ull-驱动开发篇33——platform 平台驱动模型

目录 Linux 驱动的分离与分层 驱动的分隔与分离 驱动的分层 platform 平台驱动模型 platform 总线 bus_type 结构体 platform 总线 platform_match函数 platform 驱动 platform_driver 结构体 device_driver 结构体 platform_driver_register 函数 platform_drive…

Win/Linux笔记本合盖不睡眠设置指南

在 笔记本电脑上&#xff0c;当你合上屏幕时&#xff0c;默认系统可能会进入“睡眠”或“休眠”状态。如果你希望合上屏幕时系统继续正常运行&#xff08;例如后台下载、运行程序、远程访问等&#xff09;&#xff0c;需要修改系统的电源设置。 一、以下是 Windows 10 / Windo…

(栈)Leetcode155最小栈+739每日温度

739. 每日温度 - 力扣&#xff08;LeetCode&#xff09; while要把stack的判断放在前面&#xff0c;否则stack[-1]可能报错 class Solution(object):def dailyTemperatures(self, temperatures):""":type temperatures: List[int]:rtype: List[int]""…

【NLP(01)】NLP(自然语言处理)基础

目录NLP基础一、基本概念1. 自然语言处理的基本介绍1.1 与语言相关的概念1.2 为什么使用NLP2. NLP的应用方向2.1 **自然语言理解**2.2 自然语言转换2.3 自然语言生成3. NLP基础概念4. NLP的发展历史5. NLP的基本流程二、NLP中的特征工程0. 引入1. 词向量2. 传统NLP中的特征工程…

Python工程师进阶学习道路分析

本文将分为以下几个核心部分&#xff1a; 心态与基础重塑&#xff1a;从“会用”到“精通”核心语言深度&#xff1a;窥探Python的奥秘编程范式与设计模式&#xff1a;写出优雅的代码并发与异步编程&#xff1a;释放多核时代的威力性能分析与优化&#xff1a;让代码飞起来深入…