面向接口编程是很多软件架构设计理论都倡导的编程方式,学习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方法和“处理行为”的分离。

Java中的面向接口编程的更多相关文章

  1. Python 中的面向接口编程

    前言 "面向接口编程"写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...

  2. Java中的面向切面编程(AOP)

    一.什么是AOP? Aspect Oriented Programming ,即面向切面编程. AOP是对面向对象编程的一个补充. 它的目的是将复杂的需求分解为不同的切面,将散布在系统中的公共功能集中 ...

  3. javascript设计模式学习之十七——程序设计原则与面向接口编程

    一.编程设计原则 1)单一职责原则(SRP): 这里的职责是指“引起变化的原因”:单一职责原则体现为:一个对象(方法)只做一件事. 事实上,未必要在任何时候都一成不变地遵守原则,实际开发中,因为种种原 ...

  4. Java面向接口编程,低耦合高内聚的设计哲学

    接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极大的降低程序中各个模块之间的耦合,提高系统的可维护性以及可扩展性. 因此,很多的软件架构设计理念都倡导"面向接口编程"而 ...

  5. java接口,接口的特性,接口实现多态,面向接口编程

    package cn.zy.cellphone; /**接口是一种引用数据类型.使用interface声明接口,形式 * 形式:public interface 接口名称{} * 接口不能拥有构造方法 ...

  6. java面向接口编程

    在oop中有一种设计原则是面向接口编程,面向接口编程有非常多优点,详细百度一大片.我来谈一下详细的使用中的一些不成熟的见解.! 首先面向接口编程能够消除类之间的依赖关系,使得业务仅仅依赖接口. 这样有 ...

  7. go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?

    到底是要猫还是要狗 在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念. ...

  8. 转:二十一、详细解析Java中抽象类和接口的区别

    转:二十一.详细解析Java中抽象类和接口的区别 http://blog.csdn.net/liujun13579/article/details/7737670 在Java语言中, abstract ...

  9. Java中的图形界面编程

    前言 正文 Java中的图形界面编程 AWT/Swing AWT(Abstract Window ToolKits,抽象窗体工具集) 1.容器类:用来存储组件,实现容器布局 2.组件类:实现界面的一些 ...

随机推荐

  1. UVALive 6525

    二分图最大匹配 #include<cstdio> #include<iostream> #include<cstring> #define MAX 10010 us ...

  2. POJ 3411 Paid Roads(SPFA || DFS)

    题目链接 题意 : 要从1城市到n城市,求最短路是多少,从a城市到达b城市的路程,如果你到过c城市,则需要走p,否则走r长. 思路 : 因为可以来回走,所以不能用单纯的最短路,可以用二维SPFA,状态 ...

  3. POJ3250Bad Hair Day

    http://poj.org/problem?id=3250 题意 :  n个牛排成一列向右看,牛 i 能看到牛 j 的头顶,当且仅当牛 j 在牛 i 的右边并且牛 i 与牛 j 之间的所有牛均比牛 ...

  4. linux 踢出用户方法

    linux系统root用户可强制踢制其它登录用户, 首先以root登录以便查看全部的在线用户信息,可用w命令查看登录用户信息 强制踢人命令格式:pkill -kill -t tty 解释: pkill ...

  5. cojs 榴莲 题解报告

    首先这道题目是求第k大 求第k大我们有逐位确定,主席树,整体二分等等方法 首先我们考虑如何处理每个询问 我们可以二分答案k,之后扫一遍之前的操作 我们只需要知道有多少个权值>=k的操作经过当前点 ...

  6. lintcode : 二叉树的最小深度

    题目: 二叉树的最小深度 给定一个二叉树,找出其最小深度. 二叉树的最小深度为根节点到最近叶子节点的距离. 样例 给出一棵如下的二叉树: 1 /     \ 2       3 /    \ 4    ...

  7. Swift 版本很好的卡片切换效果基于ZLSwipeableView

    前言:在这篇文章你可以学到,一些基本的Swift语法, 基本UI控件闭包等. 实际的效果,比gif图的效果好很多. 卡片切换.gif 首先需要导入ZLSwipeableView pod 'ZLSwip ...

  8. Ios 弹框 MJPopup,KxMenu

    IOS 弹框 如果直接弹出一个自定义的视图 可以选用第三方: MJPopup 弹出: if(!bandview) { bandview=[[[NSBundle mainBundle]loadNibNa ...

  9. java:构造函数

    class Dog { Dog(){ } } 构造函数没有返回值定义,构造函数名必须和类名相同,如果类里面没有构造函数,编译器会帮你加一个构造函数. 使用this调用构造函数 class Dog { ...

  10. Nmap扫描原理与用法

    Nmap扫描原理与用法 1     Nmap介绍 Nmap扫描原理与用法PDF:下载地址 Nmap是一款开源免费的网络发现(Network Discovery)和安全审计(Security Audit ...