实验1:框架设计者

最后编辑2019.11.21【有同学说,本博客看不懂。我搞详细一点。晕】

【实验3:框架设计者 目的】本实验的编程任务,是编写并测试冒泡排序framework.temp.BubbleSort,然后在此基础上编写(或者说获得)排序的测试框架,用简单的例子破除人们对框架的神秘感。同时,说明依赖倒置原则是垃圾。

下面先给出最简单粗暴的代码,即在编写完了排序算法后,将测试相关的代码也直接地放在BubbleSort中。

package chap1.temp;//将被重构 –移动
public class BubbleSort  {
    public int[] sort(int[] arr){//本书通常不关注算法!
        for(int i = 0 ; i< arr.length-1; i++){
            for(int j = 0 ; j < arr.length-i-1;j++){
                if(arr[j] > arr[j+1]){ //冒泡
                    swap(arr ,j, j+1);
                }
            }
        }
        return arr;
    }
    
    //移到父类型中
    public static void swap(int[] arr ,int one, int two){
        if(one == two){return;}
        int temp = arr[one];
        arr[one] = arr[two];
        arr[two] = temp;
    }

    //移到SortTest中
    public static void  simpTest(int[] array) {        
        array = new BubbleSort().sort(array);
        pln(array);
    }
    public static void pln(int[] array) {
        System.out.println(java.util.Arrays.toString(array));
    }

    public static void main(String[] args){//BlueJ中可以省略本方法。
        simpTest(new int[]{5, 8, 5, 4, 3, 5, 2, 7, 5, 9});
    }  
}

temp(临时文件夹)这个包名,意味着BubbleSort代码的结构,有许多值得改进的地方,后面将简略地说明,为什么需要改进以及如何改进。本实验一共包括五个步骤的变化。其中→表示依赖/使用关系,<=表示继承关系。

  1. SortTest→BubbleSort。客户SortTest依赖具体的服务BubbleSort,这是不好的设计。
  2. SortTest→IntSort<=BubbleSort。应用程序SortTest遵循针对接口编程/依赖抽象类型原则,它依赖抽象类型IntSort,而BubbleSort自然地依赖父类型。
  3. 【SortTest→IntSort】。这一步很关键。在需要的时候,可以将SortTest设计成框架,于是将控制模块SortTest和它必须依赖的抽象类型IntSort打包(最好能够为该包提供JavaDoc)。从应用程序(上层模块)变为框架(下层模块)过程中,建议将IntSort的方法名sort 改成soooooort——体现控制反转。
  4. Main→【SortTest→IntSort】<=BubbleSort。(其他程序员)使用框架。
  5. 带main的BubbleSort,BubbleSort→【SortTest →IntSort】<=BubbleSort。与第一步“依赖倒置”。

1.单一职责原则、针对接口编程/抽象依赖原则

在temp.BubbleSort中,存在编程初学者最常见的问题——把所有的功能都写在一个类里。比如说temp.BubbleSort既有排序相关的代码,如sort()和swap();也有测试相关的代码,如simpTest()。恰当的面向对象的设计方案,是将不同的职责由不同的类型封装。

package framework.temp; 
public interface IntSort { //仅对int进行排序
    public int[] sort(int[] arr);
    public static void swap(int[] arr ,int one, int two){     /*略*/    }
}

package framework.temp; 
import static yqj2065.util.Print.pln;
public class SortTest{
    public static void  simpTest (IntSort s, ,int[] array){
        array = s.sort(array);
        pln(array);
    } 
    //或者,成员变量等待setter参数传入 ;或者依赖注入
    private IntSort s;    
    public static void setIntSort(IntSort s){
        this.s =s;
    }
    public static void  simpTest ( int[] array){
        array = s.sort(array);
        pln(array);
    } //main(String[])略
}
//Main
    public static void main(){
        framework.temp.IntSort obj = new framework.temp.BubbleSort();
        framework.temp.SortTest.setIntSort(obj);
        framework.temp.SortTest.simpTest(new int[]{5, 8, 5, 4, 3, 5, 2, 7, 5, 9});
    }

