今天说说几种常见的设计模式,写个博客,记录一下。

首先,啥叫设计模式呢? 设计模式是一套被反复使用、多数人知晓的、经过分类编码、代码设计经验的总结。举个例子,把编码比喻成打仗,那么设计模式就是孙子兵法三十六计。设计模式的目的是为了可重用代码、让代码更容易被他人理解、保证代码的可靠性。

单例模式
作用是保证整个应用程序代码中某个实例有且仅有一个。
分为饿汉模式和懒汉模式
区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全。
懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全。
创建单例模式的步骤:
1,将构造方法私有化,不允许外边直接创建对象
2,声明类的唯一实例,用private static修饰
3,提供一个用于获取实例的方法,使用public static修饰

常用的单例模式写法:
饿汉式

public class Singleton {
    /**
     * 饿汉式单例
     */
    private final static Singleton instance = new Singleton();
    private Singleton(){

    }
    public static Singleton getInstance(){
        return instance;
    }
}

这是一种饿汉式最简单的写法,在装载类的时候就完成了实例化,可以保证线程的安全,但是如果不使用,就会造成内存浪费。

懒汉式

public class Singleton {
    /**
     * 懒汉单线程单单例
     */
    private static Singleton instance;
    private Singleton(){

    }
    public static Singleton getInstance(){
        if(instance ==null){
            instance = new Singleton();
        }
        return instance;
    }
}

这是懒汉式的一种简单写法,但是这种写法线程不安全,如果有多个线程同时调用,可能会出现多个实例,所以这种方法只适合单线程的单例。

鉴于以上线程不安全的问题,可以使用synchronized关键字在方法上进行同步,但是每个线程想获取到类的实例都需要执行getInstance方法进行同步,但是这个方法的目的是只实例化一次,后面如果有线程想要获得实例,直接return就可以了,所以这种同步的方法不好,不推荐使用,具体改进可以使用双重检查的单例模式,如下:

