单例模式
- 设计思想:内存中只会创建且仅创建一次对象。
- 使用:所有要调用该对象的地方共享单例对象。
- 目的:防止频繁创建对象使用内存。
分为两类:
- 懒汉式(非线程安全):真正需要使用时才去加载对象。
- 饿汉式:类加载时已经创建好。
实现饿汉需要:
private static Singleton singleton = new Singleton();
实现单例模式(懒汉)需要:
- 限制用户自己创建实例->将构造方法设为private
- 使用时判断是否已经存在这个对象->将
singleton
定义为null
,并在使用时判断,==null
则创建(注意这里会发生线程安全问题)
代码:
public class Singleton{
// 最简单的实现,会有线程安全问题
private Singleton(){};//private限制用户自己创建该类实例
private static Singleton singleton = null;//注意线程安全问题的修改点
//使用时先获取;线程安全问题修改点
public static Singleton getSingleton(){
if(singleton==null){
//如果没有就创建
singleton = new Singleton();
}
return singleton;
}
线程安全问题在于1.指令重排序:
Singleton singleton = new Singleton();
的步骤有3步:
Step1:分配内存空间
Step2:初始化对象
Step3:设置instance指向刚分配给它的内存地址
其中2、3步是可以颠倒的。那么就会发生线程A从1直接执行了3,此时instance已经不是null了,但仍没有被初始化。如果线程B此时选择获取实例,那么就会发现不是null,但是获取的时候就会报错NPE。
解决方法:
禁止指令重排序->volatile
关键字修饰singleton
2.多个线程同时进入
两种可能:要么是后走完的覆盖前一个的实例,要么是同时走完出现两个实例。总之都要出现两个实例。只有一前一后进入的才可以。
解决方法:(最好的解决方法是静态内部类)
首先考虑加锁 Synchronized 粗略的来看加锁的地方在getSingleton()这个方法上。但是为了确保单例空间共享和方法不被重写、重载,我们已经用了static
来修饰方法。如果synchronized
加在静态方法上,会把整个类给锁住。因此synchronized
要放到方法里面来锁。
双重检测机制:(为什么要双重?)
if(instance==null){
//避免全走锁
syschronized(Singleton.class){
if(instance==null){
//确保另一个线程获得锁之后也要判断
instance = new Singleton():
}
}
}
静态内部类:
public class Singleton{
//静态内部类
private static class LazyHolder{
//通过静态内部类来创建
private static final Singleton INSTANCE = new Singleton();
}
//原构造方法
public Singleton(){};
//获取实例方法,static避免重写重载,
public static final Singleton getInstance(){
return LazyHolder.INSTANCE;//通过静态内部类来加载
}
}
如何打破单例模式?——利用“反射”机制
- 获得构造器
Constructor con = Singleton.class.getDeclaredConstructer();
- 设为可访问的构造器
con.setAccessible(true)
- 构造两个不同对象
如何防止利用反射来多次创建?——枚举类
枚举类在类加载的时候初始化,JVM会组织反射获取枚举类的私有构造方法。
public enum SingletonEnum{
INSTANCE;
}
单例模式在Spring中的应用——依赖注入(A类依赖B类,A不再创建B类对象而是写好配置xml文件,控制反转让Spring容器去按配置信息创建管理bean)
Spring依赖注入Bean对象默认为单例。使用的是双重检测加锁的方式。
依赖注入发生在AbstractBeanFactory
的getBean
里,doGetBean()
->getSingleton()
->singletonFactories()
->创建单例实例
工厂模式
简单工厂模式
工厂类专门负责创建对象,所以将XX类的初始化方法迁到工厂类的创建方法(createXX)里。
public class XXFactory{
public XX createXX(){
XX xx = new XX();
//初始化代码
return xx;
}
}
如果XX存在子类,则加判断。
比如,抽象接口XX,实现子类A,实现子类B。以参数type
传入创建方法,让其判断并选择new A还是B。
在客户端的实现:
XXFactory factory = new XXFactory();
XX a = factory.createXX(type);//依靠传参让工厂类判断new哪个子类。
这种依靠传参给工厂类创建方法来创建不同类别对象的是简单工厂模式。
工厂方法模式
去掉对type
的if-else
判断,对每个XX的子类创建对应的工厂子类,这些工厂子类实现抽象工厂接口。
即,实例化不同的工厂子类,调用创建方法,然后得到对应的对象。(子类继承父类重写方法,多态的体现)
//用接口来代替工厂类
public interface XXFactory{
XX createXX();
}
实现接口的子类:
public class AF implements XXFactory{
@Override
public XX createXX(){
XX xx = new AF();//实例化不同的工厂子类
}
}
客户端:
XXFactory af = new AF();
XXFactory bf = new BF();
//利用多态,调用名称相同的创建方法,无需像简单工厂模式一样传参。
XX a = af.createXX();
XX b = bf.createXX();
每个XX子类对应一个实现了XXFactory接口的工厂子类。
抽象工厂模式
如果待创建的子类还是多,可以放弃创建子类,转而寻找子类之间的共性。比如1234四类产品,但是他们本身又都有AB两种分类。那么可以将其分为AB两组,组中的不同产品,由同一工厂类里的不同方法创建:
- 产品->抽象接口
- 分组->实现类
public interface IFactory{
//产品1
XX createXX();
//产品2
YY createiYY();
}
A组的工厂
public class AFactory implements IFactory{
@Override
public XX createXX(){
XX xx = new A();
}
...
//YY ZZ
}
客户端的代码:
IFactory fa = new AFactory();
IFactory fb = new BFactory();
//生产A类XX
XX ax = fa.createXX();//调用A工厂的createXX方法
//生产A类YY
YY ay = fa.createYY();
//生产B类XX
XX bx = fb.createXX();
//生产B类YY
YY by = fb.createYY();
设计思想总结:多个产品按类分组,组内的不同产品对应同一工厂子类的不同方法。
工厂模式总结
- 简单工厂模式:一个工厂类,创建方法根据传入参数判断。
- 工厂方法模式:工厂是抽象接口,多个工厂子类实现接口,并且对应产品调用接口中的创建方法,利用多态——覆写来创建不同的产品对象。
- 抽象工厂模式,产品子类再次分组,而不再创建多个子类。按不同产品中的某些共性分组,对组创建子类实现抽象工厂接口,子类中由不同的create方法创建不同产品,减少子类的数量。
工厂模式应用
在上述工厂模式应用中,客户端必须指定工厂子类。但是在Spring中,可以依靠控制反转让Spring创建和管理各种Bean对象。用户需要做的只是在XML(或注解)中配置好bean的各种属性,如实现类class,这样可以创建对象并创建其所依赖的对象。
通过Spring的依赖注入不需要在业务代码中手动实例化对象。(详见BeanFactory源码及其实现类)
依赖注入:A类依赖B类,A类中不实例化B类的实例,而是通过外部将B类传参(注入A中)进A的构造函数里。A把这种依赖关系配置在外部文件里,Spring可以根据配置信息创建、管理bean对象。
- Spring如何根据xml配置创建、管理bean?——反射,运行时创建对象,使用class元信息(如完整类名)
工厂模式终于结束啦,继续施工。。
策略模式——解决if-else、switch的困境
基本上都是知乎@程序员小灰写的那篇文章里的。。
根据用户选择而使用不同的处理方法。
策略模式的角色分配:
- Strategy:决定实现策略所必需的接口
- ConcreteStrategy:具体的实现Strategy的API
- Context: 使用Strategy,保存ConcreteStrategy的实例,并调用Strategy的API来实现需求
- 定义策略接口:
public interface IStrategy{
void dealMethod(String option);
}
- 具体策略DealX实现策略接口
public class DealX implements IStrategy{
@Override
public void dealMethod(String option){...}
}
- 定义上下文
public static class DealContext{
private String type;//type=X
private Strategy deal;//策略实例
//构造函数
public DealContext(String type, Strategy deal){
this.type = type;
this.deal = deal;
}
//获取Strategy的实例
public Strategy getDeal(){
return deal;
}
//判断是否与type相等
public boolean options(String type){
return this.type.equals(type);
}
}
- 最终功能的实现(new 很多个上下文,在里面传参type和新建对应具体策略实例
public class Share{
private static List<DealContext> algs = new ArrayList<>();
//静态代码块,加载所有策略(algs表里)
static{
algs.add(new DealContext("X", new DealX());//具体策略
//...其他策略
}
public void shareOptions(String type){
Strategy deal = null;
for(DealContext d:algs){
if(d.options(type)){
//找到哪个具体策略对应了此处type
deal = d.getDeal();
//获取具体策略
breal;//跳出循环,不必再找了
}
}
deal.dealMethod(type);//调用具体策略里的方法
}
}
如果要添加新的策略:
6. public class DealNew implements Strategy
继续继承Strategy
7. Share中的静态代码块也加入`(new DealContext(“new”,new DealNew());
Java中策略模式的应用
Comparator
如果某类没有实现Comparable接口,则建立一个实现Comparator接口的比较器。
按照上述4步进行
- 策略接口:
public interface Comparator<T>{
int Compare(T o1, T o2);
}
- 定义具体策略:
public class Sorter implements Comparator{
//具体策略排序,实现Comparator接口
@Override
public int compare(String s1,String s2){
return Integer.compare(s1.length,s2.length);//此处设计为比较字符串长度
}
}
- 策略上下文
- 实现
可以通过配置文件或者注解来标注定义的策略类。读取配置文件/搜索被标注的策略类,通过反射来加载策略类、创建策略对象。
与状态模式的区分:待施工。。。还没看
装饰者模式
套娃。。详见java io
new BufferedReader(new InputStreamReader(System.in));
装饰者模式由三部分组成(跟策略模式很像,都是抽象->具体,不同的是策略有个上下文,装饰器分为两组,组件和装饰器, 装饰器是用来扩展的):
- 抽象组件类
abstract class Component{
public abstract void operation();
}
- 具体组件类
class ConcreteComponent extends Component{
public void operation(){...}//提供具体实现
}
- 抽象装饰器类继承抽象组件类
abstract class Decorator extends Component{
protected Component component;
public Decorator (Component component){
//依赖注入?
this.component = component;
}
public void operation(){
component.operation();
}
}
- 具体装饰器类(有多个)
class ConcreteDecoratorA extends Decorator{
public ConcreteDecoratorA(Component component){
//调用父类构造方法
super(component);
}
//具体操作
private void operation1(){...}
private void operation2(){...}
...
public void operation(){
operation1();
super.operation();
operation2();
}
//新功能
public void anotherOperation(){
...
}
}
- 客户端实现
public class Client{
public static void main(String[] args){
Component c1 = new ConcreteComponent();
//套娃开始⬇️
Decorator decoratorA = new ConcreteDecoratorA(c1);
//实例化具体装饰器并将具体组件实例作为参数传进去
decoratorA.operation();//上边定义的三个操作
//新功能
Decorator decoratorBandA = new ConcreteDecoratorBandA(decoratorA);
//⬆️扩展装饰器
decoratorBandA.operation();
}
}
新功能:新建类继承抽象装饰器类。不修改原有的代码,符合开闭原则。
观察者模式
游戏开发和窗体应用程序中多见,Spring的ApplicationListener
和ApplicationContext
也有运用。非常简单,待施工。。。