当前位置: 首页 > news >正文

设计模式之原型模式与建造者模式详解和应用

目录

  • 1 原型模式
    • 1.1 原型模式定义
    • 1.2 原型模式的应用场景
    • 1.3 原型模式的通用写法(浅拷贝)
    • 1.4 使用序列化实现深度克隆
    • 1.5 克隆破坏单例模式
    • 1.6 原型模式在源码中的应用
    • 1.7 原型模式的优缺点
    • 1.8 总结
  • 2 建造者模式
    • 2.1 建造者模式定义
    • 2.2 建造者模式的应用场景
    • 2.3 建造者模式的基本写法
    • 2.4 建造者模式的链式写法
    • 2.5 建造者模式应用案例
    • 2.6 建造者模式在源码中的体现
    • 2.7 建造者模式的优缺点
    • 2.8 建造者模式和工厂模式的区别


1 原型模式

1.1 原型模式定义

原型模式(PrototypePattern)是指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,属于创建型模式。

官方原文:Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.

原型模式的核心在于拷贝原型对象。以系统中已存在的一个对象为原型,直接基于内存二进制流进行拷贝,无需再经历耗时的对象初始化过程(不调用构造函数),性能提升许多。当对象的构建过程比较耗时时,可以利用当前系统中已存在的对象作为原型,对其进行克隆(一般是基于二进制流的复制),躲避初始化过程,使得新对象的创建时间大大减少。下面,我们来看看原型模式类结构图:

从 UML 图中,我们可以看到,原型模式 主要包含三个角色:

客户(Client):客户类提出创建对象的请求。

抽象原型(Prototype):规定拷贝接口。

具体原型(Concrete Prototype):被拷贝的对象。

注:对不通过 new 关键字,而是通过对象拷贝来实现创建对象的模式就称作原型模式。

1.2 原型模式的应用场景

你一定遇到过大篇幅getter、setter赋值的场景。

代码非常工整,命名非常规范,注释也写的很全面,其实这就是原型模式的需求场景。但是,大家觉得这样的代码优雅吗?我认为,这样的代码属于纯体力劳动。那原型模式,能帮助我们解决这样的问题。

原型模式主要适用于以下场景:

1、类初始化消耗资源较多。

2、new产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)

3、构造函数比较复杂。

4、循环体中生产大量对象时。

在 Spring 中,原型模式应用得非常广泛。例如 scope=“prototype”,在我们经常用的**JSON.parseObject()**也是一种原型模式。

1.3 原型模式的通用写法(浅拷贝)

一个标准的原型模式代码,应该是这样设计的。先创建原型IPrototype接口:

public interface IPrototype<T> {T clone();
}

创建具体需要克隆的对象ConcretePrototype

public class ConcretePrototype implements IPrototype {private int age;private String name;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic ConcretePrototype clone() {ConcretePrototype concretePrototype = new ConcretePrototype();concretePrototype.setAge(this.age);concretePrototype.setName(this.name);return concretePrototype;}@Overridepublic String toString() {return "ConcretePrototype{" +"age=" + age +", name='" + name + '\'' +'}';}
}

测试代码:

