Java常用设计模式
单例模式
单例模式就是: 在程序运行期间, 某些类有且最多只有一个实例对象
饿汉模式(静态常量)
饥饿模式又称为饿汉模式, 指的是JVM在加载类的时候就完成类对象的创建
//饿汉式(静态常量)
public class Singleton1 {//构造器私有化,外部不能newprivate Singleton1() {}//本类创建对象实例private final static Singleton1 instance = new Singleton1();//提供一个公有的静态方法,返回对象实例public static Singleton1 getInstance() {return instance;}
}
- 优点:JVM层面的线程安全。JVM在加载这个类的时候就会对它进行初始化, 因此JVM层面包证了线程安全
- 缺点:造成空间的浪费
饿汉模式(静态代码块)
//饿汉式(静态代码块)
public class Singleton2 {//构造器私有化,外部不能newprivate Singleton2() {}//本类创建对象实例private static Singleton2 instance;static {instance = new Singleton2();}//提供一个公有的静态方法,返回对象实例public static Singleton2 getInstance() {return instance;}
}
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
饿汉模式(枚举)
//枚举
public enum Singleton8 {INSTANCE
}
枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的(枚举类也是在JVM层面保证的线程安全),并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。
懒汉模式(线程不安全,不可用)
真正需要的时候再完成类对象的创建
//懒汉式(线程不安全)
public class Singleton3 {private static Singleton3 instance;private Singleton3() {}//提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式public static Singleton3 getInstance() {if (instance == null){instance = new Singleton3();}return instance;}
}
- 优点:节省空间
- 缺点:线程不安全
懒汉模式(线程安全,同步方法,不推荐用)
通过synchronized
关键字对获取实例的方法进行同步限制, 实现了线程安全
//懒汉式(线程安全)
public class Singleton4 {private static Singleton4 instance;private Singleton4() {}//提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式public static synchronized Singleton4 getInstance() {if (instance == null){instance = new Singleton4();}return instance;}
}
- 优点:线程安全
- 缺点:对所有线程的访问都会进行同步操作, 有很严重的性能问题
懒汉模式(线程不安全,同步代码块,不可用)
//懒汉式(线程安全, 同步代码块)
public class Singleton5 {private static Singleton5 instance;private Singleton5(){};//提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式public static Singleton5 getInstance() {if (instance == null){synchronized(Singleton5.class){instance = new Singleton5();}}return instance;}
}
这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
懒汉模式(线程安全,双重检查, 推荐用)
双重检查锁(Double Checked Locking, 简称DCL)模式
//双重检查
public class Singleton6 {private Singleton6() {}private static volatile Singleton6 instance;public static Singleton6 getInstance() {//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际if (instance == null){synchronized(Singleton5.class){//抢到锁之后再次判断是否为空if (instance == null){instance = new Singleton6();}}}return instance;}
}
在多处理器的共享内存、或者编译器的优化下, DCL模式并不一定线程 —— 可能 (注意: 只是可能出现) 会发生指令的重排序, 出现半个对象的问题
Java中创建一个对象的过程并不是原子性操作,可能会发生指令的重排序(先把这个实例的引用指向地址,再对成员初始化), 出现半个对象的问题
因此要用volatile
关键字修饰instance
变量
半对象问题:当一个线程进来的时候,判断对象是否为空?肯定为空,因为还没创建呢,往下执行,拿到锁,继续往下执行,再次判断是否为空?为空,往下执行,在new对象的时候,对象有个半初始化的一个状态,在执行完new的时候,分配了一块空间,成员变量是引用类型那么它的值为null,就在此时,invokespecial
和astore 1
发生了指令重排序,直接将instance
指向了初始化一半还没有调用构造方法的内存空间,这时候第二个线程进来了,判断对象为空吗?不为空,为啥?因为它指向了一个半初始化的一个对象嘛!既然不为空,我就直接返回了这个初始化一半的对象
懒汉式(线程安全,静态内部类,推荐用)
//静态内部类
public class Singleton7 {private Singleton7() {}//提供一个静态的公有方法,当使用到该方法时,才去创建instance 即懒汉式private static class SingletonInstance {private static final Singleton7 INSTANCE = new Singleton7();}public static Singleton7 getInstance() {return SingletonInstance.INSTANCE;}
}
- JVM在加载外部类的过程中, 是不会加载静态内部类的, 只有内部类(SingletonHolder)的属性/方法被调用时才会被加载, 并初始化其静态属性(instance)
- 优点:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
破坏单例模式
除枚举方式外, 其他方法都会通过反射的方式破坏单例
- 反射是通过调用构造方法生成新的对象, 可以在构造方法中进行判断 —— 若已有实例, 则阻止生成新的实例,
private Singleton() throws Exception {if (instance != null) {throw new Exception("Singleton already initialized, 此类是单例类, 不允许生成新对象, 请通过getInstance()获取本类对象");}
}
- 如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例。可以不实现序列化接口, 或者重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象
// 反序列化时直接返回当前实例
public Object readResolve() {return instance;
}
- Object#clone()方法也会破坏单例, 即使你没有实现Cloneable接口 —— 因为clone()方法是Object类中的。可以重写clone()方法, 并在其中抛出异常信息“Can not create clone of Singleton class”
工厂模式
简单工厂模式
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
-
Factory: 工厂角色 负责根据不同的参数创建不同的实例。
-
IProduct: 抽象产品角色 所有产品实例的接口,负责描述所有产品实例的行为。
-
Product(A B …): 具象产品角色,所有产品的实例,实现了抽象产品定义的代码
示例:
平台做一个机票代购业务,对接了两个供应商A、B,用户选择完机票后,平台拿着机票去供应商下单。下单时根据机票由那个供应商提供去相应的供应商去下单。
- 定义一个下单接口
public interface IVender {/*** 供应商下单方法*/void order();
}
- 分别实现A、B供应商的下单方法
public class VendorA implements IVender {@Overridepublic void order() {// 业务逻辑处理System.out.println("A供应商下单成功,下单时间" + new Date());}
}public class VendorB implements IVender {@Overridepublic void order() {// 业务逻辑处理System.out.println("B供应商下单成功,下单时间:" + new Date());}
}
- 接着定义一个工厂类,根据传入的不同参数请求,分别创建不同的供应商实例并返回,若碰到无效的参数,则抛出异常
public class VendorFactory {public static IVender createVendor(String type) {switch (type) {case "A":return new VendorA();case "B":return new VendorB();default:throw new RuntimeException("供应商不存在");}}
}
- 最后,由我们客户端进行调用
public class Client {public static void main(String[] args) {String type = "A";IVender iVender = VendorFactory.createVendor(type);iVender.order();}
}
缺点:缺点在于不符合开闭原则
,每次添加新产品就需要修改工厂类。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展维护,并且工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
工厂方法模式
- 工厂方法模式将工厂抽象化,并定义一个创建对象的接口。每增加新产品,只需增加该产品以及对应的具体实现工厂类,由具体工厂类决定要实例化的产品是哪个,将对象的创建与实例化延迟到子类,这样工厂的设计就符合“开闭原则”了,扩展时不必去修改原来的代码。
- 缺点:但缺点在于,每增加一个产品都需要增加一个具体产品类和实现工厂类,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。
- 抽象产品
// 工厂方法的抽象产品
public interface Interviewer {void askQuestion();
}
- 具体产品
// 具体产品
public class Developer implements Interviewer{@Overridepublic void askQuestion() {System.out.println("询问设计模式相关的问题");}
}public class CommunityExecutive implements Interviewer{@Overridepublic void askQuestion() {System.out.println("询问社区建设相关的问题");}
}
- 抽象工厂
//抽象工厂类
public abstract class HiringManager {// 抽象工厂方法protected abstract Interviewer makeInterviewer();public void takeInterviewer() {Interviewer interviewer = makeInterviewer(); //创建具体的interviewer.askQuestion();}
}
- 具体工厂(决定要实例化的产品是哪个)
// 实现工厂类
public class DevelopmentManager extends HiringManager{@Overrideprotected Interviewer makeInterviewer() {return new Developer();}
}public class MarketingManager extends HiringManager{@Overrideprotected Interviewer makeInterviewer() {return new CommunityExecutive();}
}
策略模式
策略模式定义了一系列的算法,并将每一个算法封装起来,使每个算法可以相互替代,使算法本身和使用算法的客户端分割开来,相互独立
- 策略接口角色IStrategy:用来约束一系列具体的策略算法,策略上下文角色ConcreteStrategy使用此策略接口来调用具体的策略所实现的算法
//策略接口
public interface IStrategy {//定义的抽象算法方法 来约束具体的算法实现方法public void algorithmMethod();
}
- 具体策略实现角色ConcreteStrategy:具体的策略实现,即具体的算法实现
// 具体的策略实现2
public class ConcreteStrategy implements IStrategy {//具体的算法实现@Overridepublic void algorithmMethod() {System.out.println("this is ConcreteStrategy method...");}
}
- 策略上下文角色StrategyContext:策略上下文,负责具体的策略实现交互,通常策略上下文对象会持有一个真正的策略实现对象,策略上下文还可以让具体的策略实现从其中获取相关数据,回调策略上下文对象的方法。
/*** 策略上下文*/
public class StrategyContext {//持有一个策略实现的引用private IStrategy strategy;//使用构造器注入具体的策略类public StrategyContext(IStrategy strategy) {this.strategy = strategy;}public void contextMethod(){//调用策略实现的方法strategy.algorithmMethod();}
}
- 外部客户端
//外部客户端
public class Client {public static void main(String[] args) {//1.创建具体测策略实现IStrategy strategy = new ConcreteStrategy();//2.在创建策略上下文的同时,将具体的策略实现对象注入到策略上下文当中StrategyContext ctx = new StrategyContext(strategy);//3.调用上下文对象的方法来完成对具体策略实现的回调ctx.contextMethod();}
}
- 缺点:
- 客户端必须了解所有的策略,清楚它们的不同:
如果由客户端来决定使用何种算法,那客户端必须知道所有的策略,清楚各个策略的功能和不同,这样才能做出正确的选择,但是这暴露了策略的具体实现 - 增加了对象的数量:
由于策略模式将每个具体的算法都单独封装为一个策略类,如果可选的策略有很多的话,那对象的数量也会很多 - 只适合偏平的算法结构:
由于策略模式的各个策略实现是平等的关系(可相互替换),实际上就构成了一个扁平的算法结构。即一个策略接口下面有多个平等的策略实现(多个策略实现是兄弟关系),并且运行时只能有一个算法被使用。这就限制了算法的使用层级,且不能被嵌套
- 本质:
分离算法,选择实现。如果没有上下文,策略模式就回到了最基本的接口和实现了,只要是面向接口编程,就能够享受到面向接口编程带来的好处,通过一个统一的策略接口来封装和分离各个具体的策略实现,无需关系具体的策略实现。貌似没有上下文什么事,但是如果没有上下文的话,客户端就必须直接和具体的策略实现进行交互了,尤其是需要提供一些公共功能或者是存储一些状态的时候,会大大增加客户端使用的难度;引入上下文之后,这部分工作可以由上下文来完成,客户端只需要和上下文进行交互就可以了。这样可以让策略模式更具有整体性,客户端也更加的简单
代理模式
- 前言:代理(Proxy)模式是一种结构型设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象。
代理模式大致有三种角色:
- Real Subject:真实类,也就是被代理类、委托类。用来真正完成业务服务功能;
- Proxy:代理类,将自身的请求用 Real Subject 对应的功能来实现,代理类对象并不真正的去实现其业务功能;
- Subject:定义 RealSubject 和 Proxy 角色都应该实现的接口。
静态代理
静态代理需要先定义接口,被代理对象与代理对象一起实现相同的接口,然后通过调用相同的方法来调用目标对象的方法。
![外
- 优点:静态代理模式在不改变目标对象的前提下,实现了对目标对象的功能扩展。
- 缺点:静态代理实现了目标对象的所有方法,一旦目标接口增加方法,代理对象和目标对象都要进行相应的修改,增加维护成本。
动态代理
JDK代理
-
原理:JDK动态代理对象不需要实现接口,但是目标对象必须实现接口。代理对象会实现与目标类一样的方法,并将方法调用转发给目标对象
-
样例:有一天公司增加了业务,出售的商品越来越多,售后也需要更上。但是公司发现原来的代理商,还要再培训才能完成全部的业务,于是就找了另外的动态代理商B 。 代理商B 承诺无缝对接公司所有的业务,不管新增什么业务,均不需要额外的培训即可完成
- 公司增加了维修业务
//接口添加方法
public interface TVCompany {/*** 生产电视机* @return 电视机*/public TV produceTV();/*** 维修电视机* @param tv 电视机* @return 电视机*/public TV repair(TV tv);
}
- 工厂也得把维修业务搞起来
//新增目标类
public class TVFactory implements TVCompany {@Overridepublic TV produceTV() {System.out.println("TV factory produce TV...");return new TV("小米电视机","北京");}@Overridepublic TV repair(TV tv) {System.out.println("tv is repair finished...");return new TV("小米电视机","北京");}
}
- B代理商 全面代理公司所有的业务。使用Proxy.newProxyInstance方法生成代理对象,实现InvocationHandler中的 invoke方法,在invoke方法中通过反射调用代理类的方法,并提供增强方法
//新增代理类
public class TVProxyFactory {private Object target;public TVProxyFactory(Object o){this.target = o;}/*ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。*/public Object getProxy(){return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("TV proxy find factory for tv.... ");Object invoke = method.invoke(target, args);return invoke;}});}
}
- 客户端调用
public class TVConsumer {public static void main(String[] args) {TVCompany target = new TVFactory();TVCompany tvCompany = (TVCompany) new TVProxyFactory(target).getProxy();TV tv = tvCompany.produceTV();tvCompany.repair(tv);}
}
缺点:JDK 动态代理有一个最致命的问题是它只能代理实现了某个接口的实现类(因为java是单继承,生成的新的代理类继承Proxy),并且代理类也只能代理接口中实现的方法,要是实现类中有自己私有的方法,而接口中没有的话,该方法不能进行代理调用
事务失效场景:内部调用,当类内部的方法调用另一个带有 @Transactional 注解的方法时,这个调用不会通过 Spring 的代理对象进行,而是直接通过 this 引用,因此 Spring 无法拦截并应用事务。Spring AOP 代理机制只能拦截通过代理对象进行的方法调用,而不能拦截类内部的直接方法调用
解决:
其中一种解决方法是在类内部通过 Spring 容器获取当前对象的代理实例,然后通过代理对象调用目标方法,从而让事务生效
@Service
public class TransactionService {@Autowiredprivate ApplicationContext context;@Transactionalpublic void publicMethod() {// 从 Spring 容器中获取代理对象TransactionService proxy = context.getBean(TransactionService.class);proxy.internalMethod(); // 通过代理对象调用方法,事务生效}@Transactionalpublic void internalMethod() {// 事务在这里生效}
}
Cglib代理
Cglib代理可以称为子类代理,是在内存中构建一个子类对象,从而实现对目标对象功能的扩展。它不要求目标类实现接口中的方法,而是基于字节码生成技术,生成目标类的子类作为代理类,并重写父类的方法和增强逻辑
Cglib通过Enhancer 来生成代理类,通过实现MethodInterceptor接口,并实现其中的intercept方法,在此方法中可以添加增强方法,并可以利用反射Method或者MethodProxy继承类 来调用原方法
public class TVProxyCglib implements MethodInterceptor {//给目标对象创建一个代理对象public Object getProxyInstance(Class c){//1.工具类Enhancer enhancer = new Enhancer();//2.设置父类enhancer.setSuperclass(c);//3.设置回调函数,调用方法的时候先调用intercept(拦截器)方法,执行我们定义的方法的增强链(也就是设置)enhancer.setCallback(this);//4.创建子类(代理对象)return enhancer.create();}@Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {System.out.println("TVProxyFactory enhancement.....");Object object = methodProxy.invokeSuper(o, objects);return object;}
}
新代理的B工厂
public class TVFactoryB {public TV produceTVB() {System.out.println("tv factory B producing tv.... ");return new TV("华为电视机", "南京");}public TV repairB(TV tv) {System.out.println("tv B is repair finished.... ");return tv;}
}
C代理可以直接和公司合作,也可以和工厂打交道。并且可以代理任何工厂的产品。
public class TVConsumer {public static void main(String[] args) {TVCompany tvCompany = (TVCompany) new TVProxyCglib().getProxyInstance(TVFactory.class);TV tv = tvCompany.produceTV();tvCompany.repair(tv);System.out.println("==============================");TVFactoryB tvFactoryB = (TVFactoryB) new TVProxyCglib().getProxyInstance(TVFactoryB.class);TV tv = tvFactoryB.produceTVB();tvFactoryB.repairB(tv);}
}
打印结果
TVProxyFactory enhancement.....
TV factory produce TV...
TVProxyFactory enhancement.....
tv is repair finished...
==============================
TVProxyFactory enhancement.....
tv factory B producing tv....
TVProxyFactory enhancement.....
tv B is repair finished.... Process finished with exit code 0
Spring AOP使用代理
Spring中AOP的实现有JDK和Cglib两种,如下图:
- 如果目标对象需要实现接口,则使用JDK代理
- 如果目标对象不需要实现接口,则使用Cglib代理
总结
- 静态代理:需要代理类和目标类都实现接口的方法,从而达到代理增强其功能
- JDK动态代理:需要代理类实现某个接口,使用Proxy.newProxyInstance方法生成代理类,并实现InvocationHandler中的invoke方法,实现增强功能
- Cglib动态代理:无需代理类实现接口,使用Cblib中的Enhancer来生成代理对象子类,并实现MethodInterceptor中的intercept方法,在此方法中可以实现增强功能
模板方法模式
核心思想是:父类定义骨架,子类实现某些细节
为了防止子类重写父类的骨架方法,可以在父类中对骨架方法使用final。对于需要子类实现的抽象方法,一般声明为protected,使得这些方法对外部客户端不可见
- 父类定义骨架
public abstract class AbstractSetting {public final String getSetting(String key) {//从缓存读取String value = lookupCache(key);if (value == null) {// 在缓存中未找到,从数据库读取value = readFromDatabase(key);// 放入缓存putIntoCache(key, value);}return value;}protected abstract String lookupCache(String key);protected abstract void putIntoCache(String key, String value);
}
- 子类实现某些细节
public class RedisSetting extends AbstractSetting {private RedisClient client = RedisClient.create("redis://localhost:6379");protected String lookupCache(String key) {try (StatefulRedisConnection<String, String> connection = client.connect()) {RedisCommands<String, String> commands = connection.sync();return commands.get(key);}}protected void putIntoCache(String key, String value) {try (StatefulRedisConnection<String, String> connection = client.connect()) {RedisCommands<String, String> commands = connection.sync();commands.set(key, value);}}
}
- 客户端调用
AbstractSetting setting = new RedisSetting();
System.out.println("autosave = " + setting.getSetting("autosave"));
System.out.println("autosave = " + setting.getSetting("autosave"));
ng> connection = client.connect()) {RedisCommands<String, String> commands = connection.sync();return commands.get(key);}}protected void putIntoCache(String key, String value) {try (StatefulRedisConnection<String, String> connection = client.connect()) {RedisCommands<String, String> commands = connection.sync();commands.set(key, value);}}
}
- 客户端调用
AbstractSetting setting = new RedisSetting();
System.out.println("autosave = " + setting.getSetting("autosave"));
System.out.println("autosave = " + setting.getSetting("autosave"));
观察者模式
基本理解
观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式
- 优点:
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则
- 目标与观察者之间建立了一套触发机制
- 缺点:
- 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
- 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
- 观察者模式的结构:
- 抽象主题(subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的方法
- 具体主题(Concrete subject)角色:也叫具体目标类,实现了抽象目标类的方法,当具体主题的内部状态发生变化的时候,通知所有注册过的观察者对象
- 抽象观察者(Observer)角色:它是一个抽象类或者接口,它包含了一个更新自己的抽象方法,当接受到具体主题的更改通知时被调用
- 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
示例:
Observer
// 抽象观察者
public interface Observer {//更新的方法void update(String messages);
}
WexinUser
//具体观察者类 实现更新的方法
public class WexinUser implements Observer {//用户名private String name;public WexinUser(String name) {this.name = name;}public WexinUser() {}@Overridepublic void update(String messages) {System.out.println(name + "-->" + messages);}
}
Subject
//抽象主题类
public interface Subject {//增加订阅者public void attach(Observer observer);//删除订阅者public void remove(Observer observer);//通知订阅者更新消息public void notify(String messages);
}
SubscriptionSubject
//具体主题(具体被观察者)
public class SubscriptionSubject implements Subject {//存储订阅公众号的微信用户private List<Observer> weixinUserList = new ArrayList<Observer>();@Overridepublic void attach(Observer observer) {weixinUserList.add(observer);}@Overridepublic void remove(Observer observer) {weixinUserList.remove(observer);}
}
Client
public class Client {public static void main(String[] args) {SubscriptionSubject subject = new SubscriptionSubject();//创建微信用户WexinUser user1 = new WexinUser("张三");WexinUser user2 = new WexinUser("李四");WexinUser user3 = new WexinUser("王五");//订阅公众号subject.attach(user1);subject.attach(user2);subject.attach(user3);//通过订阅用户subject.notify("您关注的公众号更新啦~~~");}
}
JDK源码解析
在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例
- Observable类(抽象被观察者)
Observable 类是抽象目标类(被观察者),它有一个 Vector 集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的3个方法
- void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中
- void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知
- void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者
- Observer 接口(抽象观察者)
Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 update 方法,进行相应的工作
示例:
警擦(观察者)抓小偷(被观察者),当小偷偷东西的时警擦会被通知
Thief(被观察者)
//小偷类 继承Observable接口
import java.util.Observable;public class Thief extends Observable {private String name;public Thief(String name) {this.name = name;}public Thief() {}public String getName() {return name;}public void setName(String name) {this.name = name;}public void steal() {System.out.println("emmm我正在悄悄偷东西");super.setChanged();//默认为truesuper.notifyObservers();}
}
Policemen(观察者)
import java.util.Observable;
import java.util.Observer;
public class Policeman implements Observer {private String name;public Policeman(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Policeman() {}@Overridepublic void update(Observable o, Object arg) {System.out.println("警察:" + ((Thief) o).getName() + "我抓住你了!!!");}
}
Client
public class Client {public static void main(String[] args) {//小偷(被观察者)Thief thief = new Thief("法外狂徒格雷福斯");//警察(观察者)Policeman policeman = new Policeman("凯瑟琳女警");//警察观察小偷thief.addObserver(policeman);//小偷行窃thief.steal();}
}/* 打印结果:emmm我正在悄悄偷东西警察:法外狂徒格雷福斯我抓住你了!!!
*/
相关文章:
Java常用设计模式
单例模式 单例模式就是: 在程序运行期间, 某些类有且最多只有一个实例对象 饿汉模式(静态常量) 饥饿模式又称为饿汉模式, 指的是JVM在加载类的时候就完成类对象的创建 //饿汉式(静态常量) public class Singleton1 {//构造器私有化,外部不能newprivate Singleto…...
29.Java 集合线程安全(ArrayList 类线程安全问题处理方案、HashSet 、HashMap 类线程安全问题处理方案)
一、ArrayList 类线程安全问题 1、概述 ArrayList 类存在线程安全问题 2、异常演示 ListNoSafeTest.java,演示 ArrayList 类线程安全问题 package com.my.listsafe;import java.util.ArrayList; import java.util.UUID;public class ListNoSafeTest {public st…...
解锁企业数字化转型新力量:OpenCoze(开源扣子)
在当今数字化浪潮席卷之下,企业对于高效管理和协同运作的需求愈发迫切,而开源技术正逐渐成为众多企业破局的关键利器。今天,想给大家介绍一款极具潜力的开源项目 ——OpenCoze,中文名称 “开源扣子”。 一、OpenCoze 是什么&…...
Docker 使用Dockerfile创建镜像
创建并且生成镜像 在当前目录下创建一个名为Dockerfile文件 vi Dockerfile填入下面配置 # 使用 CentOS 作为基础镜像 FROM centos:7# 设置工作目录 WORKDIR /app# 复制项目文件到容器中 COPY bin/ /app/bin/ COPY config/ /app/config/ COPY lib/ /app/lib/ COPY plugin/ /a…...
linux网络 | https前置知识 | 数据加密与解密、数据摘要
前言:本节内容讲述https的相关内容。 https博主会着重讲解https如何让一个请求和一个响应能够安全的进行交互。 https博主将用两篇文章进行讲解。本篇是两篇中第一篇。会把http的安全问题引出来, 然后说一下https的基本解决方法。 下面废话不多说, 开始我…...
01 Oracle自学环境搭建(Windows系统)
1 Oracle12C安装 1.1 下载 官网地址:https://www.oracle.com/ 进入官网→Resource→Customer Downloads 如果没有登录,会提示登录后后才能下载 选择适合自己的版本(我电脑是Windows系统 64位) 选择需要的安装包进行下载 双击下载…...
负载均衡原理及算法
什么是负载均衡? 负载均衡 指的是将用户请求分摊到不同的服务器上处理,以提高系统整体的并发处理能力以及可靠性。负载均衡服务可以有由专门的软件或者硬件来完成,一般情况下,硬件的性能更好,软件的价格更便宜&#x…...
STM32第5章、IWDG
一、简介 IWDG:全称是Independent watchdog,即独立看门狗。本质上是一个能产生系统复位信号的计数器。 特性: 是一个递减计数器。 时钟信号由独立的RC振荡器提供,可在待机和停止模式下运行。 看门狗被激活后,当递减计…...
[python3]Uvicorn库
Uvicorn 是一个用于运行 ASGI(Asynchronous Server Gateway Interface)应用程序的轻量级服务器。ASGI 是 Python Web 应用程序接口的一种扩展,它不仅支持传统的同步 Web 请求处理,还支持异步请求处理、WebSockets 以及 HTTP/2。 h…...
openEuler 22.04使用yum源最快速度部署k8s 1.20集群
本文目的 openEuler的官方源里有kubernetes 1.20,使用yum源安装是最快部署一个k8s集群的办法 硬件环境 主机名系统架构ipmasteropenEuler release 22.03 (LTS-SP2)arm192.168.3.11edgeopenEuler release 22.03 (LTS-SP2)arm192.168.3.12deviceopenEuler release 22.…...
【深度学习】数据预处理
为了能用深度学习来解决现实世界的问题,我们经常从预处理原始数据开始, 而不是从那些准备好的张量格式数据开始。 在Python中常用的数据分析工具中,我们通常使用pandas软件包。 像庞大的Python生态系统中的许多其他扩展包一样,pan…...
Oracle:ORA-00904: “10“: 标识符无效报错详解
1.报错Oracle语句如下 SELECT YK_CKGY.ID,YK_CKGY.DJH,YK_CKGY.BLRQ,YK_CKGY.ZBRQ,YK_CKGY.SHRQ,YK_CKGY.YT,YK_CKGY.ZDR,YK_CKGY.SHR,YK_CKGY.BZ,YK_CKGY.JZRQ,YK_CKGY.ZT,YK_CKGY.CKLX,(case YK_CKGY.CKLXwhen 09 then药房调借when 02 then科室退药when 03 then损耗出库when…...
CentOS 7 下 Nginx 的详细安装与配置
1、安装方式 1.1、通过编译方式安装 下载Nginx1.16.1的安装包 https://nginx.org/download/nginx-1.16.1.tar.gz 下载后上传至/home目录下。 1.2、通过yum方式安装 这种方式安装更简单。 2、通过编译源码包安装Nginx 2.1、安装必要依赖 sudo yum -y install gcc gcc-c sudo…...
Vue.js:现代前端开发的灵活框架
大家好!我是 [数擎 AI],一位热爱探索新技术的前端开发者,在这里分享前端和 Web3D、AI 技术的干货与实战经验。如果你对技术有热情,欢迎关注我的文章,我们一起成长、进步! 开发领域:前端开发 | A…...
VideoPlayer插件的功能和用法
文章目录 1. 概念介绍2. 使用方法2.1 实现步骤2.2 具体细节3. 示例代码4. 内容总结我们在上一章回中介绍了"如何获取文件类型"相关的内容,本章回中将介绍如何播放视频.闲话休提,让我们一起Talk Flutter吧。 1. 概念介绍 播放视频是我们常用的功能,不过Flutter官方…...
GPT-SoVITS学习01
1.什么是TTS TTS(Text-To-Speech)这是一种文字转语音的语音合成。类似的还有SVC(歌声转换)、SVS(歌声合成)等。 2.配置要求 GPT-SoVITS对电脑配置有较高的要求。 训练:对于Windows电脑&#…...
C语言程序环境和预处理详解
本章重点: 程序的翻译环境 程序的执行环境 详解:C语言程序的编译链接 预定义符号介绍 预处理指令 #define 宏和函数的对比 预处理操作符#和##的介绍 命令定义 预处理指令 #include 预处理指令 #undef 条件编译 程序的翻译环境和执行环…...
DBeaver执行本地的sql语句文件避免直接在客户端运行卡顿
直接在客户端运行 SQL 语句和通过加载本地文件执行 SQL 语句可能会出现不同的性能表现,原因可能包括以下几点: 客户端资源使用: 当你在客户端界面直接输入和执行 SQL 语句时,客户端可能会消耗资源来维护用户界面、语法高亮、自动完…...
【Linux】5.Linux常见指令以及权限理解(3)
文章目录 3. Linux指令如何把自己的公网IP配置到XShell里面日志3.9 时间相关的指令3.10 Cal指令3.11 find指令:(灰常重要)3.12 grep指令3.13 zip/unzip指令:3.14 tar指令(重要):打包/解包&#…...
QT鼠标、键盘事件
一、鼠标 鼠标点击 mousePressEvent 鼠标释放 mouseReleaseEvent 鼠标移动 mouseMoveEvent 鼠标双击 mouseDoubleClickEvent 鼠标滚轮 QWheelEvent 二、键盘 键盘按下 keyPressEvent 键盘松开keyReleaseEvent 一、鼠标 #include <QMouseEvent> 鼠标点击 mouse…...
LabVIEW启动时Access Violation 0xC0000005错误
问题描述 在启动LabVIEW时,可能出现程序崩溃并提示以下错误:Error 0xC0000005 (Access Violation) Access Violation错误通常是由于权限不足、文件冲突或驱动问题引起的。以下是解决此问题的全面优化方案: 解决步骤 1. 以管理员身份运行…...
WPF中组件之间传递参数的方法研究
在 WPF (Windows Presentation Foundation) 中,组件(或称为控件)之间传递参数的方法有很多种。不同的传递方式适用于不同的应用场景,具体选择取决于应用需求、性能、可维护性等因素。以下是几种常见的传递参数的方法,并…...
本地大模型工具哪家强?对比Ollama、LocalLLM、LM Studio
前言 对于AIGC的初学者, 你一定想尝试在本地搭建一个私有的开源大模型,比如常见的chatglm、llama或者qwen。在实践过程你会发现,每个模型单独配置环境,下载模型文件,还要确保它们互不干扰。这不仅耗时耗力,…...
dify 常见问题总结 2025 持续更新
任何 Dify 问题评论区留言。 问题总结 Q:模型在回答时出现异常情况该如何处理? A: 可以通过记录异常情况并分析其原因来进行处理。通常可以调整提示词、重新训练模型或增加异常处理机制来改进模型的表现。 关键词:提示词、模型、…...
贪心算法笔记
贪心算法笔记 大概内容 贪心就是对于一个问题有很多个步骤,我们在每一个步骤中都选取最优的那一个,最后得出答案。就是在一些函数中可行,但是有些比如二次函数,因为它的转折点不一定最优,就是不可行的。那么如何判断贪心呢?有这么几种 看时间复杂度,一般的就是 O ( n…...
切比雪夫插值
切比雪夫插值是一种基于切比雪夫节点的多项式插值方法,其优势是减少插值误差(特别是龙格现象:表现为高维插值时在边缘处插值误差骤增)。本文对其基本操作进行说明。 1. 切比雪夫节点 切比雪夫插值的核心是使用切比雪夫节点作为插值点。切比雪夫节点是切…...
西电-神经网络基础与应用-复习笔记
此为24年秋研究生课程复习笔记 导论 神经网络的研究方法分为 连接主义,生理学派,模拟神经计算。高度的并行、分布性,很强的鲁棒和容错性。便于实现人脑的感知功能(音频图像的识别和处理)。符号主义,心理学派,基于符号…...
【面试题】简单聊一下什么是云原生、什么是k8s、容器,容器与虚机相比优势
云原生(Cloud Native) 定义:云原生是一种构建和运行应用程序的方法,旨在充分利用云计算的优势。它涵盖了一系列技术和理念,包括容器化、微服务架构、自动化部署与管理等。特点:云原生应用程序被设计为可弹性…...
Vue 3 Diff 算法过程及基本实现方式
Vue 3 的 Diff 算法 Vue 3 使用的是一种高效的 DOM Diff 算法,主要用于在虚拟 DOM 树发生变化时,计算最小的操作以更新真实 DOM。相比 Vue 2,Vue 3 的 Diff 算法做了很多优化。 Diff 算法的背景与目的 虚拟 DOM 树的对比:在 Vue…...
EasyCVR视频汇聚平台如何配置webrtc播放地址?
EasyCVR安防监控视频系统采用先进的网络传输技术,支持高清视频的接入和传输,能够满足大规模、高并发的远程监控需求。平台支持多协议接入,能将接入到视频流转码为多格式进行分发,包括RTMP、RTSP、HTTP-FLV、WebSocket-FLV、HLS、W…...
PowerApps助力PowerBI实现数据写回
原文发布日期: 2019-08-01 06:03:50 0000 注:本文旨在介绍Power BI如何利用PowerApps实现用户在前端对数据源进行增删查改,关于此,你也可以在Google上找到更详细但较零散的资料 正文 在SSAS多维数据集中,开发者可以给数据开启&q…...
数据结构:DisjointSet
Disjoint Sets意思是一系列没有重复元素的集合。一种常见的实现叫做,Disjoint-set Forest可以以接近常数的时间复杂度查询元素所属集合,用来确定两个元素是否同属一个集合等,是效率最高的常见数据结构之一。 Wiki链接:https://en…...
React 元素渲染
React 元素渲染 React 是一个用于构建用户界面的 JavaScript 库,它允许开发人员创建大型应用程序,这些应用程序可以随着时间的推移而高效地更新和渲染。React 的核心概念之一是元素渲染,它描述了如何将 JavaScript 对象转换为 DOM࿰…...
【Leetcode 每日一题】3270. 求出数字答案
问题背景 给你三个 正 整数 n u m 1 num_1 num1, n u m 2 num_2 num2 和 n u m 3 num_3 num3。 数字 n u m 1 num_1 num1, n u m 2 num_2 num2 和 n u m 3 num_3 num3 的数字答案 k e y key key 是一个四位数,定义如下&…...
eNSP之家----ACL实验入门实例详解(Access Control List访问控制列表)(重要重要重要的事说三遍)
ACL实验(Access Control List访问控制列表)是一种基于包过滤的访问控制技术,它可以根据设定的条件对接口上的数据包进行过滤,允许其通过或丢弃。访问控制列表被广泛地应用于路由器和三层交换机。 准备工作 在eNSP里面部署设备&a…...
【git】-2 分支管理
目录 一、分支的概念 二、查看、创建、切换分支 1、查看分支-git branch 2、创建分支- git branch 分支名 3、切换分支- git checkout 分支名 三、git指针 -实现分支和版本间的切换 四、普通合并分支 git merge 文件名 五、冲突分支合并 【git】-初始gi…...
硬件设计-齐纳管
目录 摘要 详情 齐纳管的工作电流、 摘要 齐纳管(Zener Diode)是一种特殊的二极管,它能够在特定的反向电压下保持电流稳定。正常情况下,二极管只允许正向电流通过,而阻止反向电流流过。而齐纳管在一定的反向电压下可…...
Github出现复杂问题 无法合并 分支冲突太多 如何复原
目录 问题再现 解决思路 当然我所指的是在 main 分支开一个新的分支 删除本地文件夹 重新克隆 开一个新分支 切换分支 下载远程分支 文件覆盖 合并到主分支 问题再现 太复杂了 无法更改 编译器现状 全部崩溃了 无法更改 即使创建一个新的分支也无济于…...
《分布式光纤传感:架设于桥梁监测领域的 “智慧光网” 》
桥梁作为交通基础设施的重要组成部分,其结构健康状况直接关系到交通运输的安全和畅通。随着桥梁建设规模的不断扩大和服役年限的增长,桥梁结构的安全隐患日益凸显,传统的监测方法已难以满足对桥梁结构健康实时、全面、准确监测的需求。分布式…...
java_抽象类最佳实践-模板设计模式
基本介绍 模板设计模式可解决的问题 最佳实践 Template类 package com.hspedu.abstract_; abstract public class Template { //抽象类-模板设计模式public abstract void job();//抽象方法public void calculateTime() {//实现方法,调用 job 方法//得到开始的时间…...
linux网络 | http结尾、理解长连接短链接与cookie
前言:本节是http章节的最后一部分,主要解释一些小概念。讲解到了HTTP的方法,表单, 重定向等等。 现在废话不多说, 开始我们的学习吧。 ps:本节内容都是概念, 知道就行, 友友们放心观…...
dtdug汇编指令练习
r 通用寄存器 m 代表内存 imm 代表立即数 r8 代表8位通用寄存器 m8 代表8位内存 imm8 代表8位立即数 mov指令练习 MOV 的语法: mov 目标操作数,源操作数 作用:拷贝源操作数到目标操作数 1、源操作数可以是立即数、通用寄存器、段寄存器、或者内存单元. 2、目标操作数…...
Windows自动化Python pyautogui RPA操作
依赖包 import time import pyautogui import pyperclip import os import psutil from pywinauto.application import Application睡眠: pyautogui.sleep(1)鼠标事件: pyautogui.moveTo(100, 100, duration0.25) pyautogui.click(100, 100, duration0.…...
Ollama私有化部署大语言模型LLM
目录 一、Ollama介绍 二、安装Ollama 1、标准安装 2、国内加速 三、升级Ollama版本 四、使用Ollama 1、启动ollama服务 systemctl start ollama.service ollama serve 2、使用ollama命令 ollama run 运行模型 ollama ps 查看正在运行的模型 ollama list 查看(本地)…...
ubuntu/kali安装c-jwt-cracker
1.下载安装包 可以去GitHub下载解压,我这直接在kali克隆下来了。(网络不好可能克隆不下来) git clone https://github.com/brendan-rius/c-jwt-cracker.git 2.如果下载的压缩包就需要进行解压,克隆的直接进入目录就好了。 unzi…...
MySql按年月日自动创建分区存储过程
-- 创建存储过程【通过数据库和表名】建立【partition_number】get分区,分区间隔为【gaps】 -- datasource 数据库名称 -- table_name 数据库表名 -- partition_number 新建分区的数量 -- partition_type 分区类型(0-按天分区,1-按月分区&…...
Spring配置文件中:密码明文改为密文处理方式(通用方法)
目录 一、背景 二、思路 A) 普通方式 B) 适合bootstrap.properties方式 三、示例 A) 普通方式(连接Redis集群) A) 普通方式(连接RocketMQ) B) 适合bootstrap.properties方式 四、总结 一、背景 SpringBoot和Sprin…...
树的模拟实现
一.链式前向星 所谓链式前向星,就是用链表的方式实现树。其中的链表是用数组模拟实现的链表。 首先我们需要创建一个足够大的数组h,作为所有结点的哨兵位。创建两个足够大的数组e和ne,一个作为数据域,一个作为指针域。创建一个变…...
计算机图形学【直线和圆的生成算法】
在计算机图形学中,光栅化是将几何图元转换成一个光栅图像(像素或点)在屏幕上输出的过程。光栅化可实现图形变为二维图像,其目的是将连续的几何图形转换为离散的像素点。光栅化算法的基本原理包括两个主要步骤:首先&…...
OpenAI 故障复盘 - 阿里云容器服务与可观测产品如何保障大规模 K8s 集群稳定性
本文作者: 容器服务团队:刘佳旭、冯诗淳 可观测团队:竺夏栋、麻嘉豪、隋吉智 一、前言 Kubernetes(K8s)架构已经是当今 IT 架构的主流与事实标准(CNCF Survey[1])。随着承接的业务规模越来越大,用户也在使…...