public class Singleton {
    /**
     * 懒汉双重检查模式 适合多线程 线程安全
     */
    private static Singleton instance;
    private Singleton(){

    }
    public static Singleton getInstance(){
        if(instance ==null){
            synchronized(Singleton.class){
                if (instance ==null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查模式保证了线程安全,也保证了只实例化一次。

适配器模式
适配器模式将一个类的接口,转换成客户期望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作。
组合的方式,采用组合方式的适配器称为对象适配器,特点是把被适配者作为一个对象组合到适配器类中,以修改目标接口包装被适配者。
继承的方式,采用继承方式的称为类适配器,特点是通过多重继承不兼容接口,实现对目标接口的匹配,单一的为某个类而实现适配。

适配器模式的作用:
1,透明。通过适配器,客户端可以调用同一接口,因而对客户端来说是透明的。这样做更简单、更直接、更紧凑。
2,重用。复用了现存的类,解决了现存类和复用环境要求不一致的问题。
3,低耦合。将目标类和适配者类解耦,通过引入一个适配器类重用现有的适配者类,而无需修改原有代码(遵循开闭原则)

对象适配器demo,手机充电需要将220V电流转化为5V:
被包装类,即source类:

public class AC220 {
    public int output220V(){
        int output = 220;
        return output;
    }
}

包装类(目标类):

/**
 *	此是一个接口,定义好抽象输出方法
 */
public interface DC5 {
    int output5V();
}

适配器类:

/**
 * 适配器类需要继承输出接口,传入源类对象,重写输出方法
 */
public class PowerAdapter implements DC5{
		private AC220 mAC220;
		public PowerAdapter(AC220 ac220){
        	this.mAC220 = ac220;
    }
    	@Override
    	public int output5V() {
        	int output = 0;
        	if (mAC220 != null) {
        		output = mAC220.output220V() / 44;
        	}
        	return output;
    }
}

主函数:

public static void main(String[] args) {
		PowerAdapter adapter = new PowerAdapter(new AC220());
	}

策略模式

策略模式将可变的部门从程序中抽象分离出算法接口(通用部分),在该接口下分别封装一系列算法实现并使他们可以互相替换,从而导致客户端程序独立于算法的改变。

策略模式的实现:
1,通过分离变化,对策略对象定义一个公共接口。
2,编写策略类,该类实现了上面的公共接口。(多个功能具体实现)
3,在使用策略对象的类中保存一个对策略对象的引用。
4,在使用策略对象的类中,实现对策略对象的setter和getter方法或者使用构造方法完成赋值。

demo:
定义一个接口(就是抽象策略,比如网银支付,不用管用什么银行支付,只是知道支付这一层就可以),定义一个方法对两个整数进行运算

public interface Strategy {
 	public abstract int calculate(int a,int b);
}

定义具体的算法类,实现两个整数的加减乘除运算:

public class AddStrategy implements Strategy{
    @Override
    public int calculate(int a, int b) {
        return a+b;
    }
}

public class SubstractStrategy implements Strategy{
    @Override
    public int calculate(int a, int b) {
        return a-b;
    }
}

public class MultiplyStrategy implements Strategy {
    @Override
    public int calculate(int a, int b) {
        return a*b;
    }
}

public class DivisionStrategy implements Strategy{
    @Override
    public int calculate(int a, int b) {
        if(b!=0){
         	return a/b;
        }
        else {
            throw new RuntimeException("除数不能为零");
        }
    }
}

定义具体的环境角色,持有Strategy接口的引用,并且有getter和setter方法可以完成策略更换,在环境角色中调用接口的方法完成动作。

public class Context {
    
    private Strategy strategy;
    
    public Context(Strategy strategy) {
        super();
        this.strategy = strategy;
    }

    public Strategy getStrategy() {
        return strategy;
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
    
    public int calculate(int a,int b){
        return strategy.calculate(a, b);
    }
}

客户端在调用时,只需向环境角色设置相应的算法类,就可以得到相应的结果。

public class StrategyTest {
    public static void main(String[] args) {
        //加法
        Context add=new Context(new AddStrategy());
        System.out.println(add.calculate(10, 5));
        //减法
        Context sub=new Context(new SubstractStrategy());
        System.out.println(sub.calculate(3, 2));
        //乘法
        Context mul=new Context(new MultiplyStrategy());
        System.out.println(mul.calculate(6, 8));
        //除法
        Context div=new Context(new DivisionStrategy());
        System.out.println(div.calculate(90, 9));    
    }
}

策略模式的优缺点:
优点:
1,使用了组合,使架构更加灵活。
2,富有弹性,可以较好的应对变化(开闭原则)。
3,更好的代码复用性(相对于继承)。
4,消除了大量的条件语句。(在多种情况下,通常switch case、if else,但是策略模式优化了这一点)。
缺点:
1,客户代码需要了解每个策略实现的细节,需要知道每一个实现类的具体实现。
2,增加了很多对象的数目。

策略模式的适用场景:
1,许多相关的类仅仅是行为的差异。
2,运行时选取不同的算法变体。
3,通过条件语句在多个分支中选取一种。

代理模式
定于是为其他对象提供一种代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。在不修改源代码的基础上,增加删除功能。

静态代理:代理和被代理对象在代理之前是确定的,它们都实现相同的接口或者继承相同的抽象类、父类。并且可以连续代理多个。

demo:
接口Moveable

public interface Moveable {
    void run();
}

目标对象run:

public class Run implements Moveable{
    @Override
    public void run() {
        System.out.println("run run run");
    }
}

代理对象car:

public class Car implements Moveable{

    private Moveable moveable;

    public Car(Moveable moveable){
        this.moveable=moveable;
    }

    @Override
    public void run() {
        System.out.println("car run");
    }
}

测试类

public class Test {
    public static void main(String[] args) {
        Run run = new Run();
        Car car = new Car(run);
        car.run();
    }
}

静态代理总结:
优点:可以做到在不修改目标对象功能的前提下,对目标功能扩展。
缺点:代理对象需要与目标对象实现一样的接口,所以会出现很多代理类,一旦原始接口增加方法,目标对象和代理对象都要维护。

动态代理

动态代理实现步骤:
1,创建一个实现接口InvocationHandler的类,它必须实现invoke方法。
2,创建被代理的类以及接口。
3,调用Proxy的静态方法,创建一个代理类newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
4,通过代理调用方法

demo:
原始接口:

public interface Moveable {
    void run();
}

被代理对象:

public class Run implements Moveable{
    @Override
    public void run() {
        System.out.println("run run run");
    }
}

动态代理类:

public class TimeHandler implements InvocationHandler {

    private Object object;

    public TimeHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("run starting");
        method.invoke(object);
        System.out.println("run ending");
        return null;
    }
}

main方法测试:

public class Test {
    public static void main(String[] args) {
        /**
         * 动态代理模式
         */
        //被代理类
        Run run = new Run();
        InvocationHandler h = new TimeHandler(run);
        Class<?> cls = run.getClass();

        /**
         * loader 类加载器
         * interfaces 实现接口
         * h invocationHandler
         */
        Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(),
                cls.getInterfaces(),h);
        m.run();
    }
}

cglib代理模式

动态代理与cglib代理的区别:
1,jdk动态代理,只能代理实现了接口的类,没有实现接口的类不能实现jdk动态代理。
2,cglib动态代理针对类来实现代理的,对指定目标类产生一个子类,通过方法拦截技术拦截所有父类方法的调用。
3,cglib动态代理的类不能是static和final,因为那样不能继承。

demo:

首先导入cglib的jar包

目标对象:

public class UserDao {

    public void save() {
        System.out.println("----已经保存数据!----");
    }
}

Cglib代理类:

/**
 * Cglib子类代理工厂
 * 对UserDao在内存中动态构建一个子类对象
 */
public class ProxyFactory implements MethodInterceptor{
    //维护目标对象
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    //给目标对象创建一个代理对象
    public Object getProxyInstance(){
        //1.工具类
        Enhancer en = new Enhancer();
        //2.设置父类
        en.setSuperclass(target.getClass());
        //3.设置回调函数
        en.setCallback(this);
        //4.创建子类(代理对象)
        return en.create();
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("开始");
        //执行目标对象的方法
        Object returnValue = method.invoke(target, args);
        System.out.println("结束");
        return returnValue;
    }
}

主函数测试:

/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        //目标对象
        UserDao target = new UserDao();

        //代理对象
        UserDao proxy = (UserDao)new ProxyFactory(target).getProxyInstance();

        //执行代理对象的方法
        proxy.save();
    }
}

工厂模式

工厂模式概念:实例化对象,用工厂方法代替new操作。工厂模式包括工厂方法模式和抽象工厂模式,抽象工厂模式是工厂方法模式的拓展。

工厂模式的意图:定义一个接口来创建对象,但是让子类来决定哪些类需要被实例化,工厂方法把实例化的工作推迟到子类中实现。

工厂模式适用场景:
1,有一组类似的对象需要创建。
2,在编码时不能预见需要创建哪种类的实例。
3,系统需要考虑拓展性,不应依赖于产品类实例如何被创建、组合和表达的细节。

工厂模式与java spring框架依赖注入结合的较多,具体表现为controller层、dao层、service层以及具体实现impl,层层调用,但是互不干扰,如需添加新的功能,只需要增加新的方法即可,这里就不举例了。

以上写了几种常见的设计模式,如有问题,请大家留言纠正。