【设计模式】原型模式 Pototype Pattern
前面讲了创建一个对象实例的方法单例模式Singleton Pattern, 创造多个产品的工厂模式(简单工厂模式 Simple Factory Pattern, 工厂方法模式 FactoryMothed Pattern,抽象工厂模式 Abstract Factory Method),以及创建复杂对象的建造者模式 Builder Pattern, 这几几乎包含了产品创建的各个方方面,但是还有一种,那就是有自我创建能力的模式,这种模式能够创建出和自己相同或者相似的对象。生活中经常也会见到这方面的例子,比如蠕虫病毒的自我复制,细胞分裂以及自我繁殖,游戏角色的自我复制和分身等。
在软件开发中也会经常遇到这样的问题,最近在做项目的时候就碰到了这么一个需求,问卷调查试卷复用的问题。我们的系统用户组成是这样的,系统有一个超级管理员的角色,超级管理员可以干任何事情,系统中还接入了N多公司,每个公司有公司的管理员,公司管理员可以干超级管理员分配给该公司的相应权限, 那么这个问卷调查的需求是这样的:
1. 超级管理员可以创建问卷调查试卷,但是这个问卷调查的试卷不能直接使用,只能供各公司的管理员作为模板样例创建自己公司的问卷调查试卷。
2.各个公司的管理员可以创建自己公司的问卷调查试卷,仅供自己公司员工使用。
3.各个公司的管理员可以可以基于超级管理员创建的问卷调查试卷创建自己的模板,创建出来的问卷调查试卷仅供自己公司的员工使用。
4.公司管理员不能修改超级管理员创建的调查问卷试卷。
5. 超级管理员可以修改自己的问卷调查试卷, 并且不会影响各个公司之前根据问卷调查试卷创建出来的试卷。
需求用文字描述出来有点费劲,看下面这张图:
该怎么来实现呢? 这就是本文要讨论的主角原型模式Pototype Pattern ,也是最后一个创建型模式,原型模式就是为解决这类问题而生的:)。
一、原型模式的定义
原型模式(Prototype Pattern):使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式
二、原型模式的结构
1、Prototype(抽象原型类):
它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
2、ConcretePrototype(具体原型类):
它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
3、Client(客户类):
让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
三、原型模式的经典实现
原型模式的核心是克隆方法的实现:
1、通用实现方法
通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同:
- public abstract class Pototype
- {
- public string Some { get; set; }
- public abstract Pototype Clone();
- }
- public class ConcretePototype : Pototype
- {
- public override Pototype Clone()
- {
- Pototype pototype = new ConcretePototype();
- pototype.Some = this.Some;
- return pototype;
- }
- }
客户端调用:
- static void Main(string[] args)
- {
- Structure.Pototype pototype = new ConcretePototype();
- pototype.Some = "pototype";
- Structure.Pototype clone = pototype.Clone();
- Console.WriteLine("I'm old "+pototype.Some);
- Console.WriteLine("I'm clone "+clone.Some);
- Console.ReadKey();
- }
输出结果:
2、C#实现
C# 中有一个实现拷贝的方法MemberwiseClone,在克隆方法中我们用MemberwiseClone来实现克隆一个对象:
- public class CsharpPototype : Pototype
- {
- public override Pototype Clone()
- {
- return this.MemberwiseClone() as Pototype;
- }
- }
客户端调用:
- static void Main(string[] args)
- {
- Structure.Pototype pototype = new CsharpPototype();
- pototype.Some = "pototype";
- Structure.Pototype clone = pototype.Clone();
- Console.WriteLine("I'm old "+pototype.Some);
- Console.WriteLine("I'm clone "+clone.Some);
- Console.ReadKey();
- }
客户端输出和上面一样, 这种方式实现的是浅拷贝。
深拷贝和浅拷贝, 浅拷贝如果原型对象的成员是值类型那么就将原型对象复制一份给克隆对象,如果是引用类型,只将原型对象的地址复制一份给克隆对象,这时克隆对象和原型对象在内存中指向同一个对象,修改其中的一个会影响另一个。深拷贝,则是将原型对象拷贝一份给克隆对象,克隆对象和原型对象在内存中有独立的存储空间,一个改动了不会影响另一个。
四、原型模式实例
现在我们弄明白了原型模式,现在就来实现开头提出的问卷调查试卷创建的需求,因为试卷的内容比较复杂这里我们是找出一些核心的能够说明问题的模型来演示这个例子。我们先将试卷的内容假定为字符串类型。
这里我们将超级管理员创建的试卷命名为SuperSurveyPaper。
这里SuperSurveyPaper 充当抽象原型类,CompanyASurveyPaper 和CompanyBSurveyPaper 充当具体原型类。
1、浅拷贝实现:
- public abstract class SuperSurveyPaper
- {
- public string Name { get; set; }
- public string Content { get; set; }
- public abstract SuperSurveyPaper Clone();
- }
- public class CompanyASurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- return this.MemberwiseClone() as SuperSurveyPaper;
- }
- }
- public class CompanyBSurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- return this.MemberwiseClone() as SuperSurveyPaper;
- }
- }
客户端调用代码:
- static void Main(string[] args)
- {
- PototypeInstance.SuperSurveyPaper pototype = new CompanyASurveyPaper();
- pototype.Name = "SuperSurveyPaper ->Name";
- pototype.Content = "SuperSurveyPaper -> Content";
- PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
- Console.WriteLine("I'm old Name: " + pototype.Name);
- Console.WriteLine("I'm old Content: " + pototype.Content);
- Console.WriteLine("======================== ===========");
- Console.WriteLine("I'm Clone Name: " + clone.Name);
- Console.WriteLine("I'm Clone Content: " + clone.Content);
- Console.ReadKey();
- }
输出结果:
这里也可以加入配置通过反射来创建原型对象
在app.config 中加入配置:
- <appSettings>
- <add key="Pototype" value="DesignPattern.Pototype.PototypeInstance.CompanyBSurveyPaper"/>
- </appSettings>
调用段代码改成:
- static void Main(string[] args)
- {
- PototypeInstance.SuperSurveyPaper pototype;
- var setting = ConfigurationSettings.AppSettings["Pototype"];
- var obj = Type.GetType(setting);
- if (obj == null) return;
- pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper;
- if (pototype == null) return;
- pototype.Name = "SuperSurveyPaper ->Name";
- pototype.Content = "SuperSurveyPaper -> Content";
- PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
- Console.WriteLine("I'm old Name: " + pototype.Name);
- Console.WriteLine("I'm old Content: " + pototype.Content);
- Console.WriteLine("======================== ===========");
- Console.WriteLine("I'm Clone Name: " + clone.Name);
- Console.WriteLine("I'm Clone Content: " + clone.Content);
- Console.ReadKey();
- }
输出结果和上面一样
2、深拷贝
如果现在的试卷内容不是一个简单的字符串了而是一个对象:
- [Serializable]
- public class SurveyPaperModel
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- }
- [Serializable]
- public abstract class SuperSurveyPaper
- {
- public string Name { get; set; }
- public SurveyPaperModel Content { get; set; }
- public abstract SuperSurveyPaper Clone();
- }
- [Serializable]
- public class CompanyASurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- MemoryStream memoryStream = new MemoryStream();
- BinaryFormatter formatter = new BinaryFormatter();
- formatter.Serialize(memoryStream, this);
- memoryStream.Position = 0;
- return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
- }
- }
- [Serializable]
- public class CompanyBSurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- MemoryStream memoryStream = new MemoryStream();
- BinaryFormatter formatter = new BinaryFormatter();
- formatter.Serialize(memoryStream, this);
- memoryStream.Position = 0;
- return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
- }
- }
客户端调用:
- static void Main(string[] args)
- {
- PototypeInstance.SuperSurveyPaper pototype;
- var setting = ConfigurationSettings.AppSettings["Pototype"];
- var obj = Type.GetType(setting);
- if (obj == null) return;
- pototype = Activator.CreateInstance(obj) as PototypeInstance.SuperSurveyPaper;
- if (pototype == null) return;
- pototype.Name = "SuperSurveyPaper ->Name";
- pototype.Content = new SurveyPaperModel { FirstName = "Design", LastName = "Pattern" };
- PototypeInstance.SuperSurveyPaper clone = pototype.Clone();
- Console.WriteLine("I'm old Name: " + pototype.Name);
- Console.WriteLine("I'm old Content: " + pototype.Content.FirstName);
- Console.WriteLine("I'm old Content: " + pototype.Content.LastName);
- Console.WriteLine("======================== ===========");
- Console.WriteLine("I'm Clone Name: " + clone.Name);
- Console.WriteLine("I'm Clone Content: " + clone.Content.FirstName);
- Console.WriteLine("I'm Clone Content: " + clone.Content.LastName);
- Console.WriteLine("pototype==clone:" + clone.Equals(pototype));
- Console.ReadKey();
- }
输出结果:
五、原型模式的优点
- 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
- 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。
- 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
- 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。
六、原型模式的缺点
- 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。
- 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。
七、原型模式的使用场景
- 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。
- 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。
- 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便
八、扩展-原型管理器
原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责管理克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以在集合中找到该原型对象并克隆一个新对象。在原型管理器中针对抽象原型类进行编程,便于扩展。
还是应用上面的例子,现在加入B公司:
- [Serializable]
- public class SurveyPaperModel
- {
- public string FirstName { get; set; }
- public string LastName { get; set; }
- }
- [Serializable]
- public abstract class SuperSurveyPaper
- {
- public string Name { get; set; }
- public SurveyPaperModel Content { get; set; }
- public abstract SuperSurveyPaper Clone();
- }
- [Serializable]
- public class CompanyASurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- MemoryStream memoryStream = new MemoryStream();
- BinaryFormatter formatter = new BinaryFormatter();
- formatter.Serialize(memoryStream, this);
- memoryStream.Position = 0;
- return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
- }
- }
- [Serializable]
- public class CompanyBSurveyPaper : SuperSurveyPaper
- {
- public override SuperSurveyPaper Clone()
- {
- MemoryStream memoryStream = new MemoryStream();
- BinaryFormatter formatter = new BinaryFormatter();
- formatter.Serialize(memoryStream, this);
- memoryStream.Position = 0;
- return formatter.Deserialize(memoryStream) as SuperSurveyPaper;
- }
- }
- public class PototypeManager
- {
- static IDictionary<string, SuperSurveyPaper> superSurveyPapers = new Dictionary<string, SuperSurveyPaper>();
- static PototypeManager()
- {
- SuperSurveyPaper companyASurveyPaper = new CompanyASurveyPaper();
- companyASurveyPaper.Name = "Company A";
- companyASurveyPaper.Content= new SurveyPaperModel {FirstName="Michael",LastName="Du"};
- SuperSurveyPaper companyBSurveyPaper = new CompanyBSurveyPaper();
- companyBSurveyPaper.Name = "Company B";
- companyBSurveyPaper.Content = new SurveyPaperModel { FirstName = "Kevin", LastName = "Durant" };
- superSurveyPapers.Add("CompanyA", companyASurveyPaper);
- superSurveyPapers.Add("CompanyB", companyBSurveyPaper);
- }
- private PototypeManager (){}
- public SuperSurveyPaper GetSuperPaper(string key){
- return superSurveyPapers[key].Clone();
- }
- public void RegisterSurveyPaper(string key, SuperSurveyPaper ssp){
- superSurveyPapers.Add(key, ssp);
- }
- public static PototypeManager Instance
- {
- get{ return PototypeManagerInitializer.instance;}
- }
- private static class PototypeManagerInitializer
- {
- public static readonly PototypeManager instance=new PototypeManager();
- }
- }
这里的PototypeManager类使用了一个Singleton模式创建出来,在静态构造里初始化了原型对象,并将其注册在一个字典中,这个在项目中数据是从数据库中直接读取的。这个管理类还暴露了一个注册原型实例的方法,便于扩展和动态给管理器增加原型对象。在获取Clone对象的方法中直接将原型对象的一个Copy返回给客户程序。确保客户端得到的对象是一个全新的对象。
客户端调用代码:
- static void Main(string[] args)
- {
- PototypeInstance.SuperSurveyPaper pototype1, pototype2, pototype3, pototype4;
- pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyA");
- pototype2 = PototypeManager.Instance.GetSuperPaper("CompanyA");
- Console.WriteLine("I'm old Name: " + pototype1.Name);
- Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName);
- Console.WriteLine("I'm old Content: " + pototype1.Content.LastName);
- Console.WriteLine("I'm Clone Name: " + pototype2.Name);
- Console.WriteLine("I'm Clone Content: " + pototype2.Content.FirstName);
- Console.WriteLine("I'm Clone Content: " + pototype2.Content.LastName);
- Console.WriteLine("pototype1==pototype2:" + pototype2.Equals(pototype1));
- Console.WriteLine("======================== ===========");
- pototype3 = PototypeManager.Instance.GetSuperPaper("CompanyB");
- pototype4 = PototypeManager.Instance.GetSuperPaper("CompanyB");
- Console.WriteLine("I'm old Name: " + pototype3.Name);
- Console.WriteLine("I'm old Content: " + pototype3.Content.FirstName);
- Console.WriteLine("I'm old Content: " + pototype3.Content.LastName);
- Console.WriteLine("I'm Clone Name: " + pototype4.Name);
- Console.WriteLine("I'm Clone Content: " + pototype4.Content.FirstName);
- Console.WriteLine("I'm Clone Content: " + pototype4.Content.LastName);
- Console.WriteLine("pototype3==pototype4:" + pototype4.Equals(pototype3));
- Console.ReadKey();
- }
输出结果:
模拟ctrl+c,ctrl+v
使用原型模式的“自我”复制能力,我们可以很容易的实现,创建副本和撤销副本的功能, 在控制台中我们输入c 替代ctrl+c,输入:z 替代ctrl+z 来模拟这个拷贝和撤销的过程,首先我们创建一个原型对象,每次按C的时候使用最后clone出来的对象再克隆新的对象,并把这些对象依次保存在一个list中,当按Z的时候我们依次在list中移除最后加入的对象直到起初创建的原型对象为止, 简单的客户端代码实现如下:
- static List<PototypeInstance.SuperSurveyPaper> _list = new List<PototypeInstance.SuperSurveyPaper>();
- static List<SurveyPaperModel> _listModel = new List<SurveyPaperModel>
- {
- new SurveyPaperModel{FirstName="Terry",LastName="Go"},
- new SurveyPaperModel{FirstName="Ke",LastName="Be"},
- new SurveyPaperModel{FirstName="Lebron",LastName="Jimes"},
- new SurveyPaperModel{FirstName="Steve",LastName="Jo"},
- new SurveyPaperModel{FirstName="Stive",LastName="Kurry"},
- new SurveyPaperModel{FirstName="Henry",LastName="He"},
- new SurveyPaperModel{FirstName="Kevin",LastName="Druant"},
- new SurveyPaperModel{FirstName="Blue",LastName="Jhon"},
- new SurveyPaperModel{FirstName="Jerry",LastName="Ma"},
- new SurveyPaperModel{FirstName="Fred",LastName="Gao"},
- };
- static void Main(string[] args)
- {
- PototypeInstance.SuperSurveyPaper pototype1;
- pototype1 = PototypeManager.Instance.GetSuperPaper("CompanyB");
- Console.WriteLine("I'm old Name: " + pototype1.Name);
- Console.WriteLine("I'm old Content: " + pototype1.Content.FirstName);
- Console.WriteLine("I'm old Content: " + pototype1.Content.LastName);
- _list.Add(pototype1);
- while (true)
- {
- var key = Console.ReadKey();
- switch (key.Key)
- {
- case ConsoleKey.C:
- var pototypeLastInstance = _list.Last<PototypeInstance.SuperSurveyPaper>();
- var cloneFromPototypeLastInstance = pototypeLastInstance.Clone();
- cloneFromPototypeLastInstance.Name = "Version " + _list.Count;
- Random rd = new Random();
- cloneFromPototypeLastInstance.Content = _listModel[rd.Next(0, _listModel.Count)];
- _list.Add(cloneFromPototypeLastInstance);
- PrintList(_list);
- break;
- case ConsoleKey.Z:
- if (_list.Count > 1)
- _list.RemoveAt(_list.Count - 1);
- PrintList(_list);
- break;
- case ConsoleKey.Q:
- return;
- }
- }
- Console.ReadKey();
- }
- static void PrintList(List<PototypeInstance.SuperSurveyPaper> list)
- {
- Console.WriteLine("=========");
- var pototpe = list.Last();
- Console.WriteLine("History:" + pototpe.Name);
- Console.WriteLine("I'm Firstname: " + pototpe.Content.FirstName);
- Console.WriteLine("I'm LastName: " + pototpe.Content.LastName);
- }
客户端输出:
好了,到这里设计模式的创建型模式就全部讨论完了。下面接着讨论结构型模式。
【设计模式】原型模式 Pototype Pattern的更多相关文章
- C#设计模式——原型模式(Prototype Pattern)
一.概述 在软件开发中,经常会碰上某些对象,其创建的过程比较复杂,而且随着需求的变化,其创建过程也会发生剧烈的变化,但他们的接口却能比较稳定.对这类对象的创建,我们应该遵循依赖倒置原则,即抽象不应该依 ...
- 设计模式——原型模式(Prototype Pattern)
原型模式:用原型实例制定创建对象的种类,并且通过拷贝这些原型创建新的对象. UML 图: 原型类: package com.cnblog.clarck; /** * 原型类 * * @author c ...
- Net设计模式实例之原型模式( Prototype Pattern)
一.原型模式简介(Brief Introduction) 原型模式(Prototype Pattern):用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象. Specify the kin ...
- 乐在其中设计模式(C#) - 原型模式(Prototype Pattern)
原文:乐在其中设计模式(C#) - 原型模式(Prototype Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:weba ...
- 设计模式系列之原型模式(Prototype Pattern)——对象的克隆
说明:设计模式系列文章是读刘伟所著<设计模式的艺术之道(软件开发人员内功修炼之道)>一书的阅读笔记.个人感觉这本书讲的不错,有兴趣推荐读一读.详细内容也可以看看此书作者的博客https:/ ...
- 深入浅出设计模式——原型模式(Prototype Pattern)
模式动机在面向对象系统中,使用原型模式来复制一个对象自身,从而克隆出多个与原型对象一模一样的对象.在软件系统中,有些对象的创建过程较为复杂,而且有时候需要频繁创建,原型模式通过给出一个原型对象来指明所 ...
- 二十四种设计模式:原型模式(Prototype Pattern)
原型模式(Prototype Pattern) 介绍用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象.示例有一个Message实体类,现在要克隆它. MessageModel usin ...
- 【设计模式】Java设计模式 - 原型模式
[设计模式]Java设计模式 - 原型模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起 ...
- 10. 星际争霸之php设计模式--原型模式
题记==============================================================================本php设计模式专辑来源于博客(jymo ...
随机推荐
- java游戏开发杂谈 - 创建一个窗体
package game1; import javax.swing.JFrame; /** * java游戏开发杂谈 * ---demo1:创建一个窗体 * * @author 台哥 * @date ...
- Python爬虫使用lxml模块爬取豆瓣读书排行榜并分析
上次使用了BeautifulSoup库爬取电影排行榜,爬取相对来说有点麻烦,爬取的速度也较慢.本次使用的lxml库,我个人是最喜欢的,爬取的语法很简单,爬取速度也快. 本次爬取的豆瓣书籍排行榜的首页地 ...
- Spring Boot 入门(六):集成 treetable 和 zTree 实现树形图
本篇文章是接着Spring Boot 入门(五):集成 AOP 进行日志管理写的,主要集成了树形图,在部门列表或者权限列表中,树形图经常被用上.主要是根据相应的 API 凭借 html 字符串 1.t ...
- 配置Asp.Net Web项目NLog配置文件的位置
在使用NLog在asp.net项目中发现,如果想单独配其配置文件的位置时没有像Log4Net的特性配置方案,可以使其提供的 XmlLoggingConfiguration类来初始化: 见:https: ...
- nn.ConvTranspose2d的参数output_padding的作用
参考:https://blog.csdn.net/qq_41368247/article/details/86626446 使用前提:stride > 1 补充:same卷积操作 是通过padd ...
- ABP学习笔记总汇
首先立下一个目标,未来一段时间开始学习ABP. 先立一个flag.之后会再次更新目录和文章连接 目录 1.ABP学习笔记(1)-使用mysql
- Velocity 模板引擎的应用
springboot三层机构,还有数据映射待实体.肯定需要一套模板引擎呀.那不然还手写不成. 根据我们的实际业务需求,我添加了一套数据库反向生成实体类的模板,用的是Velocity 的引擎. 不多说直 ...
- 浅论各种调试接口(SWD、JTAG、Jlink、Ulink、STlink)的区别
JTAG协议 JTAG(Joint Test Action Group,联合测试行动小组)是一种国际标准测试协议(IEEE 1149.1兼容),主要用于芯片内部测试.现在多数的高级器件都支持JTAG协 ...
- synchronized的四种作用域以及不能被继承解析
synchronized是java中用于同步的关键字,其典型的作用域如下所示. 1 对象锁 @Slf4j public class SynchronizedExample1 { private fin ...
- 消除点击连接或者按钮或者执行onclick事件时出现的边框
css中添加 *:not(input) { font-family: sans-serif; font-size-adjust: none; -webkit-user-select: none; -w ...