public class Client {public static void main(String[] args) {//创建原型对象ConcretePrototype prototype = new ConcretePrototype();prototype.setAge(18);prototype.setName("Oldlu");System.out.println(prototype);//拷贝原型对象ConcretePrototype cloneType = prototype.clone();System.out.println(cloneType);}
}

运行结果:

ConcretePrototype{age=18, name='Oldlu'}
ConcretePrototype{age=18, name='Oldlu'}

这时候,有小伙伴就问了,原型模式就这么简单吗?对,就是这么简单。在这个简单的场景之下,看上去操作好像变复杂了。但如果有几百个属性需要复制,那我们就可以一劳永逸。但是,上面的复制过程是我们自己完成的,在实际编码中,我们一般不会浪费这样的体力劳动,JDK已经帮我们实现了一个现成的API,我们只需要实现Cloneable接口即可。来改造一下代码,修改ConcretePrototype类:

@Data
public class ConcretePrototype implements Cloneable {private int age;private String name;@Overridepublic ConcretePrototype clone() {try {return (ConcretePrototype)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}
}

重新运行,也会得到同样的结果。

有了JDK的支持再多的属性复制我们也能轻而易举地搞定了。下面我们再来做一个测试,给ConcretePrototype增加一个个人爱好的属性hobbies:

@Data
public class ConcretePrototype implements Cloneable {private int age;private String name;private List<String> hobbies;@Overridepublic ConcretePrototype clone() {try {return (ConcretePrototype)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}
}

修改客户端测试代码:

public class Client {public static void main(String[] args) {//创建原型对象ConcretePrototype prototype = new ConcretePrototype();prototype.setAge(18);prototype.setName("Oldlu");List<String> hobbies = new ArrayList<String>();hobbies.add("书法");hobbies.add("美术");prototype.setHobbies(hobbies);//拷贝原型对象ConcretePrototype cloneType = prototype.clone();cloneType.getHobbies().add("技术控");System.out.println("原型对象:" + prototype);System.out.println("克隆对象:" + cloneType);System.out.println(prototype == cloneType);System.out.println("原型对象的爱好:" + prototype.getHobbies());System.out.println("克隆对象的爱好:" + cloneType.getHobbies());System.out.println(prototype.getHobbies() == cloneType.getHobbies());}
}

运行结果:

原型对象:ConcretePrototype(age=18, name=Oldlu, hobbies=[书法, 美术, 技术控])
克隆对象:ConcretePrototype(age=18, name=Oldlu, hobbies=[书法, 美术, 技术控])
false
原型对象的爱好:[书法, 美术, 技术控]
克隆对象的爱好:[书法, 美术, 技术控]
true

我们给,复制后的克隆对象新增一项爱好,发现原型对象也发生了变化,这显然不符合我们的预期。因为我们希望克隆出来的对象应该和原型对象是两个独立的对象,不应该再有联系了。从测试结果分析来看,应该是hobbies共用了一个内存地址,意味着复制的不是值,而是引用的地址。这样的话,如果我们修改任意一个对象中的属性值,prototype 和cloneType的hobbies值都会改变。这就是我们常说的浅克隆。只是完整复制了值类型数据,没有赋值引用对象。换言之,所有的引用对象仍然指向原来的对象,显然不是我们想要的结果。那如何解决这个问题呢?下面我们来看深度克隆继续改造。

扩展知识:String对象在内存中是不可变的(final类型),虽然克隆后,两个对象String的引用指向的是同一个内存地址,但是如果给克隆后的对象的String属性改变值,那么相当于是在内存中重新开辟了一块内存来存储这个改变的值,而此时的String属性对象就指向了该内存值,所以这个时候克隆前和克隆后对象的String属性是不一样的)。

String 每次赋值,相当于new String()。

1.4 使用序列化实现深度克隆

在上面的基础上我们继续改造,来看代码,增加一个deepClone()方法:

@Data
public class ConcretePrototype implements Cloneable,Serializable {private int age;private String name;private List<String> hobbies;@Overridepublic ConcretePrototype clone() {try {return (ConcretePrototype)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}public ConcretePrototype deepClone(){try {ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(this);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bis);return (ConcretePrototype)ois.readObject();}catch (Exception e){e.printStackTrace();return null;}}
}

来看客户端调用代码:

public class Client {public static void main(String[] args) {//创建原型对象ConcretePrototype prototype = new ConcretePrototype();prototype.setAge(18);prototype.setName("Oldlu");List<String> hobbies = new ArrayList<String>();hobbies.add("书法");hobbies.add("美术");prototype.setHobbies(hobbies);//拷贝原型对象ConcretePrototype cloneType = prototype.deepClone();cloneType.getHobbies().add("技术控");System.out.println("原型对象:" + prototype);System.out.println("克隆对象:" + cloneType);System.out.println(prototype == cloneType);System.out.println("原型对象的爱好:" + prototype.getHobbies());System.out.println("克隆对象的爱好:" + cloneType.getHobbies());System.out.println(prototype.getHobbies() == cloneType.getHobbies());}
}

运行程序,我们发现得到了我们期望的结果:

原型对象:ConcretePrototype(age=18, name=Oldlu, hobbies=[书法, 美术])
克隆对象:ConcretePrototype(age=18, name=Oldlu, hobbies=[书法, 美术, 技术控])
false
原型对象的爱好:[书法, 美术]
克隆对象的爱好:[书法, 美术, 技术控]
false

1.5 克隆破坏单例模式

如果我们克隆的目标的对象是单例对象,那意味着,深克隆就会破坏单例。实际上防止克隆破坏单例解决思路非常简单,禁止深克隆便可。要么你我们的单例类不实现 Cloneable 接口;要么我们重写clone()方法,在clone方法中返回单例对象即可,具体代码如下:

@Override
protected Object clone() throws CloneNotSupportedException {return INSTANCE;
}

1.6 原型模式在源码中的应用

先来JDK中Cloneable接口:

public interface Cloneable {
}

接口定义还是很简单的,我们找源码其实只需要找到看哪些接口实现了 Cloneable 即可。来看ArrayList类的实现。

Object方法

protected native Object clone() throws CloneNotSupportedException;

复制

ArrayList是实现的clone方法

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandOldluAccess, Cloneable, java.io.Serializable
{public Object clone() {try {ArrayList<?> v = (ArrayList<?>) super.clone();v.elementData = Arrays.copyOf(elementData, size);v.modCount = 0;return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError(e);}}
}

我们发现方法中只是将List中的元素循环遍历了一遍。这个时候我们再思考一下,是不是这种形式就是深克隆呢?其实用代码验证一下就知道了,继续修改 ConcretePrototype 类,增加一个deepCloneHobbies()方法:

@Data
public class ConcretePrototype implements Cloneable,Serializable {...public ConcretePrototype deepCloneHobbies(){try {ConcretePrototype result = (ConcretePrototype)super.clone();result.hobbies = (List)((ArrayList)result.hobbies).clone();return result;} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}...
}

修改客户端代码:

public class Client {public static void main(String[] args) {...//拷贝原型对象ConcretePrototype cloneType = prototype.deepCloneHobbies();...}
}

运行也能得到期望的结果。但是这样的代码,其实是硬编码,如果在对象中声明了各种集合类型,那每种情况都需要单独处理。因此,深克隆的写法,一般会直接用序列化来操作。

1.7 原型模式的优缺点

优点:

1、性能优良,Java自带的 原型模式 是基于内存二进制流的拷贝,比直接new一个对象性能上提升了许多。

2、可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。

缺点:

1、需要为每一个类配置一个克隆方法。

2、克隆方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违反了开闭原则。

3、在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深拷贝、浅拷贝需要运用得当。

1.8 总结

克隆方式:1.序列化 反序列化 2.jsonobject 3浅克隆加赋值

浅克隆:继承Cloneable接口的都是浅克隆。

深克隆两种方式:序列化,转JSON。

2 建造者模式

2.1 建造者模式定义

建造者模式(Builder Pattern)是将一个复杂对象的构建过程与它的表示分离,使得同样的构建过程可以创建不同的表示,属于创建型模式。使用建造者模式对于用户而言只需指定需要建造的类型就可以获得对象,建造过程及细节不需要了解。

官方原文:Separate the construction of a cOldluplex object frOldlu its representation so that the same construction process can create different representations.

建造者模式适用于创建对象需要很多步骤,但是步骤的顺序不一定固定。如果一个对象有非常复杂的内部结构(很多属性),可以将复杂对象的创建和使用进行分离。先来看一下建造者模式的类图:

在这里插入图片描述

建造者模式的设计中主要有四个角色:

1、产品(Product):要创建的产品类对象

2、建造者抽象(Builder):建造者的抽象类,规范产品对象的各个组成部分的建造,一般由子类实现具体的建造过程。

3、建造者(ConcreteBuilder):具体的Builder类,根据不同的业务逻辑,具体化对象的各个组成部分的创建。

4、调用者(Director):调用具体的建造者,来创建对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

2.2 建造者模式的应用场景

建造者模式适用于一个具有较多的零件的复杂产品的创建过程,由于需求的变化,组成这个复杂产品的各个零件经常猛烈变化,但是它们的组合方式却相对稳定。

