java编程思想第四版第九章总结
1. 策略设计模式
- 参考这篇文章:http://blog.csdn.net/chenjie19891104/article/details/6396458 讲的很清楚,策略设计模式。并且举了一个例子,很具有代表性。
- 先简单了解一下:
和模板方法模式的区别:
文章里还有一个例子:
备注:我来分解,解释一下这个例子。
将共同的方法定义成了一个接口,在这个接口中并没有这个共同方法的实现。
在Strategy类中,定义了一个方法execute,它的参数是拥有共同方法的接口类。
用户Context在调用Strategy的execute方法时,在定义这个共同的方法。 这样就大大提高了灵活性。因为公共方法也是可以后定义的。而不是在创建类的时候就定义好了。
下面把这个例子敲成代码
package net.mindview.interfaces; //公共方法回调类 interface SameCallback{ void doTheSame(); } //定义了一个策略类 interface Strategy { void execute(SameCallback sc); } //定义策略实现类 class concreteStrategy1 implements Strategy { @Override public void execute(SameCallback sc) { sc.doTheSame(); } } class concreteStrategy2 implements Strategy { @Override public void execute(SameCallback sc) { sc.doTheSame(); } } class concreteStrategy3 implements Strategy { @Override public void execute(SameCallback sc) { sc.doTheSame(); } } public class Context { public static void main(String[] args) { Strategy strategy = new concreteStrategy1(); //这个公共方法类 SameCallback sc = new SameCallback() { @Override public void doTheSame() { System.out.println("do same things"); } }; //那个策略需要用到公共方法类,调用即可,如果不用, 那就不在方法中小勇 strategy.execute(sc); strategy = new concreteStrategy2(); strategy.execute(sc); } }
思考: 如果一段代码中, 总会出现很多的if…else或者case,就要考虑使用策略设计模式了
2. 解耦
- 看下面这两个例子:
案例一
package net.mindview.interfaces.classprocessor; import java.math.BigInteger; import java.util.Arrays; /** * 处理器 */ class Processor { public String name(){ return getClass().getName(); }; //处理 Object process(Object input) { return input; } } /** * 大写处理器 */ class UpCase extends Processor { @Override public Object process(Object input) { return ((String)input).toUpperCase(); } } /** * 小写处理器 */ class DownCase extends Processor { @Override public Object process(Object input) { return ((String)input).toLowerCase(); } } /** * 分割数组处理器 */ class Splitter extends Processor { @Override public Object process(Object input) { return Arrays.toString(((String)input).toString().split(" ")); } } public class Apply { public static void process(Processor p, Object s){ System.out.println("Using Processor "+ p.name()); System.out.println(p.process(s)); } public static String s = "Disagreement with beliefs is by definition incorrect"; /** * 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法, * 被称为策略设计模式 * @param args */ public static void main(String[] args) { BigInteger b = new BigInteger("111111111111111"); process(new UpCase(), s); process(new DownCase(), s); process(new Splitter(), s); } /** * 这个类使用到了一个设计模式: 叫做策略设计模式 * 所谓 的策略设计模式指的是: 能够根据传递参数的不同而具有不同的行为的方法, 被称为策略设计模式 * 这类方法包含了所有执行的算法中固定不变的部分(这里是基类Processor,Object),而策略就是实际调用该方法时传递的实际参数, * 这部分参数是变化的,因此被称之为是"策略"部分. * 在main中看到了三种不同类型的策略应用到了String类型s对象上。 */ }在这段代码中,定义了一个处理器,这个处理器有两个方法name()和process(). 然后定义了三个类, UpCase, DownCase,Spltter继承自这个类. 当我们在Apply类中定义process(Process process, Object o)方法, 方法传递的参数是父类Process. 这样不仅可以处理父类,还可以处理子类. 在main方法中就有体现. 这就是策略设计模式的思想。但是,他还有局限性。如果在main中想调用Apply.process()方法, 则必须传递Process类或者子类,不能传递其他类。 下面这个案例滤波器就说明了这一点:
案例二
package net.mindview.interfaces.filters; //波形 public class Waveform { //计数器,第几波 private static long counter; private final long id = counter ++; //当前第几波 @Override public String toString() { return "Waveform " + id; } } package net.mindview.interfaces.filters; //滤波器 public class Filter { public String name(){ return getClass().getSimpleName(); } //加工 public Waveform process(Waveform input){ return input; } } package net.mindview.interfaces.filters; /** * 低频电磁波 */ public class LowPass extends Filter{ //切波的波长 double cutoff; public LowPass(double cutoff){ this.cutoff = cutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } } package net.mindview.interfaces.filters; /** * 高频电磁波 */ public class HighPass extends Filter { //切波 double cutoff; public HighPass(double cutoff){ this.cutoff = cutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } } package net.mindview.interfaces.filters; /** * 段波电磁波 */ class BandPass extends Filter { //其实切波点, 最高且波点 double cutoff ,highCutoff; public BandPass(double cutoff, double highCutoff){ this.cutoff = cutoff; this.highCutoff = highCutoff; } //加工 @Override public Waveform process(Waveform input) { return input; } }在上面这个类中,我们定义了一个Filter滤波器。这里面有何Process中同名的两个方法。这个时候,我想复用案例一的Apply类中的process()方法,可不可以呢?不可以。因为Filter不是Process的子类。虽然, process中的方法体调用完全适用于继承Filter类的子类。为什么不可以呢?Apply就是一个应用类。应用类要处理一些列的类。他可以堆字符串进行处理, 应该也可以对波进行处理呀。现在不可以,因为Process类是一个实体类,他和Apply类中的process方法紧密耦合。如果能够实现解耦,那么,就可以将Apply类更加泛化。这就是将Process作为接口来处理
案例三
package net.mindview.interfaces.classprocessor2; import java.math.BigInteger; import java.util.Arrays; /** * 处理器 */ interface Processor { public String name(); //处理 Object process(Object input); } /** * 处理器有一部分是所有处理器都是一样的.有一部分是不同的. * 所以定义一个抽象类, 将共有方法定义为实体方法, 将各自实现的部分定义为抽象的 */ abstract class StringProcess implements Processor { @Override public String name() { return getClass().getSimpleName(); } public abstract Object process(Object input); } /** * 大写处理器 */ class UpCase extends StringProcess { @Override public Object process(Object input) { return ((String)input).toUpperCase(); } } /** * 小写处理器 */ class DownCase extends StringProcess { @Override public Object process(Object input) { return ((String)input).toLowerCase(); } } /** * 分割数组处理器 */ class Splitter extends StringProcess { @Override public Object process(Object input) { return Arrays.toString(((String)input).toString().split(" ")); } } public class Apply { public static void process(Processor p, Object s){ System.out.println("Using Processor "+ p.name()); System.out.println(p.process(s)); } public static String s = "Disagreement with beliefs is by definition incorrect"; /** * 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法, * 被称为策略设计模式 * @param args */ public static void main(String[] args) { BigInteger b = new BigInteger("111111111111111"); process(new UpCase(), s); process(new DownCase(), s); process(new Splitter(), s); } /** * 这个类使用到了一个设计模式: 叫做策略设计模式 * 所谓 的策略设计模式指的是: 能够根据传递参数的不同而具有不同的行为的方法, 被称为策略设计模式 * 这类方法包含了所有执行的算法中固定不变的部分(这里是基类Processor,Object),而策略就是实际调用该方法时传递的实际参数, * 这部分参数是变化的,因此被称之为是"策略"部分. * 在main中看到了三种不同类型的策略应用到了String类型s对象上。 */ }在这个类里面,将Process定义为了接口。并且添加了StringProcess 字符串处理器类,提供了字符串处理的公用方法。这时如何修改案例二,才能让Apply成为一个公用类呢?其中一个方法是让Filter实现Process接口。但为题又来了,Filter中的process的输入输出参数都是Waveform类型的对象。而Process中process的输入输出都是Object。这时我们使用一个适配器来解决这个问题
package net.mindview.interfaces.filters2; import net.mindview.interfaces.classprocessor2.Processor; /** * 由于Filter中的process的输入输出参数都是Waveform,而Processor中process的输入 * 输出参数都是Object. 因此,写一个适配器,让Filter可以适配Processor类型的接口 * */ public class FilterAdapter implements Processor{ Filter filter; public FilterAdapter(Filter filter){ this.filter = filter; } @Override public String name() { return filter.name(); } @Override public Object process(Object input) { return filter.process((Waveform)input); } }
接下来看看Apply类如何处理的?
public class Apply { public static void process(Processor p, Object s){ System.out.println("Using Processor "+ p.name()); System.out.println(p.process(s)); } public static String s = "Disagreement with beliefs is by definition incorrect"; /** * 向本例这样, 创建一个能够根据所传递的参数对象的不同而具有不同行为的方法, * 被称为策略设计模式 * @param args */ public static void main(String[] args) { System.out.println("======处理字符串==========="); process(new UpCase(), s); process(new DownCase(), s); process(new Splitter(), s); System.out.println("======过滤波==========="); process(new FilterAdapter(new LowPass(1.0)), new Waveform()); process(new FilterAdapter(new HighPass(100.0)), new Waveform()); process(new FilterAdapter(new BandPass(1.0,10.0)), new Waveform()); } }
上面这个例子就示范了使用接口的好处,让类和方法解耦。同时还涉及到一个设计模式,叫适配器设计模式。适配器设计模式有三种, 确切的说这是对象适配器模式.
3. 适配器设计模式
参考文章:http://blog.csdn.net/zxt0601/article/details/52848004
定义:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
属于结构型模式
主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。
本文做以下约定
-
- 需要被适配的类,对象,接口被定义为源(目前已有的),简称为src(source).
- 最终需要输出的(我们需要的),简称dst(destination,也叫target)
- 适配器Adapter
一句话描述适配器模式:src->adapter->dist,即src以某种形式(类,对象,接口)给到适配器,适配器最终输出dist。
使用 场景
-
-
想要使用一个已经存在的类,但如果它的方法不满足需求时;
-
两个类的职责相同或相似,但是具有不同的接口时要使用它;
-
应该在双方都不太容易修改的时候再使用适配器模式适配,而不是一有不同时就使用它
-
第一类: 类适配器模式
一句话描述使用方法:Adapter类,通过继承src类,实现dist接口,完成src -> dist的适配.
以充电器为例:充电器本身就是一个适配器。输入的时220v电压, 输出的是5v电压。
首先, 有一个src类,本身是输出220v电压
package net.mindview.interfaces.adaptor; /** * 这是目前有的电压 200v * @author samsung * */ public class Voltage220 { public int output(){ int i = 220; System.out.println("这是220v电压"); return i; } }
然后, 有一个目标使用电压是5v
package net.mindview.interfaces.adaptor; /** * 我们需要使用的目标电压是5v * 这是一个5v电压的接口 * @author samsung * */ public interface Voltage5 { public int output(); }
定义一个适配器, 将220v电压经过处理后转换为5v电压供用户使用
package net.mindview.interfaces.adaptor; /** * 适配器,将220v电压输出为5v电压 * 适配器 继承src类, 实现dist类,实现从src->dist的转换 */ public class VoltageAdapter extends Voltage220 implements Voltage5{ @Override public int output() { int src = super.output(); System.out.println("适配器工作开始适配电压"); int dist = src/44; System.out.println("适配完成后输出电压:" + dist); return dist; } }
有一款手机需要充电,充电电压是5v
package net.mindview.interfaces.adaptor; public class Mobile { public void chargeing(Voltage5 v5){ if(v5.output() == 5){ System.out.println("正常充电"); } else { System.out.println("秒变note7"); } } }
使用手机的时候,需要将家里的220v电压转换为5v输出.调用适配器
public static void main(String[] args) { Mobile m = new Mobile(); m.chargeing(new VoltageAdapter()); }
输出:
===============类适配器============== 我是220V 适配器工作开始适配电压 适配完成后输出电压:5 电压刚刚好5V,开始充电
小结:
Java这种单继承的机制,所有需要继承的我个人都不太喜欢。
所以类适配器需要继承src类这一点算是一个缺点,
因为这要求dst必须是接口,有一定局限性;
且src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
但同样由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
第二类: 对象适配器模式
基本思路和类适配器一样, 只是将adapter类做了修改, 这次不继承src类,而是持有src类实例对象,以解决兼容性问题。
一句话总结使用方法:持有src类,实现dist类接口, 实现src -> dist的适配
(根据”合成复用规则”, 在系统中尽量使用组合来代替继承)
package net.mindview.interfaces.adaptor; /** * 持有src类,实现dist类接口, 实现src -> dist的适配 * @author samsung * */ public class VoltageAdapter2 implements Voltage5{ Voltage220 voltage220; public VoltageAdapter2(Voltage220 voltage220){ this.voltage220 = voltage220; } @Override public int output() { System.out.println("得到220v电压"); int src = voltage220.output(); System.out.println("经过处理,输出5v电压"); int dist = src/44; System.out.println("最终使用的电压是"+dist+"v"); return dist; } }
测试结果:
得到220v电压
这是220v电压
经过处理,输出5v电压
最终使用的电压是5v
正常充电
小结:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,组合大于继承,
所以它解决了类适配器必须继承src的局限性问题,也不再强求dst必须是接口。
同样的它使用成本更低,更灵活。
第三类 接口适配器模式
也有文献称之为认适配器模式(Default Adapter Pattern)或缺省适配器模式。
定义:
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
我们直接进入大家最喜爱的源码撑腰环节:
源码撑腰环节:
Android中的属性动画ValueAnimator
类可以通过addListener(AnimatorListener listener)
方法添加监听器,
那么常规写法如下:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100); valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.start();
有时候我们不想实现Animator.AnimatorListener
接口的全部方法,我们只想监听onAnimationStart
,我们会如下写:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //xxxx具体实现 } }); valueAnimator.start();
显然,这个AnimatorListenerAdapter
类,就是一个接口适配器。
查看该Adapter类源码:
public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener, Animator.AnimatorPauseListener { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationPause(Animator animation) { } @Override public void onAnimationResume(Animator animation) { } }
可见,它空实现了Animator.AnimatorListener
类(src)的所有方法.
对应的src类:
public static interface AnimatorListener { void onAnimationStart(Animator animation); void onAnimationEnd(Animator animation); void onAnimationCancel(Animator animation); void onAnimationRepeat(Animator animation); }
类图:
我们程序里的匿名内部类就是Listener1 2 这种具体实现类。
new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //xxxx具体实现 } }
接口适配器模式很好理解,令我们的程序更加简洁明了。
总结
我个人理解,三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
类适配器,以类给到,在Adapter里,就是将src当做类,继承,
对象适配器,以对象给到,在Adapter里,将src作为一个对象,持有。
接口适配器,以接口给到,在Adapter里,将src作为一个接口,实现。
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
但是在实际开发中,实现起来不拘泥于本文介绍的三种经典形式,
例如Android中ListView、GridView的适配器Adapter,就不是以上三种经典形式之一,
我个人理解其属于对象适配器模式,一般日常使用中,我们都是在Adapter里持有datas,然后通过getView()/onCreateViewHolder()方法向ListView/RecyclerView提供View/ViewHolder。
Client是Lv Gv Rv ,它们是显示View的类。
所以dst(Target)是View。
一般来说我们有的src是数据datas,
即,我们希望:datas(src)->Adapter->View(dst)->Rv(Client)。