java编程思想第四版第九章总结

Posted itpower

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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)。

 



























以上是关于java编程思想第四版第九章总结的主要内容,如果未能解决你的问题,请参考以下文章

java编程思想第四版第9章

java编程思想第四版第五章习题

『Java编程思想-第四版』第二章:一切都是对象

Java编程思想---第九章 接口(上)

一起读《Java编程思想》(第四版)

一起读《Java编程思想》(第四版)