  1. 相同的方法,不同的执行顺序,产生不同的结果时
  2. 多个部件或零件,都可以装配到一个对象中,但是产生的结果又不相同。
  3. 产品类非常复杂,或者产品类中的调用顺序不同产生不同的作用。
  4. 当初始化一个对象特别复杂,参数多,而且很多参数都具有默认值时。

建造者模式,只关注用户需要什么,将最少的关键字传过来,生成你想要的结果。

实际顺序是在build方法里面。那是顺序和条件都确定了。每个顺序和条件都分别存储下来了。判断有没有,有就添加到product后面。当然就是先判断条件再判断order顺序了

2.3 建造者模式的基本写法

我们还是以课程为例,一个完整的课程需要由PPT课件、回放视频、课堂笔记、课后作业组成,但是这些内容的设置顺序可以随意调整,我们用建造者模式来代入理解一下。首先我们创建一个需要构造的产品类Course:

@Data
public class Course {private String name;private String ppt;private String video;private String note;private String hOldluework;
}

然后创建建造者类CourseBuilder,将复杂的构造过程封装起来,构造步骤由用户决定:

public class CourseBuilder{private Course course = new Course();public void addName(String name) {course.setName(name);}public void addPPT(String ppt) {course.setPpt(ppt);}public void addVideo(String video) {course.setVideo(video);}public void addNote(String note) {course.setNote(note);}public void addHOldluework(String hOldluework) {course.setHOldluework(hOldluework);}public Course build() {return course;}
}

编写测试类:

public class Test {public static void main(String[] args) {CourseBuilder builder = new CourseBuilder();builder.addName("设计模式");builder.addPPT("【PPT课件】");builder.addVideo("【回放视频】");builder.addNote("【课堂笔记】");builder.addHOldluework("【课后作业】");System.out.println(builder.build());}
}

运行结果:

Course(name=设计模式, ppt=PPT课件】, video=【回放视频】, note=【课堂笔记】, hOldluework=【课后作业】)

来看一下类结构图:

img

2.4 建造者模式的链式写法

在平时的应用中,建造者模式通常是采用链式编程的方式构造对象,下面我们来一下演示代码,修改CourseBuilder类,将Course变为CourseBuilder的内部类。然后,将构造步骤添加进去,每完成一个步骤,都返回this:

public class CourseBuilder {private Course course = new Course();public CourseBuilder addName(String name) {course.setName(name);return this;}public CourseBuilder addPPT(String ppt) {course.setPpt(ppt);return this;}public CourseBuilder addVideo(String video) {course.setVideo(video);return this;}public CourseBuilder addNote(String note) {course.setNote(note);return this;}public CourseBuilder addHOldluework(String hOldluework) {course.setHOldluework(hOldluework);return this;}public Course build() {return this.course;}@Datapublic class Course {private String name;private String ppt;private String video;private String note;private String hOldluework;}
}

客户端使用:

public class Test {public static void main(String[] args) {CourseBuilder builder = new CourseBuilder().addName("设计模式").addPPT("【PPT课件】").addVideo("【回放视频】").addNote("【课堂笔记】").addHOldluework("【课后作业】");System.out.println(builder.build());}
}

这样写法是不是很眼熟,好像在哪见过呢?后面我们分析建造者模式在源码中的应用大家就会明白。接下来,我们再来看一下类图的变化:

img

2.5 建造者模式应用案例

下面我们再来看一个实战案例,这个案例参考了开源框架JPA的SQL构造模式。是否记得我们在构造SQL查询条件的时候,需要根据不同的条件来拼接SQL字符串。如果查询条件复杂的时候,我们SQL拼接的过程也会变得非常复杂,从而给我们的代码维护带来非常大的困难。因此,我们用建造者类QueryRuleSqlBuilder 将复杂的构造 SQL 过程进行封装,用 QueryRule 对象专门保存 SQL 查询时的条件,最后根据查询条件,自动生成SQL语句。来看代码,先创建QueryRule类:

/*** QueryRule,主要功能用于构造查询条件*/
public final class QueryRule implements Serializable
{...    /*** 添加升序规则* @param propertyName* @return*/public QueryRule addAscOrder(String propertyName) {this.ruleList.add(new Rule(ASC_ORDER, propertyName));return this;}public QueryRule andEqual(String propertyName, Object value) {this.ruleList.add(new Rule(EQ, propertyName, new Object[] { value }).setAndOr(AND));return this;}public QueryRule andLike(String propertyName, Object value) {this.ruleList.add(new Rule(LIKE, propertyName, new Object[] { value }).setAndOr(AND));return this;}...
}

然后,创建QueryRuleSqlBuilder类:

/*** 根据QueryRule自动构建sql语句*/
public class QueryRuleSqlBuilder {...   /*** 处理like* @param rule*/private  void processLike(QueryRule.Rule rule) {if (ArrayUtils.isEmpty(rule.getValues())) {return;}Object obj = rule.getValues()[0];if (obj != null) {String value = obj.toString();if (!StringUtils.isEmpty(value)) {value = value.replace('*', '%');obj = value;}}add(rule.getAndOr(),rule.getPropertyName(),"like","%"+rule.getValues()[0]+"%");}/*** 处理 =* @param rule*/private  void processEqual(QueryRule.Rule rule) {if (ArrayUtils.isEmpty(rule.getValues())) {return;}add(rule.getAndOr(),rule.getPropertyName(),"=",rule.getValues()[0]);}/*** 处理 order by* @param rule 查询规则*/private void processOrder(Rule rule) {switch (rule.getType()) {case QueryRule.ASC_ORDER:// propertyName非空if (!StringUtils.isEmpty(rule.getPropertyName())) {orders.add(Order.asc(rule.getPropertyName()));}break;case QueryRule.DESC_ORDER:// propertyName非空if (!StringUtils.isEmpty(rule.getPropertyName())) {orders.add(Order.desc(rule.getPropertyName()));}break;default:break;}}...
}

创建Order类:

/*** sql排序组件*/
public class Order {private boolean ascending; //升序还是降序private String propertyName; //哪个字段升序,哪个字段降序public String toString() {return propertyName + ' ' + (ascending ? "asc" : "desc");}/*** Constructor for Order.*/protected Order(String propertyName, boolean ascending) {this.propertyName = propertyName;this.ascending = ascending;}/*** Ascending order** @param propertyName* @return Order*/public static Order asc(String propertyName) {return new Order(propertyName, true);}/*** Descending order** @param propertyName* @return Order*/public static Order desc(String propertyName) {return new Order(propertyName, false);}
}

编写测试代码:

public class Test {public static void main(String[] args) {QueryRule queryRule = QueryRule.getInstance();queryRule.addAscOrder("age");queryRule.andEqual("addr","ShanDong");queryRule.andLike("name","Oldlu");QueryRuleSqlBuilder builder = new QueryRuleSqlBuilder(queryRule);System.out.println(builder.builder("t_member"));System.out.println("Params: " + Arrays.toString(builder.getValues()));}
}

这样一来,我们的客户端代码就非常清朗,来看运行结果:

select * frOldlu t_member where  addr = ?  and name like ?  order by age asc
Params: [ShanDong, %Oldlu%]

2.6 建造者模式在源码中的体现

下面来看建造者模式在哪些源码中有应用呢?首先来看JDK的StringBuilder,它提供append()方法,给我们开放构造步骤,最后调用toString()方法就可以获得一个构造好的完整字符串,源码如下:

public final class StringBuilderextends AbstractStringBuilderimplements java.io.Serializable, CharSequence
{public StringBuilder append(StringBuffer sb) {super.append(sb);return this;}
}

在MyBatis中也有体现,比如CacheBuilder类。

同样在 MyBatis 中,比如 SqlSessionFactoryBuilder 通过调用 build()方法获得的是一个SqlSessionFactory 类。

当然,在 Spring中自然也少不了,比如 BeanDefinitionBuilder 通过调用getBeanDefinition()方法获得一个BeanDefinition对象。

2.7 建造者模式的优缺点

建造者模式的优点:

1、封装性好,创建和使用分离;

2、扩展性好,建造类之间独立、一定程度上解耦。

建造者模式的缺点:

1、产生多余的Builder对象;

2、产品内部发生变化,建造者都要修改,成本较大。

2.8 建造者模式和工厂模式的区别

建造者模式和工厂模式的区别

1、建造者模式更加注重方法的调用顺序,工厂模式注重于创建对象。

2、创建对象的力度不同,建造者模式创建复杂的对象,由各种复杂的部件组成,工厂模式创建出来的都一样。

3、关注重点不一样,工厂模式模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象由哪些部件组成。

4、建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。

可以理解为工厂创建过程是静态的,构建者模式创建过程经过外放而变成动态的。

相关文章:

设计模式之原型模式与建造者模式详解和应用

目录1 原型模式1.1 原型模式定义1.2 原型模式的应用场景1.3 原型模式的通用写法&#xff08;浅拷贝&#xff09;1.4 使用序列化实现深度克隆1.5 克隆破坏单例模式1.6 原型模式在源码中的应用1.7 原型模式的优缺点1.8 总结2 建造者模式2.1 建造者模式定义2.2 建造者模式的应用场…...

C语言(函数和递归)

函数是完成特定任务的独立程序代码单元。 目录 一.函数 1.创建一个简单的函数 2.定义带形式参数的函数 3.使用return从函数中返回值 二.递归 一.函数 1.创建一个简单的函数 #include <stdio.h> void print(void); //函数原型 int main(){ print(); //函…...

快乐的shell命令行

快乐的shell命令行 PART1——基础 1.权限 #超级用户权限$普通用户 2.复制粘贴 复制&#xff1a;鼠标左键沿着文本拖动高亮的文本被复制到X管理的缓冲区&#xff08;或者双击一个单词&#xff09;粘贴&#xff1a;鼠标中键 3.简单命令 时间和日期date当前月份的日历cal磁…...

大数据面试题flume篇

1.Flume 的Source&#xff0c;Sink&#xff0c;Channel 的作用&#xff1f;你们Source 是什么类型&#xff1f; 1. 作用 &#xff08;1&#xff09;Source组件是专门用来收集数据的&#xff0c;可以处理各种类型、各种格式的日志数据&#xff0c;包括 avro、thrift、exec、jm…...

零信任-深信服零信任aTrust介绍(5)

​深信服零信任aTrust介绍 深信服是国内领先的互联网信任服务提供商&#xff0c;也是国内首家通过认证的全球信任服务商。深信服零信任是其中一项核心的信任技术&#xff0c;主要针对身份认证、数字签名、数字证书等方面的信任问题。 深信服零信任提供了一种新的安全保护模式…...

UVa 1343 The Rotation Game 旋转游戏 IDA* BFS 路径还原

题目链接&#xff1a;The Rotation Game 题目描述&#xff1a; 给定二十四个整数&#xff0c;这二十四个整数由八个一&#xff0c;八个二&#xff0c;八个三组成&#xff0c;从左到右&#xff0c;从上到下依次描述下图方格中的数字&#xff1a; 例如上图左边对应的输入就是[1,…...

硬件学习 软件Cadence day02 画原理图的基本操作 (键盘快捷键 , 原理图设计流程 , 从开始到导出网表流程)

1. ORCAD Capture cls 界面的快捷键 键盘 按键对应的操作I放大 &#xff08;可以滚轮操作&#xff09;O缩小 &#xff08;可以滚轮操作&#xff09;W画线Esc退出现在的状态 &#xff08;画图界面 右键 End xxx&#xff09;N放置网络标号J放置节点 (控制…...

【python】基于Socket的聊天室Python开发

基于Socket的聊天室Python开发一、Socket简述二、创建服务端Server2.1 创建服务端初始化2.2 监听客户端连接2.3 处理客户端消息三、创建客户端Client3.1 创建服务端初始化3.2 发送消息3.3 接收消息3.3 线程工作3.4 线程工作是不是挺好玩的呢&#xff1f;也可以作为课程设计哦&a…...

2023想转行软件测试的看过来,你想要了解的薪资、前景、岗位方向、学习路线都讲明白了

在过去的一年中&#xff0c;软件测试行业发展迅速&#xff0c;随着数字化技术应用的广泛普及&#xff0c;业界对于软件测试的要求也在持续迭代与增加。 同样的&#xff0c;有市场就有需求&#xff0c;软件测试逐渐成为企业中不可或缺的岗位&#xff0c;作为一个高薪又需求广的…...

TortoiseSVN的使用

基本概念 版本库 SVN保持数据的地方&#xff0c;所有的文件都保存在这个库中&#xff0c;Tortoise访问的就是远程服务器上的Subversion版本库。 工作拷贝 就是工作副本&#xff0c;可将版本库的文件拷贝到本地中&#xff0c;可以任意修改&#xff0c; 不会影响版本库。在你…...

操作系统(day09) -- 连续分配管理方式

连续分配管理方式 单元连续分配 动态分区分配 1.系统要用什么样的数据结构记录内存的使用情况&#xff1f; 两种常用的数据结构 空闲分区表 每个空闲分区对应一个表项。表项中包含分区号、分区大小、分区起始地址等信息空闲分区链 每个分区的起始部分和末尾部分分别设置前向…...

APISpace 带你一起走进西湖美景

俗话说&#xff1a;“上有天堂&#xff0c;下有苏杭”。 “欲把西湖比西子&#xff0c;浓妆艳抹总相宜” 今天我就带大家走进杭州的西湖美景。自古以来&#xff0c;文人歌者面对西湖美景留下千古绝句&#xff0c;还以西湖为背景书写了一段段动人的爱情传说。 天生自带浪漫色…...

傻白探索Chiplet,Design Space Exploration for Chiplet-Assembly-Based Processors(十三)

阅读了Design Space Exploration for Chiplet-Assembly-Based Processors这篇论文&#xff0c;是关于chiplet设计空间探索的&#xff0c;个人感觉核心贡献有两个&#xff1a;1.提出使用整数线性规划算法进行Chiplet的选择&#xff1b;2.基于RE和NRE提出了一个cost模型&#xff…...

系统分析师真题2020试卷相关概念一

对象系统测试的基本概念: 面向对象系统的单元测试包括方法层次的测试、类层次的测试和类树层次的测试。方法层次的测试类似于传统软件测试中对单个函数的测试; 测试技术: 方法层次的测试,单个函数的测试;常用的技术:等价类划分测试、组合功能测试、递归函数的测试和多态…...

20230215_数据库过程_渠道业务计算过程

—20221209 渠道产能 —自有人员工号表 shzc.xc_qdcn_pgtx_opertype —select * from shzc.xc_qdcn_pgtx_opertype for update ; —渠道基础目录 shzc.xc_qdcn_pgtx_qdtype —select * from shzc.xc_qdcn_pgtx_qdtype for update ; SQL_STRING:‘update shzc.xc_qdcn_pgtx_q…...

【C++】Expression的学习笔记

关于不同类别表达式的举例&#xff0c;请参考博文《C 中的值类别》 1. 左值和右值的简单理解 左值对应了具有内存地址的对象&#xff0c;而右值仅仅是临时使用的值对象。&#xff08;引用自博文《C 中的值类别》&#xff09;左值有名称&#xff08;变量或常量名称&#xff09…...

[数据库迁移]-MySQL常见问题

[数据库迁移]-MySQL常见问题 森格 | 2023年2月 介绍&#xff1a;记录在MySQL数据库迁移过程中遇到的问题&#xff0c;以及解决方案。 文章目录[数据库迁移]-MySQL常见问题一、背景二、常见问题2.1 ERROR 20032.2 ERROR 12732.3 ERROR 10712.4 视图权限2.5 ERROR 1062三、总结一…...

C语言编译过程

C语言编译过程1、C语言编译过程2、单c文件编译实践3、多c文件编译实践4、define4.1、不带参宏4.2、带参宏4.3、带参宏和带参函数的区别5、选择性编译ifdef、ifndef、if5.1、#ifdef5.2、#ifndef5.3、#if6、静态库和动态链接库6.1、静态库实践6.1.1、将mylib.c制作成静态库6.1.2、…...

前端学习 ---常用标签

常用标签 1,文本标签 文本标签是双标签&#xff0c;自带加粗效果&#xff0c;有自己对应的文本大小&#xff0c;并且独占一行&#xff0c;有默认间距 一级标签&#xff1a;< h1 > < /h1 > 二级标签&#xff1a;< h2 > < /h2> 三级标签&#xff1a;&l…...

2023年PMP考试难不难?

整个考试的考察方向转向还是比较大的&#xff0c;基本上以“价值传递”和“以人为本”这两个出发点来考察项目经理所需要的能力。 1}新版提纲题目数量的变化 总题量从200道减少到180道&#xff0c;所以答题时间上相对变的宽裕一些。考试时间230分钟&#xff0c;中间有十分钟休…...

Netty 入门

文章目录一、概述1.1 Netty 是什么&#xff1f;1.2 Netty 的地位1.3 Netty 的优势二、Hello World2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理三、组件3.1 EventLoop3.2 演示 NioEventLoop 处理 io 事件3.3 演示 NioEventLoop 处理普通任务3.4 演示 NioEventLoop 处理定时任务…...

收藏|一文掌握数据分析在企业的实际流程

一、数据分析概念 1.1 数据分析 是指用适当的统计分析方法对收集来的大量数据进行分析&#xff0c;将他们加以汇总和理解并消化&#xff0c;以求最大化地开发数据的功能&#xff0c;发挥数据的作用。 1.2 数据分析包括 描述性数据分析&#xff08;初级数据分析&#xff09;…...

100ask_imx6ull 输出PWM

查看PWM对应扩展板的引脚 100ask_imx6ul通过扩展板插槽来验证pwm波&#xff0c;所以这里通过扩展板的原理图及芯片手册可知&#xff0c;gpio4_io20&#xff0c;gpio4_io19分别对应着PWM8和PWM7。 设置设备树 打开官方NXP的工具i.MX pins v6工具&#xff0c;PWM7/PWM8的配置如…...

yolov5编译安卓APP:解决图像上全是检测框

yolov5编译安卓APP&#xff1a;解决图像上全是检测框前言一、第一个YOLOv5 APP1.参考链接2.详细说明3.APP检测时图像上全是框的解决方法二、第二个YOLOv5 APP1.参考链接2.详细说明3.APP检测时图像上全是框的解决方法三、其他1.APK打包2.修改APP图标与名字前言 YOLOv5编译安卓A…...

为什么我们需要地图?

想一想&#xff0c;武侠小说里面。一张藏宝图&#xff0c;引来江湖腥风血雨&#xff0c;要么是武功秘籍&#xff0c;要么是绝世宝剑&#xff0c;要么是富可敌国的财富&#xff0c;只要有了藏宝图&#xff0c;便可曲径通幽&#xff0c;到达彼岸。 由此可见&#xff0c;地图的重…...

攻防世界1.新手练习区

4.攻防世界1.新手练习区 1.view_source 访问url&#xff1a; http://111.200.241.244:48855/ 鼠标点击右键不起作用&#xff0c;F12审查元素 得到flag为cyberpeace{0f3a3e4ab8c8664f3cf40d4240ec7b53} 2.robots 访问url&#xff1a; http://111.200.241.244:34362/ rob…...

Python进阶篇(二)-- Django 深入模型

上一节提到了Django是基于MVC架构的Web框架&#xff0c;MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据&#xff08;的表示&#xff09;&#xff0c;所以通常也被称作“数据模型”。在实际的项目中&#xff0c;数据模型通常通过数据库实现持久…...

ABAP SALV实现弹出ALV选择

问题场景 需要弹出一个ALV并获取选择的数据 实现思路 跳转屏幕弹出ALV&#xff08;通过SALV&#xff09;弹出ALV&#xff08;通过REUSE_ALV_POPUP_TO_SELECT&#xff09; 实现效果 因为这里需要的是单选&#xff0c;所以没有多选列 实现代码 MODULE sel_zfretype INPUT.…...

git check-pick,git patch 与 git stash 详解

大家好&#xff0c;我是 17。 今天和大家聊一聊 git check-pick&#xff0c;git patch 与 git stash 的用法。 git cherry-pick 为什么要用 cherry-pick? 不适合 merge 的场景就可以考虑 cherry-pick。 试想下面这些场景 只想同步分支的部分提交。两个分支是两上完全独立…...

OA漏洞-到处搜集整理

一米OA getfile.jsp 任意文件读取漏洞 原文链接 漏洞复现 一米OA getfile.jsp 任意文件读取漏洞 一米OA协同办公系统,集成了OA办公自动化系统、手机客户端、专业报表工具,为全国千万企业用户提供全功能、性价比高的OA软件。一米OA getfile.jsp文件存在任意文件读取漏洞&am…...

搜索推广 外贸/天津seo诊断技术

vue3 watch及computed的使用案例 //可以多个watch 一起使用 () => state.selectTreeList,(val) => {const treeData = _.cloneDeep(val)// 所属行业为:保理(6640),且所属地区只有省份,没有城市区县// 去...

西安公司做网站/windows优化大师最新版本

cmd 进入E文件夹 E: 查看文件夹目录 dir 进入某个文件夹 cd 目录...

网络优化方案/惠州百度关键词优化

花了几个小时终于把Sublime的配置搞定了&#xff0c;能够在里面写vex和Python&#xff0c;同时另外设置了Python对houdini模块的以及其他扩展包的自动填充功能。 这里简单讲一下安装sublime&#xff0c;因为这个不是重点&#xff0c;所以只介绍他的基本步奏了&#xff0c;本来就…...

三门峡网站建设电话/360网站推广怎么做

初学者应该选择学习Python还是C语言 发布时间&#xff1a;2020-11-21 14:11:31 来源&#xff1a;亿速云 阅读&#xff1a;74 作者&#xff1a;小新 小编给大家分享一下初学者应该选择学习Python还是C语言&#xff0c;希望大家阅读完这篇文章后大所收获&#xff0c;下面让我们一…...

网站上怎么做产品介绍/排名函数

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…...

上海网站建设公司兴田德润可以不/佛山网站建设方案咨询

我是软件工程的一名大一新生&#xff0c;高中时由于对编程的兴趣而选择了这个专业&#xff0c;上学期由于学校并没有开设编程类型的课程&#xff0c;所以我跟着社团学习了HTML基础和c基础的内容&#xff0c;这学期课程开设的是c语言&#xff0c;听说正常的课程进度里并没有pyth…...