策略模式、适配器模式、责任链模式

340 阅读7分钟

一、策略模式

1.1、使用场景

  • 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
  • 需要安全的封装多种同一类型的操作时。
  • 出现同一抽象多个子类,而又需要使用if-else 或者 switch-case来选择时。

1.2、UML图

url

1.3、未使用策略模式

  public static double calc(String op, double paramA, double paramB) {
        if ("+".equals(op)) {
            System.out.println("执行加法...");
            return paramA + paramB;
        } else if ("-".equals(op)) {
            System.out.println("执行减法...");
            return paramA - paramB;
        } else if ("*".equals(op)) {
            System.out.println("执行乘法...");
            return paramA * paramB;
        } else if ("/".equals(op)) {
            System.out.println("执行除法...");
            if (paramB == 0) {
                throw new IllegalArgumentException("除数不能为0!");
            }
            return paramA / paramB;
        } else {
            throw new IllegalArgumentException("未找到计算方法!");
        }
    }

1.4、使用策略模式

url

具体实现代码如下:

  //针对操作进行抽象
    public interface Strategy {
        public double calc(double paramA, double paramB);
    }
    
    //加法的具体实现策略
    public class AddStrategy implements Strategy {
        @Override
        public double calc(double paramA, double paramB) {
            // TODO Auto-generated method stub
            System.out.println("执行加法策略...");
            return paramA + paramB;
        }
    }

    //减法的具体实现策略
    public class SubStrategy implements Strategy {
        @Override
        public double calc(double paramA, double paramB) {
            // TODO Auto-generated method stub
            System.out.println("执行减法策略...");
            return paramA - paramB;
        }
    }

    //乘法的具体实现策略
    public class MultiStrategy implements Strategy {
        @Override
        public double calc(double paramA, double paramB) {
            // TODO Auto-generated method stub
            System.out.println("执行乘法策略...");
            return paramA * paramB;
        }
    }

    //除法的具体实现策略
    public class DivStrategy implements Strategy {
        @Override
        public double calc(double paramA, double paramB) {
            // TODO Auto-generated method stub
            System.out.println("执行除法策略...");
            if (paramB == 0) {
                throw new IllegalArgumentException("除数不能为0!");
            }
            return paramA / paramB;
        }
    }

    //上下文环境的实现
    public class Calc {
        private Strategy strategy;
        public void setStrategy(Strategy strategy) {
            this.strategy = strategy;
        }
        
        public double calc(double paramA, double paramB) {
            // TODO Auto-generated method stub
            // doing something
            if (this.strategy == null) {
                throw new IllegalStateException("你还没有设置计算的策略");
            }
            return this.strategy.calc(paramA, paramB);
        }
    }


    //执行方法
    public static double calc(Strategy strategy, double paramA, double paramB) {
        Calc calc = new Calc();
        calc.setStrategy(strategy);
        return calc.calc(paramA, paramB);
    }复制代码

二者运行:

  public static void main(String[] args) {
        double paramA = 5;
        double paramB = 21;
        
        System.out.println("------------- 普通形式 ----------------");
        System.out.println("加法结果是:" + calc("+", paramA, paramB));
        System.out.println("减法结果是:" + calc("-", paramA, paramB));
        System.out.println("乘法结果是:" + calc("*", paramA, paramB));
        System.out.println("除法结果是:" + calc("/", paramA, paramB));
        
        System.out.println("------------ 策略模式  ----------------");
        System.out.println("加法结果是:" + calc(new AddStrategy(), paramA, paramB));
        System.out.println("减法结果是:" + calc(new SubStrategy(), paramA, paramB));
        System.out.println("乘法结果是:" + calc(new MultiStrategy(), paramA, paramB));
        System.out.println("除法结果是:" + calc(new DivStrategy(), paramA, paramB));
    }

1.5、Android源码中策略模式的实现

属性动画插值器。

1.6、优缺点

优点:

  • 结构清晰明了、使用简单直观。
  • 耦合度相对而言较低,扩展方便。
  • 操作封装也更为彻底,数据更为安全。

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 随着策略的增加,子类也会变得繁多。

二、适配器模式

它的作用主要是让两个不兼容的接口可以一起工作。连接这些不相关接口的对象称为Adapter。

2.1、使用场景

最经典的一个适配器模式的例子,就是我们手机的充电器,我们手机充电一般都是使用3V,5V的电压进行充电,但是我们的普通插座的电压都是220V或者250V,而手机充电器就是作为普通插座和手机之间的一个适配器,适配器的作用就是将插座上的电压转换为手机可以充电的5V电压。

2.2、代码实现

首先,创建两个类分别代表电压Volt和插座Scoket

public class Volt {

    private int volts;

    public Volt(int v){
        this.volts=v;
    }

    public int getVolts() {
        return volts;
    }

    public void setVolts(int volts) {
        this.volts = volts;
    }

}

表示插座的类Scoket,我们这个插座默认它是220V的:

public class Socket {
    public Volt getVolt(){
        return new Volt(220);
    }
}

现在我想构建一个将220V电压适配为5V电压的充电器,首先,我们将用这些方法创建一个适配器接口。

public interface SocketAdapter {
	public Volt get5Volt();
}

在实现适配器模式时,有类适配器和对象适配器两种方法,这两种方法产生的结果是相同的。

  • 类适配器:使用java继承并扩展了源接口,在我们的例子中是Socket类。

  • 对象适配器:使用Java组合方式,适配器包含源对象。

