zoukankan      html  css  js  c++  java
  • Java中的面向接口编程

     面向接口编程是很多软件架构设计理论都倡导的编程方式,学习Java自然少不了这一部分,下面是我在学习过程中整理出来的关于如何在Java中实现面向接口编程的知识。分享出来,有不对之处还请大家指正。

    接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。基于这种原则,通常推荐“面向接口”编程,而不是面向实现类编程,希望通过面向接口编程来降低程序的耦合下面分两种常用场景来示范“面向接口”编程的优势。

    (一)简单工厂模式

    有一个场景,假设程序中有个Comupter类需要组合一个输出设备,现在有两个选择:直接让Comupter该类组合一个Printer属性,或者让Comupter组合一个Output属性,那么到底采用哪种方式更好呢?

    假设让Computer组合一个Printer属性,如果有一天系统需要重构,需要使用BetterPrinter来代替Printer,于是我们需要打开Computer类源代码进行修改。如果系统中只有一个Computer类组合了Printer属性还好,如果系统中有100个类组合了Printer属性,甚至1000个,10000个……将意味着我们要打开100个、1000个、10000类进行修改,这是多么大的工作量!

    为了避免这个问题,我们让Comupter组合一个Output属性,将Comupter类与Printer类完全分离。Computer对象实际组合的是Printer对象,还是BetterPrinter对象,对Computer而言完全透明。当Printer对象切换到BetterPrinter对象时,系统完全不受影响。下面是这个Computer类定义的代码。

    public class Computer

    {

    private Output out;

    public Computer(Output out)

    {

    this.out = out;

    }

    //定义一个模拟获取字符串输入的方法

    public void keyIn(String msg)

    {

    out.getData(msg);

    }

    //定义一个模拟打印的方法

    public void print()

    {

    out.out();

    }

    }

    上面的Computer类已经完全与Printer类分离开,只是与Output接口耦合。Computer不再负责创建Output对象,系统提供一个Output工厂来负责生成Output对象。这个OutputFactory工厂类代码如下:

    public class OutputFactory

    {

    public Output getOutput()

    {

    return new Printer();

    }

    public static void main(String[] args) 

    {

    OutputFactory of = new OutputFactory();

    Computer c = new Computer(of.getOutput());

    c.keyIn("疯狂Java讲义");

    c.keyIn("轻量级J2EE企业应用实战");

    c.print();

    }

    }

    在该OutputFactory类中包含了一个getOutput方法,该方法返回一个Output实现类的实例,该方法负责创建Output实例,具体创建哪一个实现类的对象由该方法决定(具体由该方法中粗体部分控制,当然也可以增加更复杂的控制逻辑)。如果系统需将Printer改为BetterPrinter实现类,只需要让BetterPrinter实现Output接口,并改变OutputFactory类中的getOutput方法即可。

    下面是BetterPrinter实现类的代码,BetterPrinter只是对原有的Printer进行简单修改,以模拟系统重构后的改进。

    public class BetterPrinter implements Output

    {

    private String[] printData = new String[MAX_CACHE_LINE * 2];

    //用以记录当前需打印的作业数

    private int dataNum = 0;

    public void out()

    {

    //只要还有作业,继续打印

    while(dataNum > 0)

    {

    System.out.println("高速打印机正在打印:" + printData[0]);

    //把作业队列整体前移一位,并将剩下的作业数减1

    System.arraycopy(printData , 1, printData, 0, --dataNum);

    }

    }

    public void getData(String msg)

    {

    if (dataNum >= MAX_CACHE_LINE * 2)

    {

    System.out.println("输出队列已满,添加失败");

    }

    else

    {

    //把打印数据添加到队列里,已保存数据的数量加1。

    printData[dataNum++] = msg;

    }

    }

    }

    上面的BetterPrinter类也实现了 Output接口,因此也可当成Output对象使用,于是我们只要把OutputFactory工厂类的getOutput方法中粗体部分改为如下代码:

    return new BetterPrinter();

    再次运行前面的OutputFactory.java程序,发现系统运行时已经改为BetterPrinter对象,而不再是原来的Printer对象。

    通过这种方式,我们把所有生成Output对象的逻辑集中在OutputFactory工厂类中管理,而所有需要使用Output对象的类只需与Output接口耦合,而不是与具体的实现类耦合。即使系统中有很多类使用了Printer对象,只要OutputFactory类的getOutput方法来生成Output对象是BetterPrinter对象,则它们全部都会改为使用BetterPrinter对象,而所有程序无需修改,只需要修改OutputFactory工厂的getOutput的方法实现即可。


    (二)命令模式

    考虑这样一种场景:某个方法需要完成某一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素时如何处理这些元素,需要在调用该方法时指定具体的处理行为。

    这个要求看起来有点奇怪:这个方法需要不仅要普通数据可以变化,甚至还有方法执行体也需要变化,难道我们能把“处理行为”作为一个参数传入该方法?

    对于这样一个需求,我们必须把“处理行为”作为参数传入该方法,这个“处理行为”用编程来实现就是一段代码。那如何把这段代码传入该方法呢?

    因为Java不允许代码块单独存在,因此我们使用一个Command接口来定义一个方法,用这个方法来封装“处理行为”。下面是该Command接口代码。

    public interface Command

    {

    //接口里定义的process方法用于封装“处理行为”

    void process(int[] target);

    }

    上面的Command接口里定义了一个process方法,这个方法用于封装“处理行为”,但这个方法没有方法体——因为现在还无法确定这个处理行为。

    下面是需要处理数组的处理类,在这个处理类中包含一个process方法,这个方法无法确定处理数组的处理行为,所以定义该方法时使用了一个Command参数,这个Command参数负责对数组的处理行为。该类的程序代码如下。

    public class ProcessArray

    {

    public void process(int[] target , Command cmd) 

    {

    cmd.process(target);

    }

    }

    通过一个Command类,就实现了让ProcessArray类和具体“处理行为”的分离,程序使用Command接口代表了对数组的处理行为。Command接口也没有提供真正的处理,只有等到需要调用ProcessArray对象process方法时,才真正传入一个Command对象,才确定对数组的处理行为。

    下面程序示范了对数组的两种处理方式:

    public class TestCommand

    {

    public static void main(String[] args) 

    {

    ProcessArray pa = new ProcessArray();

    int[] target = {3, -4, 6, 4};

    //第一次处理数组,具体处理行为取决于PrintCommand

    pa.process(target , new PrintCommand());

    System.out.println("------------------");

    //第二次处理数组,具体处理行为取决于AddCommand

    pa.process(target , new AddCommand());

    }

    }

    下面分别是PrintCommand类和AddCommand类的代码。

    public class PrintCommand implements Command

    {

    public void process(int[] target)

    {

    for (int tmp : target )

    {

    System.out.println("迭代输出目标数组的元素:" + tmp);

    }

    }

    }

    public class AddCommand implements Command

    {

    public void process(int[] target)

    {

    int sum = 0;

    for (int tmp : target )

    {

    sum += tmp;

    }

    System.out.println("数组元素的总和是:" + sum);

    }

    }

    对于PrintCommand和AddCommand两个实现类而言,实际有意义的部分就是process(int[] target)方法,该方法的方法体就是传入ProcessArray类里process方法的“处理行为”,通过这种方式就可实现process方法和“处理行为”的分离。

  • 相关阅读:
    InnoDB引擎面面观
    [读史思考]为何此大神可以同时进入文庙和武庙?
    [源码解析] 当 Java Stream 遇见 Flink
    Alink漫谈(十八) :源码解析 之 多列字符串编码MultiStringIndexer
    [源码解析] Flink UDAF 背后做了什么
    [业界方案] ClickHouse业界解决方案学习笔记
    Istio Routing 实践掌握virtualservice/gateway/destinationrule/AB版本发布/金丝雀发布
    树立个人品牌意识:从背景调查谈谈职业口碑的重要性
    Istio 生产环境用户流量接入方案
    故障管理:故障应急和故障复盘
  • 原文地址:https://www.cnblogs.com/android-blogs/p/5542153.html
Copyright © 2011-2022 走看看