注意:import static yqj2065.util.Print.pln;不可用时,直接用下面的工具方法。

    public static void pln(int[] array) {
        System.out.println(java.util.Arrays.toString(array));
    }

SortTest 的方法,需要依赖IntSort。方法simpTest 修改成: public static void  simpTest (IntSort s, ,int[] array)

2.设计框架

现在聚焦SortTest,它是测试流程的控制模块。当应用程序员编写SortTest,他感觉一切都在他的(代码的)控制之下。也就是说,应用程序员决定控制模块SortTest要干什么、测试程序的流程是什么;最为关键的是,应用程序员还决定SortTest依赖的类型名为IntSort、IntSort的接口名为sort(),等等。

考虑编写SortTest的另外一种场景。假设SortTest中提供了完善的测试逻辑——如比较被测算法与JDK排序算法的一致性(保证排序正确)、大规模测试的时间复杂度的图形绘制等等重要而有用的代码,因此希望SortTest能够被广泛地复用。或者说,SortTest和IntSort是老师预先提供的测试工具并打包提供给学生,学生随后编写自己的某种排序算法并被要求复用该包。

这种场景,可以称为对排序测试的框架的复用

为了体现框架的独立性,下面将排序测试框架涉及到SortTest和IntSort打包,表示为【SortTest→IntSort】。

在BlueJ中创建另一个项目tool,包名为yqj2065.util,【永远记住:不要在默认包中编写代码

将SortTest和IntSort移动到该包,打包为一个jar文件(文件名随意,如123. jar)。SortTest的方法改名为test()。将IntSort对方法改名为soooooooort。【可以可以将包名、修改后的函数名复制粘贴到某处备用。】

将打包的jar添加到应用程序项目的依赖库中,就能够方便地复用它们。【打包、添加第三方包的方式,见《编程导论》 6.2.2 类路径和第三方包 】

3.应用程序员的工作

将打包的jar添加到应用程序项目的依赖库中,应用程序员通过import语句,就能够方便地复用它们。【import语句也可以不要,程序中直接写类全名。import语句和jar的文件名无关。我为什么要你用123.jar,当你import 123时,会感觉不对劲,因为Java中数字不能够作为标识符】

应用程序员编写IntSort的子类型BubbleSort,以及Main。表示为Main->【SortTest->IntSort】<=BubbleSort。

说明:什么是控制反转,什么是框架;

体会:什么是策略模式

 

4.何谓控制反转

拜托,在我的术语表中没有控制反转(Inversion of Control,英文缩写为IoC)这个垃圾术语,我不知道它是什么东西。【考试的时候,你要知道!】

控制反转/Ioc,即框架控制一切,应用程序员提供支持代码。IoC反映了某些应用程序员的失落心情:从自由自在变为受控于人。IoC描述了一种现象:软件设计决定的控制权,由应用程序员转移到框架设计者手中。除此之外,IoC没有任何其他含义

记住,永远不要问“哪些方面的控制被反转了呢?”这种愚蠢的问题——像Martin Fowler那样。因为框架的设计者从来不考虑这种问题,他们不谈控制——他们只考虑SortTest应该如何进行完美的测试,没有反转——他们不知道框架的使用者要测试什么排序算法,但是必须是IntSort的子类型。

 

同学们不清楚的地方,可以在博客下面提问。

 

 

 

 

 

 

 

 

 

 

 

 

 

---------------------------------------------------------------------

下面的文字先不要管。

本文介绍何谓框架,并提供了一个最简单的框架——对两个double操作后返回一个double值。

1、何谓框架

框架/framework是在底层/下层模块中定义的一个骨架式方案,处理各种应用中面临的共同的底层细节;而应用开发者可以按照自己的需求使用框架,给出自己的功能实现——只需要填入自己的东西/flesh。如果是applet或GUI框架,就有太多底层细节需要处理,因而最简单的框架util.DoubleOP仅仅说明这是一个骨架式方案——对两个double操作后返回一个double值。在这个骨架式方案,本框架处理了....大量的技术细节,不过代码被省略了。