首先我们用类适配器的方式实现:

// 适配器继承了Socket
public class SocketClassAdapterImpl extends Socket implements SocketAdapter{

	@Override
	public Volt get5Volt() {
		Volt v= getVolt();
		return convertVolt(v,5);
	}
	
	private Volt convertVolt(Volt v, int i) {
        if(v.getVolts()==i){
            return v;
        }
		return new Volt(i);
	}
}

这种实现方式把适配器比喻为一个转换插座可能更容易理解。

对象适配器实现方式:

public class SocketObjectAdapterImpl implements SocketAdapter{
	// 将插座作为属性组合
	private Socket sock = new Socket();
	
	@Override
	public Volt get5Volt() {
		Volt v= sock.getVolt();
		return convertVolt(v,5);
	}
	
	private Volt convertVolt(Volt v, int i) {
        if(v.getVolts()==i){
            return v;
        }
		return new Volt(i);
	}
}

这两种适配器实现方式几乎是相同的,它们都实现了是SocketAdapter接口。

下面是使用适配器设计模式实现的测试程序。

public class AdapterPatternTest {

	public static void main(String[] args) {
		testClassAdapter();
		testObjectAdapter();
	}

	private static void testObjectAdapter() {
		SocketAdapter sockAdapter = new SocketObjectAdapterImpl();
		Volt v5 = sockAdapter.get5Volt();
		System.out.println("v5 volts using Object Adapter="+v3.getVolts());
	}

	private static void testClassAdapter() {
		SocketAdapter sockAdapter = new SocketClassAdapterImpl();
		Volt v5 = sockAdapter.get5Volt();
		System.out.println("v5 volts using Object Adapter="+v3.getVolts());
	}
}

输出结果:

v5 volts using Class Adapter=5
v5 volts using Object Adapter=5

2.3适配器模式类图

img

2.4、JDK中的适配器模式的实现

ListView、RecyclerView。

2.5、优缺点

优点:

  • 可以让任何两个没有关联的类一起运行,灵活性好。

缺点:

  • 过多的使用会让代码非常凌乱,比如命名调用的是A接口,但是内部调用了B接口,这种情况太多不易于管理。

适配器的使用一般是在一个已有的正常运行的功能上进行扩展时使用,而不是在一开始设计时就考虑。

三、策略模式和适配器模式的区别

  • 什么时候用策略模式?我认为是情况比较多的时候。比如:情况1要怎么怎么样,情况2则要怎么怎么样。简单来说就是:不同的情况对应不同的方案。

  • 什么时候用适配器模式?我认为非常适合那种功能相同,字段不同的情况。就比如生活中,充电线的功能都是给手机充电🔋,但是iPhone的是lighting接口,一些安卓的是typeC接口,一些老式安卓是microUSB接口。大家的功能都是一样的,只是一些小细节有所不同,这时候就可以通过一个适配器来兼容它们。简单来说就是:消除细节差异

四、责任链模式

4.1、使用场景

为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

4.2、UML图

img

4.3、代码实现

我们来模拟一个公司的请假流程,

假条就是请求:

data class Request(
        val leaveDay: Float, //请假时长
        val msg: String //请假原因
)

各领导就是处理者:

open class Handler(
        val name: String, //处理者名字
        val leaveLimit: Float //能审批的最长时间
){
    var next: Handler? = null

    open fun handRequest(request: Request) {
        if (request.leaveDay <= leaveLimit) {
            println("${request.msg} -> 请假,$name 准了")
        } else {
            println("$name:超过${leaveLimit}天,让领导处理")
            next?.handRequest(request)
        }
    }
}

//可以根据需求扩展 Handler
class FinalHandler(name: String) : Handler(name, Float.MAX_VALUE) {
    override fun handRequest(request: Request) {
        println("$name${request.msg} -> 找财务领下工资,不用来了!")
    }
}

然后处理者组成责任链,向第一个节点开始发送请求

fun main() {
    val leader = Handler("直接上级", 1f)
    val manager = Handler("经理", 30f)
    val generalManager = Handler("总经理", 365f)
    val boss = FinalHandler("老板")

    //组链
    leader.next = manager
    manager.next = generalManager
    generalManager.next = boss

    leader.handRequest(Request(1f, "头疼脑热")) //脑热得隔离观察哈
    println("-------------")
    leader.handRequest(Request(14f, "老婆生娃"))
    println("-------------")
    leader.handRequest(Request(365f, "世界辣么大,我想去看看"))
    println("-------------")
    leader.handRequest(Request(3650f, "不想上班"))
}

大功告成,运行看下结果:

头疼脑热 -> 请假,直接上级 准了
-------------
直接上级:超过1.0天,让领导处理
老婆生娃 -> 请假,经理 准了
-------------
直接上级:超过1.0天,让领导处理
经理:超过30.0天,让领导处理
世界辣么大,我想去看看 -> 请假,总经理 准了
-------------
直接上级:超过1.0天,让领导处理
经理:超过30.0天,让领导处理
总经理:超过365.0天,让领导处理
老板:不想上班 -> 找财务领下工资,不用来了!
复制代码

通过责任链,每一级别都能发挥自己的责任,对请求进行处理,处理不了的就交给下一节点处理。

4.4、Android源码中责任链模式的实现

View事件的分发处理。

4.5、优缺点

优点:

  • 可以对请求和处理者进行解耦,提高代码灵活性。

缺点:

  • 遍历的处理太多,可能影响性能;调试麻烦需要每一级都跟进去看。