学习设计模式《八》——原型模式
一、基础概念
原型模式的本质是【克隆生成对象】;
原型模式的定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 。
原型模式的功能: 1、通过克隆来创建新的对象实例; 2、为克隆出来的新对象实例复制原型实例属性值;
克隆:无论是自己实现克隆方法,还是采用C#提供的克隆方法,都存在一个浅度克隆和深度克隆的问题:
1、浅度克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型);
2、深度克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性数据都会被克隆出来;
序号 | 原型模式的优点 | 原型模式的缺点 |
1 | 对客户端隐藏具体的实现类型 (即:原型模式的客户端只知道原型接口类型,并不知道具体的实现类型, 从而减少了客户端对具体实现类型的依赖) | 每个原型的子类都必须实现克隆操作,尤其在包含引用类型的对象时,克隆方法会比较麻烦,必须要能够递归地让所有相关对象都要正确地实现克隆 |
2 | 在运行时动态改变具体的实现类型 (即:原型模式可以在运行期间,由客户来注册符合原型接口的实现类型,也可以动态改变具体的实现类型,看起来接口没有任何变化,但其实运行的已经是另外一个类实例了【因为克隆一个原型就类似于实例化一个类】) |
何时选用原型模式?
1、如果一个系统想要独立于它想要使用的对象时【让系统只面向接口编程,在系统需要新对象时可以通过克隆原型获取】;
2、如果需要实例化的类是在运行时动态指定的,可通过克隆原型类得到想要的实例。
二、原型模式示例
业务需求:比如我们有一个订单处理功能,需要保存订单业务(在这个业务功能中,每当订单的预订数量超过1000的时候,就需要将订单拆分为两份订单保存;如果拆成了两份订单后,数量还是超过1000,则继续拆分,直到每份订单的数量不超过1000);且这个订单类型会分为两种(一种是个人订单;一种是公司订单),无论何种订单类型都需要按照业务规则处理。
2.1、不使用模式的示例
既然有两种订单类型,且都要实现保存订单相关业务的通用功能,那么我们可以定义一个接口来声明这些功能行为,然后在定义具体的类分别实现即可:
1、定义订单接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern
{/// <summary>/// 订单接口/// </summary>internal interface IOrder{//获取订单产品数量int GetOrderProductNumber();//设置订单产品数量void SetOrderProductNumber(int productNumber);}//Interface_end
}
2、分别定义个人订单与企业订单类来实现接口定义的功能行为
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern
{/// <summary>/// 个人订单对象/// </summary>internal class PersonalOrder : IOrder{//消费者名称public string? CustomerName;//产品编号public string? ProductId;//产品订单数量private int productOrderNumber = 0;public int GetOrderProductNumber(){return productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str=$"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";return str;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern
{/// <summary>/// 企业订单对象/// </summary>internal class EnterpriseOrder : IOrder{//企业名称public string? EnterpriseName;//产品编号public string? ProductId;//产品的订单数量private int productOrderNumber=0;public int GetOrderProductNumber(){return productOrderNumber;}public void SetOrderProductNumber(int productNumber){productOrderNumber = productNumber;}public override string ToString(){string str = $"企业订单的订购企业是【{EnterpriseName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";return str;}}//Class_end
}
3、现在的中心任务就是要实现《保存订单》的业务方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern
{/// <summary>/// 处理订单的业务对象/// </summary>internal class OrderBussiness{//固定的数量private const int fixedNumber = 1000;/// <summary>/// 保存订单/// </summary>/// <param name="order">订单</param>public void SaveOrder(IOrder order){/*根据业务要求,当预订产品的数量大于1000时,就需要把订单拆分为两份*///1、判断订单是否大于1000(若大于1000则拆分订单)while (order.GetOrderProductNumber()>1000){//2.创建一份新订单,这份订单传入的订单除了数量不一样,其他都相同IOrder newOrder = null;if (order is PersonalOrder){//创建相应的新订单对象PersonalOrder newPO = new PersonalOrder();//将传入订单的数据赋值给新订单对象PersonalOrder po = (PersonalOrder)order;newPO.CustomerName = po.CustomerName;newPO.ProductId = po.ProductId;newPO.SetOrderProductNumber(fixedNumber);//将个人订单对象内容赋值给新订单newOrder = newPO;}else if (order is EnterpriseOrder){ EnterpriseOrder newEO = new EnterpriseOrder();EnterpriseOrder eo = (EnterpriseOrder)order;newEO.EnterpriseName = eo.EnterpriseName;newEO.ProductId = eo.ProductId;newEO.SetOrderProductNumber(fixedNumber);newOrder=newEO;}//3、设置拆分后的订单数量order.SetOrderProductNumber(order.GetOrderProductNumber() - fixedNumber);//4、处理业务功能Console.WriteLine($"拆分生成的订单是【{newOrder}】");}//订单数量不超过1000的直接执行业务处理Console.WriteLine($"拆分生成的订单是【{order}】");}}//Class_end
}
4、编写客户端测试
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){OrderBussinessTest();Console.ReadLine();}/// <summary>/// 处理订单的业务对象测试/// </summary>private static void OrderBussinessTest(){Console.WriteLine("---处理订单的业务对象测试---");/*个人订单*/Console.WriteLine("\n\n个人订单\n");//创建订单对象并设置内容(为了演示简单直接new)PersonalOrder po = new PersonalOrder();po.CustomerName = "张三";po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100,999)}";po.SetOrderProductNumber(2966);//获取订单业务对象(为了演示简单直接new)OrderBussiness ob=new OrderBussiness();//保存订单业务ob.SaveOrder(po);/*企业订单*/Console.WriteLine("\n\n企业订单\n");EnterpriseOrder eo=new EnterpriseOrder();eo.EnterpriseName = "牛奶咖啡科技有限公司";eo.ProductId= $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";eo.SetOrderProductNumber(3001);OrderBussiness ob2 = new OrderBussiness();ob2.SaveOrder(eo);}}//Class_end
}
5、运行结果如下:
6、有何问题?
不使用模式的示例是实现了我们需要的保存订单业务功能;但是存在两个问题:
《1》既然我们想要通用的保存订单业务功能,那么实现对象是不应该知道订单的具体对象和具体实现,更不能依赖订单的具体实现;而上面的示例很明显的依赖了具体对象和具体实现;
《2》不使用模式的示例在实现业务功能的时候是很难扩展新的订单类型(即:如果我们现在又增加了几种订单类型,那么还需要在保存订单业务方法里面添新类型的处理,很繁琐,不优雅)。
2.2、使用原型模式的示例
其实上面不使用模式的示例暴露的问题总结起来就是:【我们已经有了具体的实例对象,如何能够在不修改业务方法的情况下快速的使用更多新增的对象】而原型模式刚好就是解决这个问题的。
1、定义订单接口规范产品功能行为
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.Prototype
{/// <summary>/// 订单接口/// </summary>internal interface IOrder{//获取订单产品数量int GetOrderProductNumber();//设置订单产品数量void SetOrderProductNumber(int productNumber);//克隆方法IOrder Clone();}//Interface_end
}
2、创建个人订单对象与企业订单对象继承接口实现具体功能行为
注意:关于这里的克隆方法不能直接使用【return this】来写,这是因为若这样设置,那么每次克隆客户端获取的都是同一个实例,都指向同一个内存空间,此时只要修改克隆出来的实例对象就会影响到原型对象的实例,这是不可取的;【正确地做法是:直接先new一个自己的对象实例,然后再把自己实例的数据取出来赋值到新对象实例中去】如下所示:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.Prototype
{/// <summary>/// 个人订单对象/// </summary>internal class PersonalOrder : IOrder{//订购人员名称public string? CustomerName;//产品编号public string? ProductId;//订单产品数量private int productOrderNumber=0;public IOrder Clone(){//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】PersonalOrder po = new PersonalOrder();po.CustomerName = this.CustomerName;po.ProductId = this.ProductId;po.SetOrderProductNumber(this.productOrderNumber);return po;}public int GetOrderProductNumber(){return productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";return str;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.Prototype
{/// <summary>/// 企业订单对象/// </summary>internal class EnterpriseOrder : IOrder{//企业名称public string? EnterpriseName;//产品编号public string? ProductId;//产品的订单数量private int productOrderNumber = 0;public IOrder Clone(){//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】EnterpriseOrder eo = new EnterpriseOrder();eo.EnterpriseName = this.EnterpriseName;eo.ProductId = this.ProductId;eo.SetOrderProductNumber(this.productOrderNumber);return eo;}public int GetOrderProductNumber(){return productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str = $"企业订单的订购企业是【{EnterpriseName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";return str;}}//Class_end
}
3、创建一个类构建通用的保存订单业务方法且不依赖具体的实例对象、方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.Prototype
{/// <summary>/// 处理订单的业务对象/// </summary>internal class OrderBussiness{private const int fixedNumber = 1000;public void SaveOrder(IOrder order){/*根据业务要求,当预订产品的数量大于1000时,就需要把订单拆分为两份*///1、判断订单是否大于1000(若大于1000则拆分订单)while (order.GetOrderProductNumber()>1000){//2、创建一份新的订单,除了订单的数量不一样,其他内容都一致IOrder newOrder = order.Clone();//3、然后进行赋值newOrder.SetOrderProductNumber(fixedNumber);//4、创建新订单后原订单需要将使用的数量减去order.SetOrderProductNumber(order.GetOrderProductNumber()-fixedNumber);//5、处理业务功能Console.WriteLine($"拆分生成的订单是【{newOrder}】");}//订单数量不超过1000的直接执行业务处理Console.WriteLine($"拆分生成的订单是【{order}】");}}//Class_end
}
4、客户端测试原型模式
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){OrderBussinessPrototypeTest();Console.ReadLine();}/// <summary>/// 处理订单业务原型模式测试/// </summary>private static void OrderBussinessPrototypeTest(){Console.WriteLine("---处理订单业务原型模式测试---");/*个人订单*/Console.WriteLine("\n\n个人订单\n");//创建订单对象并设置内容(为了演示简单直接new)Prototype.PersonalOrder po = new Prototype.PersonalOrder();po.CustomerName = "张三";po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";po.SetOrderProductNumber(2966);//获取订单业务对象(为了演示简单直接new)Prototype.OrderBussiness ob = new Prototype.OrderBussiness();//保存订单业务ob.SaveOrder(po);/*企业订单*/Console.WriteLine("\n\n企业订单\n");Prototype.EnterpriseOrder eo = new Prototype.EnterpriseOrder();eo.EnterpriseName = "牛奶咖啡科技有限公司";eo.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";eo.SetOrderProductNumber(3001);Prototype.OrderBussiness ob2 = new Prototype.OrderBussiness();ob2.SaveOrder(eo);}}//Class_end
}
5、运行结果
可以看到我们使用原型模式也成功实现了业务功能,并且我们现在扩展新的订单类型后也十分简单,直接用新订单类型实例调用业务方法即可,而不用对业务类方法进行任何修改。
2.3、原型实例与克隆实例
2.3.1、自己手动实现克隆方法
原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的,也就是说它们所指向不同的内存空间)如下所示:
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){TestPrototypeInstaceAndCloneInstace();Console.ReadLine();}/// <summary>/// 【浅度克隆】原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的)/// </summary>private static void TestPrototypeInstaceAndCloneInstace(){//先创建原型实例Prototype.PersonalOrder order = new Prototype.PersonalOrder();//设置原型实例的订单数量order.SetOrderProductNumber(666);//为了演示简单,就只输出数量Console.WriteLine($"第一次获取个人订单对象实例的数量【{order.GetOrderProductNumber()}】");//通过克隆来获取实例Prototype.PersonalOrder order2 = (Prototype.PersonalOrder)order.Clone();//修改克隆实例的数量order2.SetOrderProductNumber(33);//输出数量Console.WriteLine($"克隆实例的数量【{order2.GetOrderProductNumber()}】");//输出原型实例的数量Console.WriteLine($"克隆实例修改数量后原型实例的数量是【{order.GetOrderProductNumber()}】");}}//Class_end
}
运行结果如下:
2.3.2、C#中的克隆方法
在C#语言中已经提供了克隆方法,定义在Object类中;需要克隆功能的类,只需要继承【System.ICloneable】接口即可;如下为演示C#克隆方法示例:
ICloneable.Clone 方法 (System) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.icloneable.clone?view=net-7.0Object.MemberwiseClone 方法 (System) | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/api/system.object.memberwiseclone?view=net-9.01、创建接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{/// <summary>/// 订单接口/// </summary>internal interface IOrder2{//获取订单产品数量int GetOrderProductNumber();//设置订单产品数量void SetOrderProductNumber(int productNumber);}//Interface_end
}
2、创建具体的个人订单象继承订单接口与C#克隆接口实现功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{/// <summary>/// 订单对象【继承C#克隆接口】/// </summary>internal class PersonalOrder2 : IOrder2, ICloneable{//订购人员名称public string? CustomerName;//产品编号public string? ProductId;//订单产品数量private int productOrderNumber = 0;public object Clone(){//直接调用父类的克隆方法【浅度克隆】object obj = base.MemberwiseClone();return obj;}public int GetOrderProductNumber(){return this.productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】";return str;}}//Class_end
}
3、客户端调用测试
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){TestPrototypeInstaceAndCloneInstace2();Console.ReadLine();}/// <summary>/// 【浅度克隆】原型实例与克隆出来的实例,本质上是不同的实例(克隆完成后,它们之间是没有关联,互不影响的)/// </summary>private static void TestPrototypeInstaceAndCloneInstace2(){//先创建原型实例CSharpClone.PersonalOrder2 order = new CSharpClone.PersonalOrder2();//设置原型实例的订单数量order.SetOrderProductNumber(666);//为了演示简单,就只输出数量Console.WriteLine($"第一次获取个人订单对象实例的数量【{order.GetOrderProductNumber()}】");//通过克隆来获取实例CSharpClone.PersonalOrder2 order2 = (CSharpClone.PersonalOrder2)order.Clone();//修改克隆实例的数量order2.SetOrderProductNumber(33);//输出数量Console.WriteLine($"克隆实例的数量【{order2.GetOrderProductNumber()}】");//输出原型实例的数量Console.WriteLine($"克隆实例修改数量后原型实例的数量是【{order.GetOrderProductNumber()}】");}}//Class_end
}
运行结果:
2.4、深度克隆
深度克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据,基本上就是被克隆实例所有的属性数据都会被克隆出来(如果被克隆的对象里面属性数据是引用类型,也就是属性类型也是对象,则需要一直递归地克隆下去【也就是说,要想深度克隆成功,必须要整个克隆所涉及的对象都要正确实现克隆方法,如果其中的一个没有正确实现克隆,那么就会导致克隆失败】)。
2.4.1、自己实现原型的深度克隆
1、定义产品接口规范产品行为功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{/// <summary>/// 定义一个克隆产品自身的接口/// </summary>internal interface IProductPrototype{//克隆产品自身的方法IProductPrototype CloneProduct();}//Interface_end
}
2、定义一个产品对象,继承产品接口并实现克隆功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{/// <summary>/// 产品对象/// </summary>internal class Product : IProductPrototype{//产品编号public string? ProductId;//产品名称public string? ProductName;public IProductPrototype CloneProduct(){//创建一个新订单,然后把本实例的数据复制过去Product product = new Product();product.ProductId = ProductId;product.ProductName = ProductName;return product;}public override string ToString(){string str = $"产品编号【{ProductId}】产品名称【{ProductName}】";return str;}}//Class_end
}
3、订单对象的添加产品对象属性
using PrototypePattern.CSharpClone;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.Prototype
{/// <summary>/// 个人订单对象/// </summary>internal class PersonalOrder4 : IOrder{//订购人员名称public string? CustomerName;//产品编号public string? ProductId;//产品对象【新增的产品对象引用类型】public CSharpClone.Product? Product;//订单产品数量private int productOrderNumber=0;public IOrder Clone(){//创建一个新订单对象,然后把该实例的数据赋值给新对象【浅度克隆】PersonalOrder4 po = new PersonalOrder4();po.CustomerName = this.CustomerName;po.ProductId = this.ProductId;po.SetOrderProductNumber(this.productOrderNumber);/*自己实现深度克隆也不是很复杂,但是比较麻烦,如果产品类中又有属性是引用类型,* 在产品类实现克隆方法的时候,则需要调用那个引用类型的克隆方法了。这样一层层的调用下去,* 如果中途有任何一个对象没有正确实现深度克隆,那将会引起错误*///对于对象类型的数据,深度克隆的时候需要继续调用整个对象的克隆方法【体现深度克隆】po.Product = (CSharpClone.Product)this.Product.CloneProduct();return po;}public int GetOrderProductNumber(){return productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】】,产品对象是【{Product}】】";return str;}}//Class_end
}
4、客户端测试
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){OrderBussinessTestDeepClone();Console.ReadLine();}/// <summary>/// 【深度克隆】处理订单的业务对象测试/// </summary>private static void OrderBussinessTestDeepClone(){Console.WriteLine("---处理订单的业务对象测试【深度克隆】---");/*个人订单*/Console.WriteLine("\n\n个人订单\n");//创建订单对象并设置内容(为了演示简单直接new)Prototype.PersonalOrder4 po = new Prototype.PersonalOrder4();po.CustomerName = "张三";po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";po.SetOrderProductNumber(2966);//实例化产品类且指定所有属性的值CSharpClone.Product product = new CSharpClone.Product();product.ProductName = "产品1";product.ProductId = "XCKX006";//个人订单对象的产品赋值po.Product = product;Console.WriteLine($"这是第一次获取的个人订单对象实例【{po}】");//通过克隆来获取新实例Prototype.PersonalOrder4 po2 = (Prototype.PersonalOrder4)po.Clone();//修改克隆实例的值po2.CustomerName = "李四";po2.ProductId = $"2CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";po2.SetOrderProductNumber(3666);po2.Product.ProductName = "产品2";po2.Product.ProductId = "YYCKYY009";//输出克隆实例的Console.WriteLine($"输出克隆出来的个人订单对象实例【{po2}】");//再次输出原型的实例Console.WriteLine($"这是第二次获取的个人订单对象实例【{po}】");}}//Class_end
}
运行结果如下:
通过自己实现深度克隆可以了解其中原理;其实自己实现深度克隆也不是很复杂,只是比较麻烦。若产品类中又有属性是引用类型,在产品实现克隆方法的时候,则需要调用那个引用类型的克隆方法;需要这样一层层对的调用下去;但中途若有任何一个对象没有正确实现深度克隆,就会引起错误 。
2.4.2、C#中的深度克隆
1、让产品对象继承C#的克隆接口【ICloneable】
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{/// <summary>/// 产品对象/// </summary>internal class Product2 : ICloneable{//产品编号public string? ProductId;//产品名称public string? ProductName;public object Clone(){//直接使用C#的克隆方法,不用自己手动给属性逐一赋值object obj = base.MemberwiseClone();return obj;}public override string ToString(){string str = $"产品编号【{ProductId}】产品名称【{ProductName}】";return str;}}//Class_end
}
2、实现个人订单对象添加产品属性内容
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.CSharpClone
{internal class PersonalOrder5 : IOrder2, ICloneable{//订购人员名称public string? CustomerName;//产品编号public string? ProductId;//产品对象【新增的产品对象引用类型】public Product2? Product2;//订单产品数量private int productOrderNumber = 0;public object Clone(){//直接调用C#的克隆方法【浅度克隆】PersonalOrder5 obj = (PersonalOrder5)base.MemberwiseClone();//必须手工针对每一个引用类型的属性进行克隆obj.Product2 = (Product2)this.Product2.Clone();return obj;}public int GetOrderProductNumber(){return this.productOrderNumber;}public void SetOrderProductNumber(int productNumber){//做各种逻辑校验内容,此处省略this.productOrderNumber = productNumber;}public override string ToString(){string str = $"个人订单的订购人是【{CustomerName},订购的产品是【{ProductId}】,订购数量是【{productOrderNumber}】,产品对象是【{Product2}】】";return str;}}//Class_end
}
3、客户端测试
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){OrderBussinessTestDeepClone2();Console.ReadLine();}/// <summary>/// 【深度克隆】处理订单的业务对象测试/// </summary>private static void OrderBussinessTestDeepClone2(){Console.WriteLine("---处理订单的业务对象测试【深度克隆】---");/*个人订单*/Console.WriteLine("\n\n个人订单\n");//创建订单对象并设置内容(为了演示简单直接new)CSharpClone.PersonalOrder5 po = new CSharpClone.PersonalOrder5();po.CustomerName = "张三";po.ProductId = $"CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";po.SetOrderProductNumber(2966);//实例化产品类且指定所有属性的值CSharpClone.Product2 product2 = new CSharpClone.Product2();product2.ProductName = "产品1";product2.ProductId = "XCKX006";//个人订单对象的产品赋值po.Product2 = product2;Console.WriteLine($"这是第一次获取的个人订单对象实例【{po}】");//通过克隆来获取新实例CSharpClone.PersonalOrder5 po2 = (CSharpClone.PersonalOrder5)po.Clone();//修改克隆实例的值po2.CustomerName = "李四";po2.ProductId = $"2CK{new Random(Guid.NewGuid().GetHashCode()).Next(100, 999)}";po2.SetOrderProductNumber(3666);po2.Product2.ProductName = "产品2";po2.Product2.ProductId = "YYCKYY009";//输出克隆实例的Console.WriteLine($"输出克隆出来的个人订单对象实例【{po2}】");//再次输出原型的实例Console.WriteLine($"这是第二次获取的个人订单对象实例【{po}】");}}//Class_end
}
4、运行结果如下:
2.5、原型管理器
如果一个系统中的原型数目不固定(如:原型可以被动态的创建和销毁)那么久需要再系统中维护一个当前可用的原型注册表(也称为原型管理器);有了原型管理器后,除了向原型管理器里面添加原型对象的时候是通过new来创建对象的,其余时候都是通过原型管理器来请求原型实例,然后通过克隆方法来获取新对象实例,就可以动态的管理原型了。
1、定义原型接口规范行为功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.PrototypeeManager
{/// <summary>/// 原型管理器接口/// </summary>internal interface IPrototypeManager{IPrototypeManager Clone();string GetName();void SetName(string name);}//Interface_end
}
2、定义类对象原型继承接口实现具体功能
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.PrototypeeManager
{internal class ConcreatePrototype1 : IPrototypeManager{private string name;public IPrototypeManager Clone(){ConcreatePrototype1 cp=new ConcreatePrototype1();cp.SetName(name);return cp;}public string GetName(){return name;}public void SetName(string name){this.name = name;}public override string ToString(){string str = $"这是具体的原型一,名称是【{name}】";return str;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.PrototypeeManager
{internal class ConcreatePrototype2 : IPrototypeManager{private string name;public IPrototypeManager Clone(){ConcreatePrototype2 cp=new ConcreatePrototype2();cp.SetName(name);return cp;}public string GetName(){return name;}public void SetName(string name){this.name = name;}public override string ToString(){string str = $"这是具体的原型二,名称是【{name}】";return str;}}//Class_end
}
3、实现原型管理器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace PrototypePattern.PrototypeeManager
{internal class PrototypeManager{//定义一个字典来记录原型编号与原型实例的对应关系private static Dictionary<string,IPrototypeManager> dicPrototype=new Dictionary<string,IPrototypeManager>();//私有化构造方法,避免外部私自创建实例private PrototypeManager(){}/// <summary>/// 添加原型/// </summary>/// <param name="prototypeId">原型编号</param>/// <param name="prototype">原型实例</param>public static void AddPrototype(string prototypeId,IPrototypeManager prototype){if (string.IsNullOrEmpty(prototypeId) || prototype == null){string str = $"原型编号或者原型不能为空,请检查后重试!";Console.WriteLine(str);return;}if (!dicPrototype.ContainsKey(prototypeId)){dicPrototype.Add(prototypeId, prototype);}else{string str = $"当前已经存在编号为【{prototypeId}】的原型【{prototype}】,不用重复添加!!!";Console.WriteLine(str);}}/// <summary>/// 删除原型/// </summary>/// <param name="prototypeId">原型编号</param>public static void DelPrototype(string prototypeId){if (string.IsNullOrEmpty(prototypeId)){string str = $"原型编号不能为空,请检查后重试!";Console.WriteLine(str);return;}dicPrototype.Remove(prototypeId);}/// <summary>/// 获取原型/// </summary>/// <param name="prototypeId">原型编号</param>/// <returns></returns>public static IPrototypeManager GetPrototype(string prototypeId){IPrototypeManager prototype = null;if (string.IsNullOrEmpty(prototypeId)){string str = $"原型编号不能为空,请检查后重试!";Console.WriteLine(str);return prototype;}if (dicPrototype.ContainsKey(prototypeId)){prototype = dicPrototype[prototypeId];return prototype;}else{Console.WriteLine($"你希望获取的原型还没注册或已被销毁!!!");return prototype;}}/// <summary>/// 修改原型/// </summary>/// <param name="prototypeId">原型编号</param>/// <param name="prototype">原型实例</param>public static void ModifyPrototype(string prototypeId, IPrototypeManager prototype){if (string.IsNullOrEmpty(prototypeId) || prototype == null){string str = $"原型编号或者原型不能为空,请检查后重试!";Console.WriteLine(str);return;}if (dicPrototype.ContainsKey(prototypeId)){dicPrototype[prototypeId] = prototype; ;}else{string str = $"当前不存在编号为【{prototypeId}】的原型,无法修改!!!";Console.WriteLine(str);}}}//Class_end
}
4、客户端使用原型管理器
using PrototypePattern.PrototypeeManager;
using System.Net.Sockets;namespace PrototypePattern
{internal class Program{static void Main(string[] args){PrototypeManagerTest();Console.ReadLine();}/// <summary>/// 原型管理器测试/// </summary>private static void PrototypeManagerTest(){Console.WriteLine("---原型管理器测试---");//初始化原型管理器string prototypeId = "原型一";IPrototypeManager pm = new ConcreatePrototype1();PrototypeManager.AddPrototype(prototypeId,pm);//1、获取原型来创建对象IPrototypeManager pm1 = PrototypeManager.GetPrototype(prototypeId).Clone();pm1.SetName("张三");Console.WriteLine($"第一个实例是【{pm1}】");//2、有人动态的切换string prototypeId2 = "原型二";IPrototypeManager pm2 = new ConcreatePrototype2();PrototypeManager.AddPrototype(prototypeId2,pm2);//3、重新获取原型创建对象IPrototypeManager pm3 = PrototypeManager.GetPrototype(prototypeId2).Clone();pm3.SetName("李四");Console.WriteLine($"第二个实例是【{pm3}】");//4、有人注销了原型PrototypeManager.DelPrototype(prototypeId);//5、再次获取原型一来创建对象IPrototypeManager pm4 = PrototypeManager.GetPrototype(prototypeId).Clone();pm4.SetName("王五");Console.WriteLine($"第三个实例是【{pm4}】");}}//Class_end
}
5、运行结果:
三、项目源码工程
kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern
相关文章:
学习设计模式《八》——原型模式
一、基础概念 原型模式的本质是【克隆生成对象】; 原型模式的定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象 。 原型模式的功能: 1、通过克隆来创建新的对象实例; 2、为克隆出来的新对象实例复制…...
【MySQL】存储引擎 - MEMORY详解
📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! &…...
正则表达式实用指南:原理、场景、优化与引擎对比
正则表达式实用指南:原理、场景、优化与引擎对比 正则表达式(Regular Expression,简称 regex 或 regexp)是程序员处理文本数据时不可或缺的“瑞士军刀”。无论是表单校验、日志分析、数据清洗,还是敏感信息脱敏&#…...
Python3正则表达式:字符串魔法师的指南[特殊字符]♂️
Python3正则表达式 什么是正则表达式?在Python中使用正则表达式一、正则表达式基础语法:你的魔法咒语基本匹配符字符类:性格各异的字符们预定义字符类:常见角色的快捷方式重复限定符:贪婪的收集者贪婪vs非贪婪…...
k8s术语之CronJob
CronJob管理基于时间的Job,即: 在给定时间点只运行一次 周期性地在给定时间点运行 一个CronJob对象类似于crontab文件中的一行。它根据指定的预定计划周期地运行一个Job,格式可以参考Cron 前提条件 当前使用地Kubernetes集群,版本>1.8.对…...
常见的提示词攻击方法 和防御手段——提示词注入(Prompt Injection)攻击解析
提示词注入(Prompt Injection)攻击解析 提示词注入是一种针对大型语言模型(LLM)的新型攻击手段,攻击者通过精心设计的输入文本(提示词)操控AI模型的输出,使其执行非预期行为或泄露敏…...
软件逆向工程核心技术:脱壳原理与实战分析
目录 一、脱壳技术概述:从保护到还原的逆向之旅 1.1 脱壳技术的本质与核心价值 1.2 壳的分类与核心技术解析 1.3 学习路径:从压缩壳到加密壳的渐进式突破 二、脱壳三步法:系统化逆向工程框架 2.1 核心流程总览 2.2 实战案例࿱…...
C27-简单选择排序法
一 基本思想 每轮从待排序序列中选出最小或最大的元素,与待排序区间起始位置交换,逐步缩小待排序区间 二 算法实现 遍历数组:设数组长度为n,外层循环i从0到n-2(共n-1轮) 找最小值下标:内层循环j从i1到n-1,遍历待排序区间(i到n-1),记录找最小值下标min 交换元素:将arr[i]与a…...
【Redis】持久化与事务
文章目录 1. 持久化1.1 RDB(定期)1.1.1 触发方式1.1.2 触发流程 1.2. AOF(实时)1.2.1 设置AOF1.2.2 刷新策略1.2.3 重写机制 2. 事务2.1 redis事务概念2.2 事务操作 Mysql有几个特性: 原子性一致性隔离性,redis是串行的,自带隔离性持久性&…...
Web 自动化之 HTML JavaScript 详解
文章目录 一、HTML 常用标签二、javascript 脚本1、什么是 javascript(js)2、 js变量和函数3、js 弹窗处理4、js 流程控制语句和 switch 结构语句应用 一、HTML 常用标签 HTML:超文本标记语言 超文本:不仅只包含文字,还有超链接、视频…这些…...
【JavaScript】二十九、垃圾回收 + 闭包 + 变量提升
文章目录 1、作用域1.1 局部作用域1.2 全局作用域1.3 作用域链 2、JC垃圾回收机制♻️3、GC算法3.1 引用计数法3.2 标记清除法 4、闭包4.1 定义4.2 闭包的应用:实现数据的私有 5、变量提升 1、作用域 即一个范围,离开了这个范围,这个变量就不…...
Python在自动驾驶实时数据处理中的应用:让AI驾驶更智能、更高效
Python在自动驾驶实时数据处理中的应用:让AI驾驶更智能、更高效 近年来,自动驾驶技术的飞速发展离不开人工智能和数据处理的支撑,而Python作为AI与数据分析的核心编程语言,在自动驾驶实时数据处理方面扮演着不可或缺的角色。从传感器数据解析,到路径规划与决策优化,再到…...
功能安全的关键——MCU锁步核技术全解析(含真实应用方案)
随着智能汽车的发展,整车对功能安全的要求越来越高。特别是像电动助力转向(EPS)、制动控制系统、气囊控制器这类对“出错零容忍”的系统,已经广泛采用一种重要的安全架构——锁步核(Lockstep Core)。 今天我…...
Java实现桶排序算法
1. 桶排序原理图解 桶排序是一种基于分桶思想的非比较排序算法,适用于数据分布较为均匀的场景。其核心思想是将数据分散到有限数量的“桶”中,每个桶再分别进行排序(通常使用插入排序或其他简单的排序算法)。以下是桶排序的步骤&a…...
剖析 FFmpeg:从基本功能到过滤器,实现音视频处理的灵活性
目录 1.解复用2 解码2.1 音频解码2.2 视频解码 3 修饰3.1 avio3.2 重采样 4 过滤器4.1 过滤器基本知识4.2 简单过滤器4.3 复杂滤镜图 1.解复用 解复用就是把容器中的媒体流分离出来,方便我们对媒体流处理。 step1:对媒体文件上下文初始化 AVFormatCont…...
maven如何搭建自己的私服(LINUX版)?
环境准备 安装 JDK :确保系统已安装 JDK 8 或更高版本。可以通过以下命令安装 JDK: 安装 OpenJDK :sudo apt update && sudo apt install openjdk-11-jdk 安装 Oracle JDK :需要添加第三方仓库,例如 WebUpd8 …...
机器视觉的手机FPC油墨丝印应用
在现代智能手机制造过程中,精密的组件装配和质量控制是确保产品性能和用户体验的关键。其中,柔性印刷电路板(FPC)的油墨丝印工艺尤为关键,它不仅影响到电路板的美观,更直接关系到电路的导电性能和可靠性。而…...
AI原生手机:三大技术阵营的终极对决与未来展望
引言:AI手机时代的真正到来 2024年,智能手机行业迎来了一个历史性转折点——AI原生手机从概念走向主流。根据IDC最新报告,中国AI手机出货量同比激增591%,渗透率从2023年的3%飙升至22%。这一数据背后,是手机厂商在硬件…...
CFCA受邀参加盛京银行手机银行7.0发布会
4月30日,盛京银行举办手机银行7.0发布会。 盛京银行手机银行7.0围绕“慧享生活,财富随行”主题,聚焦便捷体验、财富管理、惠民生活,构建12大类服务,升级142项功能,全新设置信用卡频道,推出“云…...
IT/OT 融合架构下的工业控制系统安全攻防实战研究
1. 引言 随着工业 4.0 和智能制造的浪潮席卷全球,信息技术 (IT) 与运营技术 (OT) 的融合已成为不可逆转的趋势。这种融合旨在通过实时数据交换和分析,打破传统的信息孤岛,显著提升生产效率、优化决策、降低运营成本并增强市场竞争力。IT 系统…...
AI优化高频PCB信号完整性:猎板PCB的技术突破与应用实践
随着5G通信、AI服务器及新能源汽车的快速发展,高频PCB的信号完整性已成为决定电子产品性能的关键。本文以猎板PCB的技术实践为例,解析如何通过AI算法与精密制造工艺的结合,实现高频信号传输的极致优化,为行业提供高可靠性的解决方…...
【Bluedroid】蓝牙 SDP(服务发现协议)模块代码解析与流程梳理
本文深入剖析Bluedroid蓝牙协议栈中 SDP(服务发现协议)服务记录的全生命周期管理流程,涵盖初始化、记录创建、服务搜索、记录删除等核心环节。通过解析代码逻辑与数据结构,揭示各模块间的协作机制,包括线程安全设计、回…...
obj = null; 赋值null之前没有其他引用指向obj对象,那么,当obj=null时,会被垃圾回收机制立即回收吗?
不会立即回收。 具体原因是: 赋值 obj null; 后,对象变成“不可达”,符合垃圾回收条件,但垃圾回收器并不会立刻回收它。垃圾回收是CLR自动控制的非确定性过程,什么时候执行回收取决于系统内存压力、GC策略、分代情况…...
Android 数据持久化之 文件存储
在 Android 开发中,存储文件是一个常见的需求。 本文中介绍 openFileOutput 和 File 两种不同的方式来操作文件。 一、File 方式 根据文件的存储位置和访问权限,可以将文件存储分为内部存储(Internal Storage)和外部存储&#x…...
差分OPA verilogaA 模型
做电路设计,需要提前用理想模型如VerilogA模型做验证。这里分享一个由ahdlib库里单端opamp改造而来的差分opamp。参考何乐年的《模拟集成电路设计与仿真》10.4节423页; 描述的小信号模型如上。 VerilogA 用到了SRI/C,GBWgm/C,gaingm*r1等概念…...
oracle goldengate非并行进程转换为并行进程
oracle goldengate非并行进程转换为并行进程 在上一期的文章中写道了直接创建并行进程的方式对大事务进行分解,这对于新建立同步进程的时候提前规划是很有帮助的,但是如果对已经进行了同步的进程重新建立需要耗时比较长,Oracle提供了非并行进…...
58.[前端开发-前端工程化]Day05-webpack-Git安装-配置-Git命令
Git版本控制工具详解 1 邂逅版本控制工具 认识版本控制(版本控制) 版本控制的功能 版本控制的历史 2 集中式和分布式区别 集中式版本控制 分布式版本控制 3 Git的环境安装搭建 Git的安装 Bash – CMD – GUI 区别 Git的配置分类 Git的配置选项 Git的…...
CF每日5题
每日刷题两小时颐养天年 1855A 800 思维 将不高兴的同学计数cnt 不高兴的同学之间两两交换,一定不会在 p i i p_ii pii的位置上,贡献是cnt/2 如果cnt%2>0,那就多交换一次 void solve() {int n;cin>>n;int cnt0;forr(i,1,n){in…...
Redis实现分布式获取全局唯一自增ID的案例。
【1】简易自增版本(从 1 开始 1,2,3,...) 项目结构 下面是一个基于 RedisTemplate 实现的分布式全局唯一自增 ID 生成器的案例。适用于 Java Spring Boot 环境,利用 Redis 的原子操作 INCR 指令。 ✅ 原理说明 Redis 提供的 INCR 命令是原子性的&…...
创建型模式:工厂方法(Factory Method)模式
一、简介 工厂方法(Factory Method)模式是一种创建型设计模式,它定义了一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。在 C# 中,工厂方法模式提供了一种更灵活的对象创建方式,将对象的创建和使用分离,提高了代码的可维护性和…...
大型语言模型在网络安全领域的应用综述
大型语言模型在网络安全领域的应用综述 简介1. 引言1.1 背景与意义1.2 LLMs 的基本概念1.3 LLMs 在网络安全中的优势1.4 报告目标 2. 文献综述方法2.1 研究问题2.2 文献检索策略2.3 文献筛选标准 3. LLMs 在网络安全领域的应用3.1 软件和系统安全 (Software and System Securit…...
TDEngine 与 Grafana
目录 实践目录 Grafana 参考文档 实践目录 10.60.100.194:/home/dualven/tdengine Grafana systemctl status grafana-server http://10.60.100.194:3000/ 这个端口与mydoor的new server服务冲突 (同时只开一个) 参考文档 运行监…...
iPhone手机连接WiFi异常解决方法
iPhone手机连接WiFi异常解决方法 一、问题现象二、iPhone连不上可能的原因三、基础排查与快速修复第一步:重启大法第二步:忽略网络,重新认证第三步:关闭“私有无线局域网地址”第四步:修改DNS服务器第五步:还原网络设置四、路由器端排查及设置关闭MAC地址过滤或添加到白名…...
微服务不注册到nacos的方法
引言:在开发中,有时候多个开发一起开发,可能会同时注册到dev环境中,这样可能会影响dev环境,那么在idea添加2个参数即可解决 spring.cloud.nacos.discovery.register-enabled falsespring.cloud.nacos.discovery.enabled false...
Spring Boot + Vue 实现在线视频教育平台
一、项目技术选型 前端技术: HTML CSS JavaScript Vue.js 前端框架 后端技术: Spring Boot 轻量级后端框架 MyBatis 持久层框架 数据库: MySQL 5.x / 8.0 开发环境: IDE:Eclipse / IntelliJ IDEA JDK&…...
【嵌入式开发-SPI】
嵌入式开发-SPI ■ SPI简介■ SPI (Standard SPI)■ DSPI (Dual SPI)■ QSPI是 Queued SPI的简写 ■ SPI简介 SPI协议其实是包括:Standard SPI、Dual SPI和Queued SPI三种协议接口,分别对应3-wire, 4-wire…...
【链表扫盲】FROM GPT
链表是一种线性数据结构,由节点(Node)组成,每个节点包含两个部分: 数据域(data): 存储节点值。指针域(next): 存储指向下一个节点的引用。 链表…...
如何在macOS上通过SSHFS挂载远程文件系统
在macOS系统中,想要便捷地访问远程计算机上的目录?借助SSH文件系统(SSHFS)就能轻松实现。SSHFS是一款文件系统客户端,它基于SSH文件传输协议(SFTP)建立安全连接,进而实现对远程文件的…...
Android studio profiler使用
主要讲内存泄露排查 1、把怀疑内存泄露的页面都跑一边,然后回到初始页面 2、打开profile的home,找到Analysis Memory Usage,点击右下角start profiler task,开始分析内存,等待分析完成,分析过程中页面是卡…...
排序算法-选择排序
选择排序是一种简单直观的排序算法,其核心思想是每次从未排序的部分中选出最小(或最大)的元素,放到已排序部分的末尾。 选择排序步骤 初始化:将序列分为已排序部分(初始为空)和未排序部分&…...
云计算的基础概论
一、云计算基础概念 1. 云计算定义 • 英文:Cloud Computing • 定义:通过互联网(Internet)按需提供可扩展的计算资源(如服务器、存储、数据库、网络、软件等),用户无需管理底层基础设施。 …...
仿LISP运算 - 华为OD机试真题(A卷、JavaScript题解)
华为OD机试题库《C》限时优惠 9.9 华为OD机试题库《Python》限时优惠 9.9 华为OD机试题库《JavaScript》限时优惠 9.9 针对刷题难,效率慢,我们提供一对一算法辅导, 针对个人情况定制化的提高计划(全称1V1效率更高)。 看…...
数据透视表控件DHTMLX Pivot v2.1发布,新增HTML 模板、增强样式等多个功能
DHTMLX Pivot数据透视表能快速地对数据进行计数、总计、平均和执行许多其他操作。近日,DHTMLX Pivot发布了2.1版本,该版本扩展了开发人员通过新增的 CSS 样式选项、HTML 模板以及数字和日期的自定义格式修改表格外观的能力。此外,该版本还增加…...
简易的考试系统设计(Web实验)
简易的考试系统设计(Web实验) 1.实验内容与设计思想(一)实验需求(二)设计思路 2.代码展示3.实验小结 1.实验内容与设计思想 (一)实验需求 1.编写两个页面程序,一个HTML…...
C++之set和map的运用
目录 序列式容器和关联式容器 熟识set 在STL中的底层结构: set的构造和迭代器 set的增删查 multiset和set的差异 练习题: 熟识map map类的介绍 pair类型介绍 map的构造 map的增删查 map的数据修改 测试样例: multimap和map的差…...
基于智能家居项目 RGB彩灯(P9813)
一、P9813 是什么? P9813 是一颗专门用来控制 RGB LED灯珠 的芯片,也就是说,它能控制红色、绿色、蓝色三种灯光的亮度,从而调出各种颜色。它最常见的用途就是在各种“会变色”的灯带中。 它的通信方式非常简单,只需要…...
EMQX 作为 MQTT Broker,支持 MQTT over TCP 和 MQTT over WebSocket 两种协议
1. EMQX 支持的协议与端口 协议类型默认端口用途说明MQTT over TCP1883标准的 MQTT 协议,基于 TCP 传输(用于后端服务、物联网设备等)。MQTT over TLS8883加密的 MQTT over TCP(TLS/SSL 加密,安全性更高&am…...
软件测试学习笔记
第1章 绪论 软件测试 本质上说,就是寻找软件的缺陷、错误,对其质量度量的方法与过程。软件测试的一切活动都围绕着两个目标(验证是否符合需求,识别差异)而行进。它是测试思维、策略方针、设计实施的基本出发点。 学…...
Vue3 + Node.js 实现客服实时聊天系统(WebSocket + Socket.IO 详解)
Node.js 实现客服实时聊天系统(WebSocket Socket.IO 详解) 一、为什么选择 WebSocket? 想象一下淘宝客服的聊天窗口:你发消息,客服立刻就能看到并回复。这种即时通讯效果是如何实现的呢?我们使用 Vue3 作…...
python 上海新闻爬虫
1. 起因, 目的: 继续做新闻爬虫。我之前写过。此文先记录2个新闻来源。后面打算进行过滤,比如只选出某一个类型新闻。 2. 先看效果 过滤出某种类型的新闻,然后生成 html 页面,而且,自动打开这个页面。 比如科技犯罪…...