本框架可以使你对两个double进行各种操作,如相加,比较、求幂等等,你具体如何应用,需要你按照自己的需求提供代码。

 

package util;
/**
 *框架/framework的目的,是定义一个骨架式方案,处理各种应用中面临的共同的底层细节;
 * @author yqj2065
 * @version 2016.7
 */
@FunctionalInterface 
public interface  DoubleOP {
    //对两个double操作后返回一个double值.可以使你对两个double进行各种操作,如相加,比较、求幂等等
    double op(double m,double n) ;
}

嗯,最简单的框架就是一个函数接口,它对两个double操作后返回一个double值。类似于JUnit,我们还设计了一个包含main(String[] args)启动本框架的类Main。使用本框架时,请在配置文件my.properties中指定将要创建的对象,键为2065

 

package util;
import tool.God;
public class Main{
    /**
     * 命令行启动本框架。
     * @param args 至少两个double
     */
   public static void main(String[] args){
        DoubleOP f = (DoubleOP)God.create("2065");
        double d =f.op(Double.parseDouble(args[0]),Double.parseDouble(args[1]));
        System.out.println(d);
    }
    /**
     * 和JUnit一样,提供App调用的方式。
     */
    public static double runFramework(DoubleOP f,double x,double y){
        return f.op(x,y);
    } 
}

注意,框架一般使用的技术就是反射和回调

 

  • tool.God利用反射和配置文件创建某个对象,
  • Main调用 f.op(double,double),具体的方法体由上层代码提供。

 

ok,框架已经设计完成。


 

2.如何使用框架

现在是我们应用程序员干活的时候了。应用程序员最开始当然写HelloWorld,来熟悉框架..

.HelloWorld不能够和高傲的框架在一个包中,principle.callback.lowe,你可以想象它被打包为一个jar。

框架的使用者,通常进行填空式编程

这就是库与框架的区别——上层模块的程序员直接调用的,属于库函数;要求上层模块的程序员提供的回调函数的,属于框架。

 

package abc;
public class HelloWorld implements util.DoubleOP{
    public double op(double m,double n){
        return m+n;
    }
}

第二步,按照框架的要求,自己写好配置文件。于是我们在my.properties中加上下面的键值对:

2065=principle.callback.HelloWorld

第三步,在命令行中运行和测试框架,例如:

E:\designPattern>java principle.callback.lower.Main 1.2 3.4
4.6

通常,我们应用程序员不会在控制台直接启动或调用框架,而是在应用程序中启动或调用框架,这就是框架的Main提供

runFramework(MyFramework ,double,double)的意义。框架的设计者真的很贴心。我们的应用程序如下:

 

package abc;
import tool.God;
import util.DoubleOP;
import util.Main;
public class App{
    public static void main(String[] args){
        DoubleOPf = (DoubleOP)God.create("2065");
        double d = Main.runFramework(f,1.2,3.4);
        System.out.println(d);
    }
}

既然HelloWorld也在本包中,应用程序App可以直接使用它而不用反射。我们也可以在App使用匿名类、λ表达式直接描述自己的需求——不定义太多的HelloWorld这样的类。

 

 

package abc;
import tool.God;
import util.DoubleOP;
import util.Main;
public class App{
    public static void main(String[] args){
        DoubleOP f = (DoubleOP)God.create("2065");
        double d = Main.runFramework(f,1.2,3.4);
        System.out.println(d);
    }
    public static void test(){
        double d = Main.runFramework(new HelloWorld(),1,3);
        System.out.println(d);
        
        DoubleOP f = (double m,double n)->{return m * n ;};
        d = Main.runFramework(f,1,3);
        System.out.println(d);
        
        f = (m,n)->{return m +2*n ;};
        d = Main.runFramework(f,1,3);
        System.out.println(d);

        d = Main.runFramework((m,n)->{return m-n ;},1,3);
        System.out.println(d);
    }
}

运行test()输出:

 

4.0
3.0
7.0
-2.0

 

你可能感兴趣的:(#,面向对象设计(Java),《编程导论(Java)》道&理)