设计模式学习
1.设计模式分类
1.创建型模式
用于描述“怎样创建对象”,主要特点是“将对象的创建与使用分离”。
单例,原型,工厂方法,抽象工厂,建造者
2.结构型模式
用于描述如何将类或对象按某种布局组成更大的结构
代理,适配器,桥接,装饰,外观,享元,组合
3.行为型模式
用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责
模板方法,策略,命令,职责链,状态,观察者,中介者,迭代器,访问者,备忘录,解释器
2.UML图
统一建模语言(Unified Modeling Language, UML)是用来设计软件的可视化建模语言。
特点:简单,统一,图形化,能表达软件设计中的动态与静态信息。
分类:例图,类图,对象图,状态图,活动图,时序图,协作图,构件图,部署图
1.类图(Class diagram)
类图是显示了模型的静态结构,特别是模型中存在的类,类的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。
1.类的表示方式
在UML类图中,类使用包含类名,属性(field)和方法(method)且带有分割线的矩形来表示。
Employee(类名) |
-name:String -age:int -address:String |
+work():void |
属性/方法名称前加的加号和减号表示了这个属性/方法的可见性
+:public
-:private
#:protected
属性的完整表达方式是: 可见性 名称 : 类型 [ = 缺省值(初始化值)]
方法的完整表达方式是: 可见性 名称(参数列表) [ : 返回类型]
注:1.中括号中的内容表示是可选的。2.也有将类型放在变量名前面,返回值类型放在方法名前面
2.类与类之间关系的表示方式
1.关联关系(一对一)
关联关系是对象之间的一种引用关系(在一个类中声明了另一个类的成员变量),用于表示一类对象与另一类对象之间的联系,如老师和学生,师傅和徒弟。关联关系是类与类之间最常用的一种关系,分为一般关联关系,聚合关系和组合关系。
一般关联关系又分为单向关联,双向关联,自关联。
1.单向关联
在UML类图这单向关联用一个带箭头的实线表示。一个类持有另一个类的成员变量
2.双向关联
在UML类图种,双向关联用一个不带箭头的直线表示。双方各自持有对方类型的成员变量。
3.自关联
在UML类图这,自关联用一个带有箭头且指向自身的线表示。自己包含自己。
2.聚合关系
强关联关系,是整体和部分之间的关系。聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。如:学校与老师的关系
在UML类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。
3.组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。如:头和嘴的关系。
在UML类图中,组合关系用带菱形的实线来表示,菱形指向整体。
4.依赖关系
依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。在代码只中,某个类的方法通过局部变量,方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。
在UML类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。
5.继承(泛化)关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在UML类图中,泛化关系用带空心三角箭头的实线表示,箭头从子类指向父类。代码实现时,使用面向对象的继承机制来实现泛化关系。如:Student和Teacher类都是Person的子类。
6实现关系
接口和实现类之间的关系。类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在UML类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。如:汽车和船实现了交通工具。
2.设计原则
1.开闭原则
对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果(类似USB接口)。
要达到这样的效果,需要使用接口和抽象类。因为抽象类灵活性好,适应性广,只要抽象的合理,可以基本保证软件架构的稳定。而软件中易变的细节可以从抽象派生出来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。
例:
public abstract class AbstractSkin {public abstract void display();
}
public class DefaultSkin extends AbstractSkin {public void display() {System.out.println("默认");}
}
public class HzjSkin extends AbstractSkin{public void display() {System.out.println("新");}
}
public class SougoInput {private AbstractSkin skin;public void setSkin(AbstractSkin skin) {this.skin = skin;}public void display() {skin.display();}
}
public class Client {public static void main(String[] args) {// 创建输入法对象SougoInput input = new SougoInput();// 创建皮肤对象DefaultSkin skin = new DefaultSkin();// 将皮肤设置到输入法中input.setSkin(skin);// 显示皮肤input.display();}
}
当功能有新的扩展时,只需要在写一个继承类实现方法,在Client类中进行修改即可,无需修改原型实现类。
2.里氏代换原则
任何基类可以出现的地方,子类一定可以出现。即子类可以扩展父类的功能,但不能改变父类原有的功能。子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。(除非抽象方法)
如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁是,程序运行出错的概率会非常大。
例:
/*** 四边形类接口**/
public interface Quadrilateral {double getLength();double getWidth();
}
/*** 长方形类**/
public class Rectangle implements Quadrilateral{private double length;private double width;public void setLength(double length) {this.length = length;}public void setWidth(double width) {this.width = width;}@Overridepublic double getLength() {return length;}@Overridepublic double getWidth() {return width;}
}
/*** 正方形类**/
public class Square implements Quadrilateral {private double side;public double getSide() {return side;}public void setSide(double side) {this.side = side;}@Overridepublic double getLength() {return side;}@Overridepublic double getWidth() {return side;}
}
/*** 调用类**/
public class RectangleDemo {public static void main(String[] args) {// 创建长方形对象Rectangle r = new Rectangle();// 设置长和宽r.setWidth(10);r.setLength(20);// 调用resize进行扩宽resize(r);printLengthAndWidth(r);System.out.println("=====================");// 创建正方形对象Square s = new Square();// 设置长和宽s.setSide(10);printLengthAndWidth(s);}// 扩宽的方法public static void resize(Rectangle rectangle) {// 判断宽如果比长小,进行扩宽的操作while (rectangle.getWidth() <= rectangle.getLength()) {rectangle.setWidth(rectangle.getWidth() + 1);}}// 打印长和宽public static void printLengthAndWidth(Quadrilateral quadrilateral) {System.out.println(quadrilateral.getLength());System.out.println(quadrilateral.getWidth());}
}
3.依赖倒转原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。要求对抽象进行编程,不要对实现进行编程,降低客户与实现模块间的耦合。
/*** cpu接口**/
public interface Cpu {// 运行cpupublic void run();
}
/*** Intel处理器**/
public class IntelCpu implements Cpu {@Overridepublic void run() {System.out.println("使用Intel处理器");}
}
/*** 硬盘接口**/
public interface HardDisk {// 存储数据public void save(String data);// 读取数据public String get();
}
/*** 希捷硬盘类**/
public class XiJieHardDisk implements HardDisk {@Overridepublic void save(String data) {System.out.println("希捷硬盘保存数据");}@Overridepublic String get() {System.out.println("希捷硬盘读取数据");return "数据";}
}
/*** 内存条接口**/
public interface Memory {public void save();
}
/*** 金士顿内存条**/
public class KingstonMemory implements Memory {@Overridepublic void save() {System.out.println("使用金士顿内存条");}
}
/*** 电脑**/
public class Computer {private HardDisk hardDisk;private Cpu cpu;private Memory memory;public HardDisk getHardDisk() {return hardDisk;}public void setHardDisk(HardDisk hardDisk) {this.hardDisk = hardDisk;}public Cpu getCpu() {return cpu;}public void setCpu(Cpu cpu) {this.cpu = cpu;}public Memory getMemory() {return memory;}public void setMemory(Memory memory) {this.memory = memory;}public void run() {System.out.println("运行计算机");String data = hardDisk.get();System.out.println("从硬盘读取的数据是" + data);cpu.run();memory.save();}
}
/*** TODO**/
public class ComputerDemo {public static void main(String[] args) {// 创建计算机的组件对象HardDisk hardDisk = new XiJieHardDisk();Cpu cpu = new IntelCpu();Memory memory = new KingstonMemory();// 创建计算机对象Computer c = new Computer();// 组装计算机c.setCpu(cpu);c.setHardDisk(hardDisk);c.setMemory(memory);c.run();}
}
4.接口隔离原则
客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。
/*** 防盗接口** @see [相关类/方法]* @since 2024/9/10*/
public interface AntiTheft {void antiTheft();
}
/*** 防火接口** @see [相关类/方法]* @since 2024/9/10*/
public interface Fireproof {void fireProof();
}
/*** 防水接口** @see [相关类/方法]* @since 2024/9/10*/
public interface Waterproof {void waterproof();
}
/*** 全能安全门** @see [相关类/方法]* @since 2024/9/10*/
public class AlmightySafetyDoor implements AntiTheft, Fireproof, Waterproof {@Overridepublic void antiTheft() {System.out.println("防盗");}@Overridepublic void fireProof() {System.out.println("防火");}@Overridepublic void waterproof() {System.out.println("防水");}
}
/*** 普通安全门** @see [相关类/方法]* @since 2024/9/10*/
public class CommonSafetyDoor implements AntiTheft, Fireproof {@Overridepublic void antiTheft() {System.out.println("防盗");}@Overridepublic void fireProof() {System.out.println("防火");}
}
/*** TODO** @see [相关类/方法]* @since 2024/9/10*/
public class Client {public static void main(String[] args) {// 创建全能安全门对象AlmightySafetyDoor door = new AlmightySafetyDoor();// 调用功能door.antiTheft();door.fireProof();door.waterproof();System.out.println("==========================");// 创建普通安全门CommonSafetyDoor door1 = new CommonSafetyDoor();// 调用功能door1.antiTheft();door1.fireProof();}
}
5.迪米特法则
最少知识原则。
只和你的直接朋友交谈,不跟“陌生人”说话(Talk only to your immediate friends and not to strangers)
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。目的是降低类之间的耦合度,提供模块的相对独立性。
朋友: 当前对象本身,当前对象的成员对象,当前对象所创建的对象,当前对象的方法参数等,这些对象同当前对象存在关联,聚合或组合关系,可以直接访问这些对象的方法。
/*** 明星类** @see [相关类/方法]* @since 2024/9/10*/
public class Star {private String name;public Star(String name) {this.name = name;}public String getName() {return name;}
}
/*** 粉丝类** @see [相关类/方法]* @since 2024/9/10*/
public class Fans {private String name;public Fans(String name) {this.name = name;}public String getName() {return name;}
}
/*** 公司类** @see [相关类/方法]* @since 2024/9/10*/
public class Company {private String name;public Company(String name) {this.name = name;}public String getName() {return name;}
}
/*** 经纪人类** @see [相关类/方法]* @since 2024/9/10*/
public class Agent {private Star star;private Fans fans;private Company company;public void setStar(Star star) {this.star = star;}public void setFans(Fans fans) {this.fans = fans;}public void setCompany(Company company) {this.company = company;}// 和粉丝见面的方法public void meeting() {System.out.println(star.getName() + "和粉丝" + fans.getName() + "见面");}// 和媒体公司洽谈的方法public void business() {System.out.println(star.getName() + "和" + company.getName() + "公司洽谈");}
}
/*** TODO** @see [相关类/方法]* @since 2024/9/10*/
public class Client {public static void main(String[] args) {// 创建经纪人类Agent agent = new Agent();// 创建明星对象Star star = new Star("林青霞");agent.setStar(star);// 创建粉丝类Fans fans = new Fans("李四");agent.setFans(fans);// 创建媒体公司对象Company company = new Company("麻豆传媒");agent.setCompany(company);agent.meeting();agent.business();}
}
6.合成复用原则
尽量先使用组合或聚合等关联关系来实现,其次才考虑使用继承关系来实现。
通常类的复用分为继承复用和合成复用两种。
继承复用虽然简单易实现,但也存在缺点:
1.继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
2.子类和父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展和维护。
3.它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。
采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,优点:
1.它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
2.对象的耦合度低。可以在类的成员位置声明抽象。
3.复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态的引用于成分对象类型相同的对象。
3.创建者模式
1.单例模式(Singleton Pattern)
提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。
1.结构
单例类:只能创建一个实例的类
访问类:使用单例类
2.实现
1.饿汉式
类加载就会导致该单实例对象被创建
1.静态变量方式
/*** 饿汉式:静态成员变量式**/
public class Singleton {// 1.私有构造方法private Singleton() {}// 2.在本类中创建本类对象private static Singleton instance = new Singleton();// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getInstance() {return instance;}
}
2. 静态代码块方式
/*** 饿汉式:静态代码块**/
public class Singleton {// 私有构造方法private Singleton() {}// 声明Singleton类型的变量private static Singleton instance;// 在静态代码块中进行赋值static {instance = new Singleton();}// 对外提供获取该类对象的方法public static Singleton getInstance() {return instance;}
}
2.懒汉式
类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
1.线程不安全
/*** 懒汉式:线程不安全**/
public class Singleton {// 私有构造方法private Singleton() {}// 声明Singleton类型的变量private static Singleton instance;// 对外提供访问方式public static Singleton getInstance() {// 判断instance是否为null,如果为null,说明还没有创建Singleton对象if (instance == null) {// 线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面return new Singleton();}return instance;}
}
2.线程安全
方法上面加个锁
/*** 懒汉式:线程不安全**/
public class Singleton {// 私有构造方法private Singleton() {}// 声明Singleton类型的变量private static Singleton instance;// 对外提供访问方式public static synchronized Singleton getInstance() {// 判断instance是否为null,如果为null,说明还没有创建Singleton对象if (instance == null) {// 线程1等待,线程2获取到cpu的执行权,也会进入到该判断里面, 在方法上面加个锁return new Singleton();}return instance;}
}
3.双重检查锁(推荐)
对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。故产生双重检查锁。
volatile可以保证可见性和有序性。可以保证在多线程情况下线程安全也不会有性能问题。
/*** 双重检查锁**/
public class Singleton {private Singleton() {}// volatile保证操作的有序性,防止多线程情况下,JVM实例化对象时进行优化和指令重排导致空指针private static volatile Singleton instance;public static Singleton getInstance() {// 第一次判断,如果instance的值不为null,不需要抢占锁,直接返回对象if (instance == null) {synchronized (Singleton.class) {// 第二次判断if (instance == null) {instance = new Singleton();}}}return instance;}
}
4.静态内部类(推荐)
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
/*** 静态内部类方式**/
public class Singleton {private Singleton() {}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}// 提供公共访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
第一次加载Singleton类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
静态内部类单例模式在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
7.枚举方式(推荐)(饿汉)
极力推荐,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所有单例实现中唯一不会被破坏的单例模式
public enum Singleton {INSTANCE;
}
3.存在的问题
破坏单例模式
1.序列化破坏单例模式
每次从文件读取出来的对象都是新对象,破坏了单例模式
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();readObjectFromFile(); // 每执行一次都是新的对象,破坏了单例模式readObjectFromFile();}// 从文件读取数据(对象)public static void readObjectFromFile() throws IOException, ClassNotFoundException {// 创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\learn\\a.txt"));// 读取对象Singleton instance = (Singleton) ois.readObject();// 释放资源ois.close();System.out.println(instance);}// 向文件中写数据(对象)public static void writeObject2File() throws IOException {// 获取Singleton对象Singleton instance = Singleton.getInstance();// 创建对象输出流对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\learn\\a.txt"));// 写对象oos.writeObject(instance);// 释放资源oos.close();}
}
2.反射破坏单例模式
每次通过反射创建的对象都是新对象
public class Client {public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {// 获取Singleton的字节码对象Class clazz = Singleton.class;// 获取无参构造方法对象Constructor cons = clazz.getDeclaredConstructor();// 取消访问检查cons.setAccessible(true);// 创建Singleton对象Singleton s1 = (Singleton) cons.newInstance();Singleton s2 = (Singleton) cons.newInstance();System.out.println(s1 == s2);}
}
3.序列化解决方法
在Singleton类中添加readResolve()方法,在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新new出来的对象。
原理:反序列化读取文件时调用的readObject方法,内部会检查是否存在readResolve方法,如果存在则会调用该方法。在类中重写该方法后,即可避免反序列化破坏单例。
public class Singleton implements Serializable {// 私有构造方法private Singleton() {}// 声明Singleton类型的变量private static Singleton instance;// 在静态代码块中进行赋值static {instance = new Singleton();}// 对外提供获取该类对象的方法public static Singleton getInstance() {return instance;}// 当进行反序列化是,会自动调用该方法,将该方法的返回值直接返回public Object readResolve() {return instance;}
}
部分源码
private Object readOrdinaryObject(boolean unshared)throws IOException{if (bin.readByte() != TC_OBJECT) {throw new InternalError();}ObjectStreamClass desc = readClassDesc(false);desc.checkDeserialize();Class<?> cl = desc.forClass();if (cl == String.class || cl == Class.class|| cl == ObjectStreamClass.class) {throw new InvalidClassException("invalid class descriptor");}Object obj;try {obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}passHandle = handles.assign(unshared ? unsharedMarker : obj);ClassNotFoundException resolveEx = desc.getResolveException();if (resolveEx != null) {handles.markException(passHandle, resolveEx);}if (desc.isExternalizable()) {readExternalData((Externalizable) obj, desc);} else {readSerialData(obj, desc);}handles.finish(passHandle);if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod()){Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {// Filter the replacement objectif (rep != null) {if (rep.getClass().isArray()) {filterCheck(rep.getClass(), Array.getLength(rep));} else {filterCheck(rep.getClass(), -1);}}handles.setObject(passHandle, obj = rep);}}return obj;}
4.反射解决方法
public class Singleton {private static boolean flag = false;private Singleton() {// 判断flag的值是否是true,如果是true,说明非第一次访问,直接抛一个异常,如果是false的话,说明是第一次访问synchronized (Singleton.class) {if (flag) {throw new RuntimeException("不能创建多个对象");}flag = true;}}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton INSTANCE = new Singleton();}// 提供公共访问方式public static Singleton getInstance() {return Singleton.SingletonHolder.INSTANCE;}
}
2.工厂模式
在java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,显然违背了软件设计的开闭原则。如果使用工厂来生产对象,就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。工厂模式最大优点:解耦
1.简单工厂模式
简单工厂不是一种设计模式,反而比较像是一种编程习惯
1.结构
抽象产品:定义了产品的规范,描述了产品的主要特性和功能
具体产品:实现或继承抽象产品的子类
具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品
2.实现
public abstract class Coffee {public abstract String getName();public void addSugar() {System.out.println("加糖");}public void addMilk() {System.out.println("加奶");}
}
/*** 美式咖啡**/
public class AmericanCoffee extends Coffee {public String getName() {return "美式";}
}
/*** 拿铁咖啡**/
public class LatteCoffee extends Coffee {public String getName() {return "拿铁";}
}
/*** 简单咖啡工厂,用来生成咖啡**/
public class SimpleCoffeeFactory {public Coffee createCoffee(String type) {// 声明coffee类型的变量,根据不同类型创建不同的coffee子类对象Coffee coffee = null;if ("american".equals(type)) {coffee = new AmericanCoffee();} else if ("latte".equals(type)) {coffee = new LatteCoffee();} else {throw new RuntimeException("没有");}return coffee;}
}
/*** 咖啡店**/
public class CoffeeStore {public Coffee orderCoffee(String type) {// 工厂类SimpleCoffeeFactory factory = new SimpleCoffeeFactory();// 调用生成咖啡的方法Coffee coffee = factory.createCoffee(type);coffee.addMilk();coffee.addSugar();return coffee;}
}
public class Client {public static void main(String[] args) {// 创建咖啡店类CoffeeStore coffeeStore = new CoffeeStore();Coffee latte = coffeeStore.orderCoffee("latte");System.out.println(latte.getName());}
}
3.优缺点
优点:封装了创建对象的过程,可以通过参数直接获取对象。把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。
缺点:增加新产品时还是需要修改工厂类的代码,违背了“开闭原则”。
4.扩展-静态工厂
将工厂类中创建对象的功能定义为静态的
/*** 简单咖啡工厂,用来生成咖啡**/
public class SimpleCoffeeFactory {public static Coffee createCoffee(String type) {// 声明coffee类型的变量,根据不同类型创建不同的coffee子类对象Coffee coffee = null;if ("american".equals(type)) {coffee = new AmericanCoffee();} else if ("latte".equals(type)) {coffee = new LatteCoffee();} else {throw new RuntimeException("没有");}return coffee;}
}
2.工厂方法模式
定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类
1.结构
抽象工厂(Abstract Factory):提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品。
具体工厂(ConcreteFactory):主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂一一对应。
2.实现
/*** 咖啡类**/
public abstract class Coffee {public abstract String getName();public void addSugar() {System.out.println("加糖");}public void addMilk() {System.out.println("加奶");}
}
/*** 美式咖啡**/
public class AmericanCoffee extends Coffee {public String getName() {return "美式";}
}
/*** 拿铁咖啡**/
public class LatteCoffee extends Coffee {public String getName() {return "拿铁";}
}
/*** 抽象工厂**/
public interface CoffeeFactory {// 创建咖啡对象的方法Coffee createCoffee();
}
/*** 美式咖啡工厂对象,专门用来生成美式咖啡**/
public class AmericanCoffeeFactory implements CoffeeFactory{@Overridepublic Coffee createCoffee() {return new AmericanCoffee();}
}
/*** 拿铁咖啡工厂**/
public class LatteCoffeeFactory implements CoffeeFactory{@Overridepublic Coffee createCoffee() {return new LatteCoffee();}
}
/*** 咖啡店**/
public class CoffeeStore {private CoffeeFactory factory;public void setFactory(CoffeeFactory factory) {this.factory = factory;}// 点咖啡public Coffee orderCoffee() {Coffee coffee = factory.createCoffee();coffee.addMilk();coffee.addSugar();return coffee;}
}
public class Client {public static void main(String[] args) {// 创建咖啡店对象CoffeeStore store = new CoffeeStore();// 创建对象CoffeeFactory factory = new AmericanCoffeeFactory();store.setFactory(factory);// 点咖啡Coffee coffee = store.orderCoffee();System.out.println(coffee.getName());}
}
3.优缺点
优点:1.用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。2.在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则
缺点:每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,增加了系统的复杂度。
3.抽象工厂模式
同种类产品称为同等级产品,同一个工厂生产的不同等级的一组产品称为一个产品族。上面的工厂模式只支持生成同等级的产品,抽象工厂将考虑生产不同等级的产品族。
1.概念
是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。
抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。
2.结构
抽象工厂(Abstract Factory):提供了创建产品的接口,包含多个创建产品的方法,可以创建多个不同等级的产品。
具体工厂(Concrete Factory):主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建。
抽象产品(Product):定义了产品的规范,描述了产品的主要特性和功能,抽象工厂模式有多个抽象产品。
具体产品(ConcreteProduct):实现了抽象产品角色所定义的接口,由具体工厂来创建,同具体工厂之间是多对一的关系。
3.实现
/*** 咖啡类**/
public abstract class Coffee {public abstract String getName();public void addSugar() {System.out.println("加糖");}public void addMilk() {System.out.println("加奶");}
}
/*** 美式咖啡**/
public class AmericanCoffee extends Coffee {public String getName() {return "美式";}
}
/*** 拿铁咖啡**/
public class LatteCoffee extends Coffee {public String getName() {return "拿铁";}
}
/*** 甜品抽象类**/
public abstract class Dessert {public abstract void show();
}
/*** 提拉米苏类**/
public class Trimisu extends Dessert{@Overridepublic void show() {System.out.println("提拉米苏");}
}
/*** 抹茶慕斯**/
public class MatchaMousse extends Dessert{@Overridepublic void show() {System.out.println("抹茶慕斯");}
}
/*** 抽象工厂类**/
public interface DessertFactory {// 生产咖啡Coffee createCoffee();// 生产甜品Dessert createDessert();
}
/*** 美式风味的工厂类* 生产美式咖啡和抹茶慕斯**/
public class AmericanDessertFactory implements DessertFactory{@Overridepublic Coffee createCoffee() {return new AmericanCoffee();}@Overridepublic Dessert createDessert() {return new MatchaMousse();}
}
/*** 意大利风味工厂* 生产拿铁和提拉米苏**/
public class ItalyDessertFactory implements DessertFactory{@Overridepublic Coffee createCoffee() {return new LatteCoffee();}@Overridepublic Dessert createDessert() {return new Trimisu();}
}
public class Client {public static void main(String[] args) {// 创建意大利风味甜品工厂ItalyDessertFactory factory = new ItalyDessertFactory();// 获取拿铁和提拉米苏Coffee coffee = factory.createCoffee();Dessert dessert = factory.createDessert();System.out.println(coffee.getName());dessert.show();}
}
4.优缺点
优点:当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。
缺点:当产品族中需要增加一个新的产品时,所有的工厂类都需要修改
5.使用场景
当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机,洗衣机等。
系统中有多个产品族,但每次只使用其中的某一族产品。如只穿某一个品牌的衣服和鞋。
系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。
如:输入法换皮肤,一整套一起换,生成不同操作系统的程序。
4.模式扩展
简单工厂+配置文件 解除耦合
解除工厂对象和产品对象的耦合。在配置文件中配置文件的全类名,并创建对象进行存储,客户端如果需要对象,直接进行获取即可。
静态成员变量用来存储创建的对象(键存储的是名称,值存储的是对应的对象),而读取配置文件以及创建对象写在静态代码块中,目的就是只执行一次。
american=com.hzj.pattern.factory.config_factory.AmericanCoffee
latte=com.hzj.pattern.factory.config_factory.LatteCoffee
/*** 美式咖啡**/
public class AmericanCoffee extends Coffee {public String getName() {return "美式";}
}
/*** 拿铁咖啡**/
public class LatteCoffee extends Coffee {public String getName() {return "拿铁";}
}
/*** 工厂类**/
public class CoffeeFactory {// 加载配置文件,获取配置文件中配置的全类名,并创建该类的对象进行存储// 定义容器对象存储咖啡对象private static HashMap<String, Coffee> map = new HashMap<>();// 加载配置文件,只需要加载一次static {// 创建Properties对象Properties p = new Properties();// 调用p对象中的load方法进行配置文件的加载InputStream is = CoffeeFactory.class.getClassLoader().getResourceAsStream("bean.properties");try {p.load(is);// 从p集合中获取全类名并创建对象Set<Object> keySet = p.keySet();for (Object key : keySet) {String className = p.getProperty((String) key);// 通过反射创建对象Class clazz = Class.forName(className);Coffee coffee = (Coffee) clazz.newInstance();// 将名称和对象存储到容器中map.put((String) key, coffee);}} catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException exception) {exception.printStackTrace();}}// 根据名称获取对象public static Coffee createCoffee(String name) {return map.get(name);}
}
public class Client {public static void main(String[] args) {Coffee coffee = CoffeeFactory.createCoffee("american");System.out.println(coffee.getName());}
}
3.原型模式
用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。原型模式就是克隆。
1.结构
抽象原型类:规定了具体原型对象必须实现的clone()方法
具体原型类:实现抽象原型类的clone()方法,它是可被复制的对象
访问类:使用具体原型类中的clone()方法来复制新的对象
2.实现
原型模式的克隆分为浅克隆和深克隆(默认是浅拷贝):
浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其它对象也会被克隆,不再指向原有对象地址。
Java中的Object类中提供了clone()方法来实现浅克隆。Cloneable接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下
/*** 具体的原型类** @see [相关类/方法]* @since 2024/9/14*/
public class Realizetype implements Cloneable{public Realizetype() {System.out.println("具体的原型对象创建完成");}@Overridepublic Realizetype clone() {try {System.out.println("具体原型复制成功");return (Realizetype) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}
}
/*** TODO** @see [相关类/方法]* @since 2024/9/14*/
public class Client {public static void main(String[] args) {// 创建一个原型类对象Realizetype realizetype = new Realizetype();// 调用原型类中的clone方法进行对象的克隆final Realizetype clone = realizetype.clone();System.out.println(realizetype == clone); // false}
}
3.案例
用原型模式生成”三好学生”奖状
用以学校的“三好学生”奖状除了获奖人姓名不同,其它都相同,可以使用原型模式复制多个“三好学生”奖状出来,然后在修改奖状上的名字即可。
类图如下:
代码如下:
/*** 奖状类** @see [相关类/方法]* @since 2024/9/18*/
public class Citation implements Cloneable {// 三好学生的姓名private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic Citation clone() {try {return (Citation) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}public void show() {System.out.println("name = " + name);}
}
public class CitationTest {public static void main(String[] args) {// 创建原型对象Citation citation = new Citation();// 克隆奖状对象Citation citation1 = citation.clone();citation.setName("张三");citation1.setName("李四");// 调用show方法展示citation.show();citation1.show();}
}
4.使用场景
对象的创建非常复杂,可以使用原型模式快捷的创建对象。
性能和安全要求比较高
5.扩展(深克隆)
通过序列号和反序列的方式实现深克隆
/*** 奖状类** @see [相关类/方法]* @since 2024/9/18*/
public class Citation implements Cloneable, Serializable {private Student student;public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}@Overridepublic Citation clone() {try {return (Citation) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}public void show() {System.out.println("name = " + student.getName());}
}
/*** TODO** @see [相关类/方法]* @since 2024/9/18*/
public class Student implements Serializable {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +'}';}
}
public class CitationTest {public static void main(String[] args) throws IOException, ClassNotFoundException {// 创建原型对象Citation citation = new Citation();// 创建张三学生对象Student student = new Student();student.setName("张三");citation.setStudent(student);// 创建对象输出类对象ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("文件路径"));// 写对象oos.writeObject(citation);// 释放资源oos.close();// 创建对象输入流对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("文件路径"));// 读取对象final Citation citation1 = (Citation) ois.readObject();// 释放资源ois.close();final Student student1 = citation1.getStudent();student1.setName("李四");// 调用show方法展示citation.show();citation1.show();}
}
4.建造者模式
1.概述
将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。
分离了部件的构造(由Builder来负责)和装配(由Director负责)。从而可以构建出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。
由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法,装配算法的解耦,实现了更好的复用。
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。
2.结构
抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。
具体建造者类(ConcreteBuilder):实现了Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
产品类(Product):要创建的复杂对象。
指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。
3.实现
创建共享单车
生成自行车是一个复杂的过程,它包含了车架,车座等组件的生成。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生成就可以使用建造者模式。
这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和OfoBuilder是具体的建造者;Director是指挥者。
/*** 自行车产品类** @see [相关类/方法]* @since 2024/9/18*/
public class Bike {// 车架private String frame;// 车座private String seat;public String getFrame() {return frame;}public void setFrame(String frame) {this.frame = frame;}public String getSeat() {return seat;}public void setSeat(String seat) {this.seat = seat;}
}
/*** 抽象建造者类** @see [相关类/方法]* @since 2024/9/18*/
public abstract class Builder {// 声明Bike类型的变量并进行赋值protected Bike bike = new Bike();// 构建车架public abstract void buildFrame();// 构建车座public abstract void buildSeat();// 创建自行车的方法public abstract Bike createBike();
}
/*** 具体的构建者,用来构建摩拜单车对象** @see [相关类/方法]* @since 2024/9/18*/
public class MobileBuilder extends Builder {@Overridepublic void buildFrame() {bike.setFrame("碳纤维车架");}@Overridepublic void buildSeat() {bike.setSeat("真皮车座");}@Overridepublic Bike createBike() {return bike;}
}
/*** Ofo单车构建者** @see [相关类/方法]* @since 2024/9/19*/
public class OfoBuilder extends Builder {@Overridepublic void buildFrame() {bike.setFrame("铝合金车架");}@Overridepublic void buildSeat() {bike.setSeat("橡胶车座");}@Overridepublic Bike createBike() {return bike;}
}
/*** 指挥者类** @see [相关类/方法]* @since 2024/9/19*/
public class Director {// 声明builder类型的变量private Builder builder;public Director(Builder builder) {this.builder = builder;}// 组装自行车的功能public Bike construct() {builder.buildFrame();builder.buildSeat();return builder.createBike();}
}
public class Client {public static void main(String[] args) {// 创建指挥者对象Director director = new Director(new MobileBuilder());// 让指挥者指挥组装自行车final Bike bike = director.construct();System.out.println(bike.getFrame());System.out.println(bike.getSeat());}
}
4.优缺点
优点:
1.建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
2.在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身于产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
3.可以更加精细的控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
4.创建者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
5.使用场景
建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们结合在一起的算法却相对稳定,所以它通常在以下场合使用:
1.创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
2.创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
6.模式扩展
建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时可以利用建造者模式进行重构。
重构前代码
public class Phone {private String cpu;private String screen;private String memory;private String mainBoard;public Phone(String cpu, String screen, String memory, String mainBoard) {this.cpu = cpu;this.screen = screen;this.memory = memory;this.mainBoard = mainBoard;}public String getCpu() {return cpu;}public void setCpu(String cpu) {this.cpu = cpu;}public String getScreen() {return screen;}public void setScreen(String screen) {this.screen = screen;}public String getMemory() {return memory;}public void setMemory(String memory) {this.memory = memory;}public String getMainBoard() {return mainBoard;}public void setMainBoard(String mainBoard) {this.mainBoard = mainBoard;}@Overridepublic String toString() {return "Phone{" +"cpu='" + cpu + '\'' +", screen='" + screen + '\'' +", memory='" + memory + '\'' +", mainBoard='" + mainBoard + '\'' +'}';}
}
重构之后
public class Phone {private String cpu;private String screen;private String memory;private String mainBoard;// 私有构造方法private Phone(Builder builder) {this.cpu = builder.cpu;this.screen = builder.screen;this.memory = builder.memory;this.mainBoard = builder.mainBoard;}public static final class Builder {private String cpu;private String screen;private String memory;private String mainBoard;public Builder cpu(String cpu) {this.cpu = cpu;return this;}public Builder screen(String screen) {this.screen = screen;return this;}public Builder memory(String memory) {this.memory = memory;return this;}public Builder mainBoard(String mainBoard) {this.mainBoard = mainBoard;return this;}// 使用构建者创建phone对象public Phone build() {return new Phone(this);}}@Overridepublic String toString() {return "Phone{" +"cpu='" + cpu + '\'' +", screen='" + screen + '\'' +", memory='" + memory + '\'' +", mainBoard='" + mainBoard + '\'' +'}';}
}
public class Client {public static void main(String[] args) {// 创建手机对象Phone phone = new Phone.Builder().cpu("intel").screen("三星").memory("内存条").mainBoard("主板").build();System.out.println(phone);}
}
在实体类上面添加@Builder注解可以达到相同效果,无需自己封装。
5.创建者模式对比
1.工厂方法模式VS建造者模式
工厂方法模式注重的是整体对象的创建方式,注重结果;而建造者模式注重的是部件构建的过程,意在通过一步一步的精确构造创建出一个复杂的对象,注重过程。
例:如要制造一个超人,使用工厂模式,直接产生出来的就是一个力大无穷,能飞,裤衩外穿的超人;使用建造者模式,则需要组装手,脚,躯干等部分,然后在穿内裤,最后一个超人诞生。
2.抽象工厂模式VS建造者模式
抽象工厂模式实现对产品家族的创建,一个产品家族:具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心产品用什么工厂生成即可。
建造者模式则是要求按照指定的蓝图建造产品,主要目的是通过组装零配件而产生一个新产品。
如果将抽象工厂看成汽车配件生成工厂,生产一个产品族的产品,那么建造者模式就是一个汽车组装工厂,通过对部件的组装可以返回一辆完整的汽车。
4.结构型模式
结构型模式描述如何将类或对象按某种布局组成更大的结构。分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者采用组合或聚合来组合对象。
由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则“,所以对象结构型模式比类结构型模式具有更大的灵活性。
1.代理模式
1.概述
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
2.结构
1.抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
2.真实主题(Real Subject)类:实现了抽象主题中的具体业务,时代理对象所代表的真实对象,是最终要引用的对象。
3.代理(Proxy)类:提供了与真实主题相同的接口,起内部含有对真实主题的引用,它可以访问,控制或扩展真实主题的功能。
3.静态代理
案例:火车站卖票
如果要买火车票的话,需要取火车站买票,坐车去火车站,排队等一系列的操作,显然比较麻烦。而火车站在很多个地方都有代售点,我们去代售点买票就方便多了。即火车站是目标对象,代售点是代理对象。
/*** 卖火车票的接口** @see [相关类/方法]* @since 2024/9/21*/
public interface SellTickets {void sell();
}
/*** 火车站类** @see [相关类/方法]* @since 2024/9/21*/
public class TrainStation implements SellTickets{@Overridepublic void sell() {System.out.println("火车站买票");}
}
/*** 代售点类** @see [相关类/方法]* @since 2024/9/21*/
public class ProxyPoint implements SellTickets {// 声明火车站对象private TrainStation trainStation = new TrainStation();@Overridepublic void sell() {System.out.println("代售点收取一些服务费");trainStation.sell();}
}
public class Client {public static void main(String[] args) {// 创建代售点类对象ProxyPoint proxyPoint = new ProxyPoint();// 调用方法进行买票proxyPoint.sell();}
}
测试类直接访问ProxyPoint 类对象,ProxyPoint作为访问对象和目标对象的中介,避免了直接访问目标对象,同时也增强了sell方法。
4.JDK动态代理
Java中提供了应该动态代理类Proxy,该类提供了应该创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。目标对象必须实现接口。
案例同上。
/*** 卖火车票的接口** @see [相关类/方法]* @since 2024/9/21*/
public interface SellTickets {void sell();
}
public class TrainStation implements SellTickets {@Overridepublic void sell() {System.out.println("火车站买票");}
}
/*** 获取代理对象的工厂类* 代理类也实现了对于的接口** @see [相关类/方法]* @since 2024/9/23*/
public class ProxyFactory {// 声明目标对象private TrainStation station = new TrainStation();public SellTickets getProxyObject() {// 返回代理对象即可
// ClassLoader loader: 类加载器,用于加载代理类。可以通过目标对象获取类加载器
// Class<?>[] interfaces: 代理类实现的接口的字节码对象
// InvocationHandler h: 代理对象的调用处理程序/** Object proxy: 代理对象,和proxyObject对象是同一个对象,在invoke方法中基本不用* Method method: 对接口中的方法进行封装的method对象* Object[] args: 调用方法的实际参数** 返回值: 方法的返回值* */final SellTickets proxyObject = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),(proxy, method, args) -> {
// System.out.println("invoke方法执行了");System.out.println("代售点收取服务费(jdk动态代理)");// 执行目标对象的方法final Object invoke = method.invoke(station, args);return invoke;});return proxyObject;}
}
public class Client {public static void main(String[] args) {// 获取代理对象// 1.创建代理工厂对象ProxyFactory factory = new ProxyFactory();// 2.使用factory对象的方法获取代理对象final SellTickets proxyObject = factory.getProxyObject();// 3.调用卖票的方法proxyObject.sell();}
}
ProxyFactory不是代理模式中的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。可以通过Arthas的jad方法在程序运行时去观察代理类(proxyObject类)。
代理类($Proxy0)实现了SellTickets接口,将我们提供的匿名内部类对象传递给了父类。
执行流程:
1.在测试类中通过代理对象调用sell()方法
2.根据多态的特性,执行的是代理类中的sell()方法。
3.代理类中的sell()方法这又调用了InvocationHandler接口的子实现类对象的invoke方法
4.invoke方法通过反射执行了真实对象所属类中(TrainStation)的sell()方法
5.CGLIB动态代理
如果没有定义SellTickets接口,只定义了TrainStation类时,JDK代理则无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB为没有实现接口的类提供代理,为JDK动态代理提供了很好的补充。
案例如上:
引入maven
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version></dependency>
/*** 火车站类** @see [相关类/方法]* @since 2024/9/21*/
public class TrainStation {public void sell() {System.out.println("火车站买票");}
}
/*** 代理对象工厂,用来获取代理对象** @see [相关类/方法]* @since 2024/9/23*/
public class ProxyFactory implements MethodInterceptor {// 声明火车站对象private TrainStation station = new TrainStation();public TrainStation getProxyObject() {// 创建Enhancer对象,类似于jdk代理中的Proxy类Enhancer enhancer = new Enhancer();// 设置父类的字节码对象,指定父类enhancer.setSuperclass(TrainStation.class);// 设置回调函数enhancer.setCallback(this);// 创建代理对象final TrainStation proxyObject = (TrainStation) enhancer.create();return proxyObject;}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// System.out.println("方法执行了");System.out.println("代售点收取服务费(CGLIB代理)");// 要调用目标对象的方法final Object invoke = method.invoke(station, objects);return invoke;}
}
public class Client {public static void main(String[] args) {// 创建代理工厂对象ProxyFactory factory = new ProxyFactory();// 获取代理对象 获取到的类是TrainStation的子类对象final TrainStation proxyObject = factory.getProxyObject();// 调用代理对象中的卖票方法proxyObject.sell();}
}
代理工厂必须实现MethodInterceptor接口。使用Enhancer类代理Proxy类进行代理对象的创建。
6.三种代理的对比
1.jdk代理和CGLIB代理
使用CGLIB实现动态代理,CGLIB底层采用AMS字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用JAVA反射效率要高。CGLIB不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。使用时,有接口则用jdk动态代理,没有接口则用CGLib代理。
2.静态代理和动态代理
动态代理最大的好处是接口或类中声明的所用方法都被转移到调用处理器一个集中的方法中处理。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题。
7.优缺点
优点:
1.代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用
2.代理对象可以扩展目标对象的功能
3.代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:
增加了系统的复杂度
8.使用场景
1.远程代理(Remote)
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代理设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程访问提供的功能,而不必过多关心通信部分的细节。如:RPC调用。
2.防火墙代理(Firewall)
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网反应响应时,代理服务器再把它转给你的浏览器。如:VPN
3.保护代理(Protect or Access)
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
2.适配器(Adapter)模式
1.概述
将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
适配器模式分为适配器模式和对象适配器模式,前者类之间的耦合度比后者高,且要求程序员了解现有组件库中的相关组件的内部结构,所以应用相对少一些。
2.结构
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口。
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口。
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者。
3.类适配器模式
实现方式:定义一个适配器类来实现当前系统的业务接口(目标接口),同时又继承现有组件库中已经存在的组件(适配者类)。
案例:读卡器。现有一台电脑指定读取SD卡,而要读取TF卡中的内容的话就需要使用到适配器模式。创建一个读卡器,将TF卡中的内容读取出来。
电脑要的是SD卡,所以目标接口是SD卡。要把TF卡适配为SD卡,所以TF是适配者类。
/*** 目标接口** @see [相关类/方法]* @since 2024/9/25*/
public interface SDCard {// 从SD卡中读取数据String readSD();// 往SD卡中写数据void writeSD(String msg);
}
/*** 具体的SD卡类** @see [相关类/方法]* @since 2024/9/25*/
public class SDCardImpl implements SDCard {@Overridepublic String readSD() {return "SDCard read msg : hello word SDCard";}@Overridepublic void writeSD(String msg) {System.out.println("SDCard write msg: " + msg);}
}
/*** 适配者类接口** @see [相关类/方法]* @since 2024/9/25*/
public interface TFCard {// 从TF卡中读取数据String readTF();// 往TF卡中写数据void writeTF(String msg);
}
/*** 适配者类** @see [相关类/方法]* @since 2024/9/25*/
public class TFCardImpl implements TFCard{@Overridepublic String readTF() {return "TFCard read msg: hello word TFCard";}@Overridepublic void writeTF(String msg) {System.out.println("TFCard write msg: " + msg);}
}
/*** 计算机类** @see [相关类/方法]* @since 2024/9/25*/
public class Computer {// 从SD卡中读取数据public String readSD(SDCard sdCard) {if (sdCard == null) {throw new NullPointerException("SDCard is not null");}return sdCard.readSD();}
}
/*** 适配器类** @see [相关类/方法]* @since 2024/9/25*/
public class SDAdapterTF extends TFCardImpl implements SDCard {@Overridepublic String readSD() {System.out.println("adapter read tf card");return readTF();}@Overridepublic void writeSD(String msg) {System.out.println("adapter write tf card");writeTF(msg);}
}
public class Client {public static void main(String[] args) {// 创建计算机对象Computer computer = new Computer();// 读取SD卡中的数据final String readSD = computer.readSD(new SDCardImpl());System.out.println(readSD);System.out.println("======================================");// 使用该电脑读取TF卡中的数据// 定义适配类final String readTF = computer.readSD(new SDAdapterTF());System.out.println(readTF);}
}
类适配器模式违背了合成复用原则。类适配器是客户类有一个接口规范的情况下可用,反之不可用,因为类只能单继承,适配器类需要同时继承和实现,所以必须有一个接口。
4.对象适配器模式
实现方式:对象适配器模式可采用将现有组件库中已经实现的组件引入适配器类中(将类适配者模式的继承改为聚合),该类同时实现当前系统的业务接口。
案例同上
/*** 目标接口** @see [相关类/方法]* @since 2024/9/25*/
public interface SDCard {// 从SD卡中读取数据String readSD();// 往SD卡中写数据void writeSD(String msg);
}
/*** 具体的SD卡类** @see [相关类/方法]* @since 2024/9/25*/
public class SDCardImpl implements SDCard {@Overridepublic String readSD() {return "SDCard read msg : hello word SDCard";}@Overridepublic void writeSD(String msg) {System.out.println("SDCard write msg: " + msg);}
}
/*** 适配者类接口** @see [相关类/方法]* @since 2024/9/25*/
public interface TFCard {// 从TF卡中读取数据String readTF();// 往TF卡中写数据void writeTF(String msg);
}
/*** 适配者类** @see [相关类/方法]* @since 2024/9/25*/
public class TFCardImpl implements TFCard{@Overridepublic String readTF() {return "TFCard read msg: hello word TFCard";}@Overridepublic void writeTF(String msg) {System.out.println("TFCard write msg: " + msg);}
}
/*** 计算机类** @see [相关类/方法]* @since 2024/9/25*/
public class Computer {// 从SD卡中读取数据public String readSD(SDCard sdCard) {if (sdCard == null) {throw new NullPointerException("SDCard is not null");}return sdCard.readSD();}
}
/*** 适配器类** @see [相关类/方法]* @since 2024/9/25*/
public class SDAdapterTF implements SDCard {// 声明适配者类private TFCard tfCard;public SDAdapterTF(TFCard tfCard) {this.tfCard = tfCard;}@Overridepublic String readSD() {System.out.println("adapter read tf card");return tfCard.readTF();}@Overridepublic void writeSD(String msg) {System.out.println("adapter write tf card");tfCard.writeTF(msg);}
}
public class Client {public static void main(String[] args) {// 创建计算机对象Computer computer = new Computer();// 读取SD卡中的数据final String readSD = computer.readSD(new SDCardImpl());System.out.println(readSD);System.out.println("======================================");// 使用该电脑读取TF卡中的数据// 创建适配器类对象SDAdapterTF sdAdapterTF = new SDAdapterTF(new TFCardImpl());final String readTF = computer.readSD(sdAdapterTF);System.out.println(readTF);}
}
5.接口适配器模式
当不希望实现一个接口中所用的方法时,可以创建一个抽象类Adapter,实现所有方法(空方法)。我们只需要继承该抽象类并重写需要的方法即可。
6.应用场景
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。
3.装饰者模式
1.概述
在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。
2.结构
抽象构件(Component)角色: 定义一个抽象接口以规定准备接收附加责任的对象。
具体构件(Concrete Component)角色: 实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰(Decorator)角色: 继承或实现抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰(ConcreteDecorator)角色: 实现抽象装饰的相关方法,并给具体构件对象添加附件的责任。
3.实现
案例:快餐店,快餐店有炒面,炒饭这些快餐,可以额外附加鸡蛋,火腿,培根等配菜,加配菜需要额外加钱,没个配菜的价格通常不一样,那么计算总价时就会比较麻烦。
/*** 快餐类(抽象构件角色)** @see [相关类/方法]* @since 2024/9/25*/
public abstract class FastFood {// 价格private float price;// 描述private String desc;public FastFood() {}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}public String getDesc() {return desc;}public void setDesc(String desc) {this.desc = desc;}public FastFood(float price, String desc) {this.price = price;this.desc = desc;}public abstract float cast();
}
/*** 炒饭类(具体构件角色)** @see [相关类/方法]* @since 2024/9/25*/
public class FriedRice extends FastFood {public FriedRice() {super(10, "炒饭");}@Overridepublic float cast() {return getPrice();}
}
/*** 炒面(具体构件类)** @see [相关类/方法]* @since 2024/9/25*/
public class FriedNoodles extends FastFood {public FriedNoodles() {super(12, "炒面");}@Overridepublic float cast() {return getPrice();}
}
/*** 装饰者类(抽象装饰角色)** @see [相关类/方法]* @since 2024/9/25*/
public abstract class Garnish extends FastFood {// 声明快餐类的变量private FastFood fastFood;public FastFood getFastFood() {return fastFood;}public void setFastFood(FastFood fastFood) {this.fastFood = fastFood;}public Garnish(float price, String desc, FastFood fastFood) {super(price, desc);this.fastFood = fastFood;}
}
/*** 鸡蛋类(具体的装饰者角色)** @see [相关类/方法]* @since 2024/9/25*/
public class Egg extends Garnish {public Egg(FastFood fastFood) {super(1, "鸡蛋", fastFood);}@Overridepublic float cast() {return getPrice() + getFastFood().cast();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}
/*** 培根类(具体装饰者类)** @see [相关类/方法]* @since 2024/9/25*/
public class Bacon extends Garnish {public Bacon(FastFood fastFood) {super(1, "培根", fastFood);}@Overridepublic float cast() {return getPrice() + getFastFood().cast();}@Overridepublic String getDesc() {return super.getDesc() + getFastFood().getDesc();}
}
public class Client {public static void main(String[] args) {// 点一份炒饭FastFood friedRice = new FriedRice();System.out.println(friedRice.getDesc() + ":" + friedRice.cast());System.out.println("===============================");// 在上面的炒饭中加一个鸡蛋friedRice = new Egg(friedRice);System.out.println(friedRice.getDesc() + ":" + friedRice.cast());System.out.println("============================");// 再加一个鸡蛋friedRice = new Egg(friedRice);System.out.println(friedRice.getDesc() + ":" + friedRice.cast());// 加一个培根friedRice = new Bacon(friedRice);System.out.println(friedRice.getDesc() + ":" + friedRice.cast());}
}
4.优点
装饰着模式可以带来比继承更加灵活性的扩展功能,使用更加方便,可以通过组合不同的装饰者对象来获取具有不同行为状态的多样化的结果。装饰着模式比继承更具良好的扩展性,完美的遵循开闭原则,继承是静态的附件责任,装饰着则是动态的附件责任。
装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。
5.使用场景
1.当不能采用继承的方式对系统进行扩充或者采用继承不利于系统扩展和维护时。
不能采用继承的情况:1.系统中存在大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。2.类定义不能继承(如final类)
2.在不影响其它对象的情况下,以动态,透明的方式给单个对象添加职责。
3.当对象的功能要求可以动态地添加,也可以在动态的撤销时。
6.代理模式和装饰者的区别
静态代理和装饰者模式的区别:
相同点:
都要实现与目标类相同的业务接口
在两个类中都要声明目标对象
都可以在不修改目标类的前提下增强目标方法
不同点:
目的不同:装饰者是为了增强目标对象,静态代理是为了保护和隐藏目标对象
获取目标对象构建的地方不同:装饰者是由外界传递进来,可以通过构造方法传递;静态代理是在代理类内部创建,以此来隐藏目标对象。
4.桥接模式
1.概述
将抽象与实现分离,是它们可以独立变化。用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
2.结构
抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
扩展抽象化(Refind Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合或聚合关系调用实现化角色中的业务方法。
实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。
3.实现
案例:需要开发一个跨平台视频播放器,可以在不同操作系统平台(如Windows,Mac,Linux等)上播放多种格式的视频文件,常见的视频格式包括RMVB,AVI,WMV等。该播放器包含了两个维度,适合使用桥接模式。
/*** 视频文件(实现化角色)** @see [相关类/方法]* @since 2024/9/26*/
public interface VideoFile {// 解码功能void decode(String fileName);
}
/*** Avi视频文件(具体实现化角色)** @see [相关类/方法]* @since 2024/9/26*/
public class AviFile implements VideoFile{@Overridepublic void decode(String fileName) {System.out.println("avi视频文件: " + fileName);}
}
/*** rmvb视频文件(具体的实现化角色)** @see [相关类/方法]* @since 2024/9/26*/
public class RmvbFile implements VideoFile {@Overridepublic void decode(String fileName) {System.out.println("rmvb视频文件: " + fileName);}
}
/*** 抽象的操作系统(抽象化角色)** @see [相关类/方法]* @since 2024/9/26*/
public abstract class OperatingSystem {// 声明videoFile变量protected VideoFile videoFile;public OperatingSystem(VideoFile videoFile) {this.videoFile = videoFile;}public abstract void play(String fileName);
}
/*** windows操作系统(扩展抽象化角色)** @see [相关类/方法]* @since 2024/9/26*/
public class Windows extends OperatingSystem {public Windows(VideoFile videoFile) {super(videoFile);}@Overridepublic void play(String fileName) {videoFile.decode(fileName);}
}
/*** Mac操作系统(扩展抽象化角色)** @see [相关类/方法]* @since 2024/9/26*/
public class Mac extends OperatingSystem {public Mac(VideoFile videoFile) {super(videoFile);}@Overridepublic void play(String fileName) {videoFile.decode(fileName);}
}
4.优点
桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统
相关文章:
设计模式学习
1.设计模式分类 1.创建型模式 用于描述“怎样创建对象”,主要特点是“将对象的创建与使用分离”。 单例,原型,工厂方法,抽象工厂,建造者 2.结构型模式 用于描述如何将类或对象按某种布局组成更大的结构 代理&…...
js-对象-JSON
JavaScript自定义对象 JSON 概念: JavaScript Object Notation,JavaScript对象标记法. JSON 是通过JavaScript 对象标记法书写的文本。 由于其语法简单,层次结构鲜明,现多用于作为数据载体,在网络中进行数据传输. json中属性名(k…...
C/C++编译器
C/C 代码是不可跨平台的,Windows 和 Unix-like 有着不同的 API,C/C 在不同平台有着不同编译器。 MSVC Windows 平台,MSVC 是 Visual Studio 中自带的 C/C 编译器。 GCC Unix-like 平台,GCC 原名 GNU C Compiler,后…...
【R语言】数据操作
一、查看和编辑数据 1、查看数据 直接打印到控制台 x <- data.frame(a1:20, b21:30) x View()函数 此函数可以将数据以电子表格的形式进行展示。 用reshape2包中的tips进行举例: library("reshape2") View(tips) head()函数 查看前几行数据&…...
Linux 安装 RabbitMQ
Linux下安装RabbitMQ 1 、获取安装包 # 地址 https://github.com/rabbitmq/erlang-rpm/releases/download/v21.3.8.9/erlang-21.3.8.9-1.el7.x86_64.rpm erlang-21.3.8.9-1.el7.x86_64.rpmsocat-1.7.3.2-1.el6.lux.x86_64.rpm# 地址 https://github.com/rabbitmq/rabbitmq-se…...
“AI智能分析综合管理系统:企业管理的智慧中枢
在如今这个快节奏的商业世界里,企业面临的挑战越来越多,数据像潮水一样涌来,管理工作变得愈发复杂。为了应对这些难题,AI智能分析综合管理系统闪亮登场,它就像是企业的智慧中枢,让管理变得轻松又高效。 过去…...
2024最新版Java面试题及答案,【来自于各大厂】
发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全~ 篇幅限制就只能给大家展示小册部分内容了,需要完整版的及Java面试宝典小伙伴点赞转发,关注我后在【翻到最下方,文尾点击名片】即可免费获取…...
调用腾讯云批量文本翻译API翻译srt字幕
上一篇文章介绍了调用百度翻译API翻译日文srt字幕的方法。百度翻译API是get方式调用,参数都放在ur中,每次调用翻译文本长度除了接口限制外,还有url长度限制,而日文字符通过ur转码后会占9个字符长度,其实从这个角度来讲…...
【分块解决大文件上传的最佳实践】
前言 前几天看了一篇关于大文件上传分块实现的博客,代码实现过于复杂且冗长,而且没有进行外网上传的测试。因此,我决定自己动手实现一个大文件上传,并进行优化。 实现思路 在许多应用中,大文件上传是常见的需求&…...
机器学习中的关键概念:通过SKlearn的MNIST实验深入理解
欢迎来到我的主页:【Echo-Nie】 本篇文章收录于专栏【机器学习】 1 sklearn相关介绍 Scikit-learn 是一个广泛使用的开源机器学习库,提供了简单而高效的数据挖掘和数据分析工具。它建立在 NumPy、SciPy 和 matplotlib 等科学计算库之上,支持…...
【Elasticsearch】post_filter
post_filter是 Elasticsearch 中的一种后置过滤机制,用于在查询执行完成后对结果进行过滤。以下是关于post_filter的详细介绍: 工作原理 • 查询后过滤:post_filter在查询执行完毕后对返回的文档集进行过滤。这意味着所有与查询匹配的文档都…...
Git基础
目录 一、Git介绍二、Git下载与配置1、下载安装Git2、Git配置2.1 注册码云账号2.2 Git配置 三、Git开发流程1、相关代码2、上述代码执行截图示例 四、Git提交&撤销五、Git资料 一、Git介绍 Git是一种分布式版本控制系统,广泛用于软件开发项目的版本管理。它由L…...
深度学习系列--02.损失函数
一.定义 损失函数(Loss Function)是机器学习和深度学习中用于衡量模型预测结果与真实标签之间差异的函数,它在模型训练和评估过程中起着至关重要的作用 二.作用 1.指导模型训练 提供优化方向:在训练模型时,我们的目…...
如何在自己mac电脑上私有化部署deep seek
在 Mac 电脑上私有化部署 DeepSeek 的步骤如下: 1. 环境准备 安装 Homebrew(如果尚未安装): Homebrew 是 macOS 上的包管理工具,用于安装依赖。 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com…...
攻防世界 fileclude
代码审计 WRONG WAY! <?php include("flag.php"); highlight_file(__FILE__);//高亮显示文件的源代码 if(isset($_GET["file1"]) && isset($_GET["file2"]))//检查file1和file2参数是否存在 {$file1 $_GET["file1"];$fi…...
Ubuntu24登录PostgreSql数据库的一般方法
命令格式如 psql -U user -d db 或者 sudo psql -U user -d db 修改配置 /etc/postgresql/16/main/postgresql.conf 改成md5,然后重新启动pgsql sudo systemctl restart postgresql...
3.5 Go(特殊函数)
目录 一、匿名函数 1、匿名函数的特点: 2、匿名函数代码示例 2、匿名函数的类型 二、递归函数 1. 递推公式版本 2. 循环改递归 三、嵌套函数 1、嵌套函数用途 2、代码示例 3、作用域 & 变量生存周期 四、闭包 1、闭包使用场景 2、代码示例 五、De…...
设计模式学习(三)
行为模式 职责链模式(Chain of Responsibility Pattern) 定义 它允许多个对象有机会处理请求,从而避免请求的发送者与接收者之间的耦合。职责链模式将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止…...
挑战项目 --- 微服务编程测评系统(在线OJ系统)
一、前言 1.为什么要做项目 面试官要问项目,考察你到底是理论派还是实战派? 1.希望从你的项目中看到你的真实能力和对知识的灵活运用。 2.展示你在面对问题和需求时的思考方式及解决问题的能力。 3.面试官会就你项目提出一些问题,或扩展需求…...
堆(Heap)的原理与C++实现
1. 什么是堆? 堆(Heap)是一种特殊的树形数据结构,通常用于实现优先队列。堆可以分为两种类型: 最大堆(Max Heap):每个节点的值都大于或等于其子节点的值。最小堆(Min H…...
(10) 如何获取 linux 系统上的 TCP 、 UDP 套接字的收发缓存的默认大小,以及代码范例
(1) 先介绍下后面的代码里要用到的基础函数: 以及: (2) 接着给出现代版的 读写 socket 参数的系统函数 : 以及: (3) 给出 一言的 范例代码,获取…...
linux 进程补充
环境变量 基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如:我们在编写C/C代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪 里,但是照样可以链接成功&#…...
通过docker安装部署deepseek以及python实现
前提条件 Docker 安装:确保你的系统已经安装并正确配置了 Docker。可以通过运行 docker --version 来验证 Docker 是否安装成功。 网络环境:保证设备有稳定的网络连接,以便拉取 Docker 镜像和模型文件。 步骤一:拉取 Ollama Docker 镜像 Ollama 可以帮助我们更方便地管理…...
vim-plug的自动安装与基本使用介绍
vim-plug介绍 Vim-plug 是一个轻量级的 Vim 插件管理器,它允许你轻松地管理 Vim 插件的安装、更新和卸载。相较于其他插件管理器,vim-plug 的优点是简单易用,速度较快,而且支持懒加载插件(即按需加载) 自动…...
Python 自学秘籍:开启编程之旅,人生苦短,我用python。
从2009年,用了几次python后就放弃了,一直用的php,现在人工智能时代,完全没php什么事情。必须搞python了,虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代ÿ…...
DRGDIP 2.0时代下基于PostgreSQL的成本管理实践与探索(上)
一、引言 1.1 研究背景与意义 在医疗领域的改革进程中, DRG/DIP 2.0 时代,医院成本管理的重要性愈发凸显。新的医保支付方式下,医院的收入不再单纯取决于医疗服务项目的数量,而是与病种的分组、费用标准以及成本控制紧密相关。这…...
swift 专题三 swift 规范一
一、Swift编码命名规范 对类、结构体、枚举和协议等类型的命名应该采用大驼峰法,如 SplitViewController。 文件名采用大驼峰法,如BlockOperation.swift。 对于扩展文件,有时扩展定义在一个独立的文件中,用“原始类型名 扩展名…...
Vue的状态管理:用响应式 API 做简单状态管理、状态管理库(Pinia )
文章目录 引言单向数据流多个组件共享一个共同的状态I 用响应式 API 做简单状态管理使用 reactive()创建一个在多个组件实例间共享的响应式对象使用ref()返回一个全局状态II 状态管理库Pinia枚举状态管理引言 单向数据流 每一个 Vue 组件实例都在“管理”它自己的响应式状态了…...
排序算法--希尔排序
希尔排序是插入排序的改进版本,适合中等规模数据排序,性能优于简单插入排序。 // 希尔排序函数 void shellSort(int arr[], int n) {// 初始间隔(gap)为数组长度的一半,逐步缩小for (int gap n / 2; gap > 0; gap …...
HAL库 Systick定时器 基于STM32F103EZT6 野火霸道,可做参考
目录 1.时钟选择(这里选择高速外部时钟) 编辑 2.调试模式和时基源选择: 3.LED的GPIO配置 这里用板子的红灯PB5 4.工程配置 5.1ms的systick中断实现led闪烁 源码: 6.修改systick的中断频率 7.systick定时原理 SysTick 定时器的工作原理 中断触发机制 HAL_SYSTICK_Co…...
ESXI虚拟机中部署docker会降低服务器性能
在 8 核 16GB 的 ESXi 虚拟机中部署 Docker 的性能影响分析 在 ESXi 虚拟机中运行 Docker 容器时,性能影响主要来自以下几个方面: 虚拟化开销:ESXi 虚拟化层和 Docker 容器化层的叠加。资源竞争:虚拟机与容器之间对 CPU、内存、…...
前端 | JavaScript中的reduce方法
1. 什么是reduce reduce 方法是 JavaScript 中数组的重要方法之一,用于对数组中的元素进行累积计算。它接收一个回调函数作为参数,并返回一个最终计算结果。reduce 在许多场景下都非常有用,比如求和、数组扁平化、对象计数、数据转换等。 2…...
基于联合概率密度与深度优化的反潜航空深弹命中概率模型研究摘要
前言:项目题材来自数学建模2024年的D题,文章内容为笔者和队友原创,提供一个思路。 摘要 随着现代军事技术的发展,深水炸弹在特定场景下的反潜作战效能日益凸显,如何最大化的发挥深弹威力也成为重要研究课题。本文针对评估深弹投掷落点对命中潜艇概率的影响进行分析,综合利…...
将OneDrive上的文件定期备份到移动硬盘
背景: 我在oneDrive上存了很多文件,分布在多个文件夹中,也有套了好几层文件夹的情况。我希望每隔一段时间,将oneDrive上的所有文件向移动硬盘上拷贝一份,但是我只想将距离上一次向移动硬盘拷贝的文件相比,发…...
01vue3实战-----前言
01vue3实战-----前言 1.大前端时代2.技术栈3.项目大致展示4.创建Vue项目4.1Vue CLI4.2create-vue 5.参考资料 1.大前端时代 前端移动端iOS/android开发桌面端 window/mac 常用的electron框架来开发其它平台:穿戴设备、车载系统(智能汽车)、VR、AR…web3方向 2.技术栈 开发工…...
SQL 秒变三线表 sql导出三线表
🎯SQL 秒变三线表,校园小助手超神啦 宝子们,搞数据分析、写论文的时候,从 SQL 里导出数据做成三线表是不是特别让人头疼😩 手动调整格式,不仅繁琐,还容易出错,分分钟把人逼疯&#…...
C_位运算符及其在单片机寄存器的操作
C语言的位运算符用于直接操作二进制位,本篇简单结束各个位运算符的作业及其在操作寄存器的应用场景。 一、位运算符的简单说明 1、按位与运算符(&) 功能:按位与运算符对两个操作数的每一位执行与操作。如果两个对应的二进制…...
Rust错误处理:从灭火器到核按钮的生存指南
开篇:错误处理的生存哲学 在Rust的平行宇宙里,错误分为两种人格: panic! → 核按钮💣(不可恢复,全系统警报)Result → 灭火器🧯(可控制,局部处理࿰…...
YK人工智能(六)——万字长文学会基于Torch模型网络可视化
1. 可视化网络结构 随着深度神经网络做的的发展,网络的结构越来越复杂,我们也很难确定每一层的输入结构,输出结构以及参数等信息,这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的…...
对象的实例化、内存布局与访问定位
一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化…...
Java面试题2025-并发编程进阶(线程池和并发容器类)
线程池 一、什么是线程池 为什么要使用线程池 在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可…...
Vue Router 客户端路由解决方案:axios 响应拦截(跳转到登录页面)
文章目录 引言客户端路由 vs. 服务端路由简单的路由案例术语I Vue Router 提供的组件RouterLinkRouterViewII 创建路由器实例调用 createRouter() 函数创建路由选项III 注册路由器插件通过调用 use() 来完成注册路由器插件的职责对于组合式 API,Vue Router 给我们提供了一些组…...
Redis的通用命令
⭐️前言⭐️ 本文主要介绍Redis的通用命令 🍉欢迎点赞 👍 收藏 ⭐留言评论 🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言 🍉博客中涉及源码及博主日常练习代码均已上传GitHub 📍内容导…...
[Python人工智能] 四十九.PyTorch入门 (4)利用基础模块构建神经网络并实现分类预测
从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建回归神经网络。这篇文章将介绍如何利用PyTorch构建神经网络实现分类预测,其是使用基础模块构建。前面我们的Python人工智能主要以TensorFlow和Keras为主,而现在最主流的深度学习框…...
SpringBoot使用 easy-captcha 实现验证码登录功能
文章目录 一、 环境准备1. 解决思路2. 接口文档3. redis下载 二、后端实现1. 引入依赖2. 添加配置3. 后端代码实现4. 前端代码实现 在前后端分离的项目中,登录功能是必不可少的。为了提高安全性,通常会加入验证码验证。 easy-captcha 是一个简单易用的验…...
RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)
#作者:闫乾苓 文章目录 RabbitMQ简介RabbitMQ与VMware的关系架构工作流程RabbitMQ 队列工作模式及适用场景简单队列模式(Simple Queue)工作队列模式(Work Queue)发布/订阅模式(Publish/Subscribeÿ…...
Unity中的虚拟相机(Cinemachine)
Unity Cinemachine详解 什么是Cinemachine Cinemachine是Unity官方推出的智能相机系统,它提供了一套完整的工具来创建复杂的相机运动和行为,而无需编写大量代码。它能够大大简化相机管理,提高游戏开发效率。 Cinemachine的主要组件 1. Vi…...
响应式编程_04Spring 5 中的响应式编程技术栈_WebFlux 和 Spring Data Reactive
文章目录 概述响应式Web框架Spring WebFlux响应式数据访问Spring Data Reactive 概述 https://spring.io/reactive 2017 年,Spring 发布了新版本 Spring 5, Spring 5 引入了很多核心功能,这其中重要的就是全面拥抱了响应式编程的设计思想和实…...
网络设备的安全加固
设备的安全始终是信息网络安全的一个重要方面,攻击者往往通过控制网络中设备来破坏系统和信息,或扩大已有的破坏。网络设备包括主机(服务器、工作站、PC)和网络设施(交换机、路由器等)。 一般说来ÿ…...
OpenCV:特征检测总结
目录 一、什么是特征检测? 二、OpenCV 中的常见特征检测方法 1. Harris 角点检测 2. Shi-Tomasi 角点检测 3. Canny 边缘检测 4. SIFT(尺度不变特征变换) 5. ORB 三、特征检测的应用场景 1. 图像匹配 2. 运动检测 3. 自动驾驶 4.…...