接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极大的降低程序中各个模块之间的耦合,提高系统的可维护性以及可扩展性。

因此,很多的软件架构设计理念都倡导“面向接口编程”而不是面向实现类编程,以期通过这种方式来降低程序的耦合。

但是在讨论这些之前,我们先要搞清楚一个问题:

接口还是抽象类?

为什么会有这个问题,因为在某些情况下,接口和抽象类很像:

  • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其它类实现或者继承。
  • 接口和抽象类都可以包含抽象方法,实现接口或者继承抽象类的普通子类都必须实现这些抽象方法。

接口作为系统和外界交互的窗口,体现的是一种规范。规定了实现者必须向外提供哪些服务,调用者可以调用哪些服务。当在一个程序中使用接口时,接口是多个模块之间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。

而抽象类则不同,它作为多个子类的共同父类,体现的是一种模板式设计。它可以作为系统实现的中间产品,但是必须要进行完善才能成为最终产品。

除此之外,接口和抽象类在用法上也存在差别:

  • 接口只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则可以包含普通方法。
  • 接口中不能定义静态方法;抽象类可以。
  • 接口不能定义普通成员变量;抽象类可以。
  • 接口不包含构造器;抽象类可以包含构造器,但并不是用于创建对象,而是用于让其他子类调用这些构造器来完成属于抽象类的初始化操作。
  • 一个类只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补单继承的不足。

下面介绍两种常见的应用场景来示范面向接口编程的优势。

简单的工厂模式

有这样一个场景:程序中的每一个Computer类都需要集成一个Printer类,以完成设备的输入输出,示例如下:

public class Computer{
public static void main(String []args){
Printer p = new Printer();
p.getMsg("Amos");
p.getMsg("H's blog");
p.printMsg();
}
} class Printer{
private String msg = "";
public void getMsg(String message){
msg += message;
}
public void printMsg(){
System.out.println(msg);
}
}

但是,当我们在重构代码的时候,需要使用BetterPrinter类以替换Printer类时,如果只有一个类集成了Printer那么还好,但是如果有成百上千个类集成了这个类呢?那么工作量将会是巨大的!

为了解决这个问题,我们可以使用接口实现工厂模式,让Computer类集成一个PrinterFactory类,将Computer类和Printer类的实现完全分隔开来。当从Printer类切换到BetterPrinter类时对系统完全没有影响。

示例如下:

class Computer{
private Out p;
public Computer(Out printer){
this.p = printer;
}
public void keyIn(String msg){
p.getMsg(msg);
}
public void printMsg(){
p.printMsg();
}
} class Printer implements Out{
private String msg = "";
public void getMsg(String message){
msg += message;
}
public void printMsg(){
System.out.println(msg);
}
} interface Out{
String mes = "";
void getMsg(String message);
void printMsg();
} public class PrinterFactory{
public Out out(){
return new Printer();
}
public static void main(String[] args){
PrinterFactory pf = new PrinterFactory();
Computer c = new Computer(pf.out());
c.keyIn("Amos");
c.keyIn("H's blog");
c.printMsg();
//output AmosH's blog
}
}

该实现使用工厂类用来生产Printer对象实例,而Computer类则只接收工厂类返回的对象用以初始化,分隔了和Printer对象实现之间的耦合。当系统需要使用BetterPrinter类替换Printer类时,只要BetterPrinter类实现了Out接口,然后在PrinterFactory工厂类中修改产生的对象即可。

下面是替换示例:

class Computer{
private Out p;
public Computer(Out printer){
this.p = printer;
}
public void keyIn(String msg){
p.getMsg(msg);
}
public void printMsg(){
p.printMsg();
}
} class BetterPrinter implements Out{
//全新的打印类,限制只能存储5次输入
private String msg = "";
private int count = 5;
public void getMsg(String message){
msg += message;
count--;
if(count==0){
printMsg();
count = 5;
}
}
public void printMsg(){
System.out.println(msg);
msg="";
count = 5;
}
} interface Out{
String mes = "";
void getMsg(String message);
void printMsg();
} public class PrinterFactory{
public Out out(){
return new BetterPrinter();
}
public static void main(String[] args){
PrinterFactory pf = new PrinterFactory();
Computer c = new Computer(pf.out());
c.keyIn("Amos");
c.keyIn("H's ");
c.keyIn("blog ");
c.keyIn("Wel");
c.keyIn("come");
//output AmosH's blog Welcome
c.keyIn("Amos");
c.keyIn("H's ");
c.keyIn("blog ");
c.keyIn("Wel");
c.keyIn("come");
//output AmosH's blog Welcome
c.keyIn("end this");
c.printMsg();
//output end this
}
}

命令模式

考虑这样一个问题:在某些情形下,某个方法需要完成某一个特殊的行为但这个实现无法确定,必须在调用该方法时指定具体的处理行为。

这就意味着,我们必须把“处理方法”作为参数传入该方法,这个“处理行为”用编程实现来说就是一段代码,那么如何把代码传入该方法呢?

我们可以使用Lambda表达式作为处理方法传入,但是当涉及一些比较复杂的处理行为时,这往往是不够的。

我们可以考虑使用Command接口定义一个方法,用这个方法来封装“处理行为”。以下以遍历一个数组作为示范:

public class Test{
public static void main(String[] args){
int[] target = {1,-1,10,-20};
Process pc = new Process();
pc.process(target,new PrintCmd());
//output 当前元素为正数1
//output 当前元素为负数-1
//output 当前元素为正数10
//output 当前元素为负数-20
System.out.println("=======分割线========");
pc.process(target,new AddCmd());
//output 数组的和为-10
}
} interface Command{
void process(int[] target);
} class Process{
public void process(int[] target,Command cmd){
cmd.process(target);
}
} class PrintCmd implements Command{
public void process(int[] target){
for(int i:target){
if(i<0){
System.out.println("当前元素为负数"+i);
}else{
System.out.println("当前元素为正数"+i);
}
}
}
} class AddCmd implements Command{
private int count;
public void process(int[] target){
for(int i:target){
count += i;
}
System.out.println("数组的和为"+count);
}
}

对于PrintCmd和AddCmd而言,真正有用的就是process方法,该方法体传入Process的实例中用以处理数组。实现了process()方法和“处理方法”的相分离。

Java 填坑手册,欢迎fork我的GitHub仓库

Java面向接口编程,低耦合高内聚的设计哲学的更多相关文章

  1. java面向接口编程

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

  2. C# 低耦合 高内聚

    低耦合 loosely Coupling 松散的耦合关系=炮友 couple=夫妻 夫妻=法律约束.家庭.生活.财产.繁衍 炮友:吃喝玩乐,不会产生感情方面的依赖       内聚性 内聚性又称块内联 ...

  3. 低耦合高内聚 - 不要把所有东西都放在 vuex中

    我就举一个例子.比如,我想看电视,是否需要遥控器??请认真思考这个问题. 看似电视与“我”已经解耦了.然而,我需要通过遥控器去看电视,我的目的是看电视,但是我却需要依赖遥控器这个中间件.这就变相地将“ ...

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

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

  5. JAVA面向接口的编程思想与具体实现

    面向对象设计里有一点大家已基本形成共识,就是面向接口编程,我想大多数人对这个是没有什么觉得需要怀疑的.        问题是在实际的项目开发中我们是怎么体现的呢? 难道就是每一个实现都提供一个接口就了 ...

  6. Java中的面向接口编程

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

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

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

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

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

  9. Python 中的面向接口编程

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

随机推荐

  1. 洛谷 P1053 解题报告

    P1053 篝火晚会 题目描述 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有 ...

  2. Python_doc文件写入SQLite数据库

    #docx文档题库包含很多段,每段一个题目,格式为:问题.(答案) #数据库datase.db中tiku表包含kechengmingcheng.zhanngji.timu.daan四个字段 impor ...

  3. Python_字符串之删除空白字符或某字符或字符串

    ''' strip().rstrip().lstrip()分别用来删除两端.右端.左端.连续的空白字符或字符集 ''' s='abc ' s2=s.strip() #删除空白字符 print(s2) ...

  4. Pat1071: Speech Patterns

    1071. Speech Patterns (25) 时间限制 300 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 HOU, Qiming Peo ...

  5. tkiner中Radiobutton单选框控件(七)

    Radiobutton控件 由于本次内容中好多知识都是之前重复解释过的,本次就不做解释了.不太清楚的内容请参考tkinter1-6节中的内容 import tkinter wuya = tkinter ...

  6. tkinter中checkbutton多选框控件和variable用法(六)

    checkbutton控件 简单的实现多选: import tkinter wuya = tkinter.Tk() wuya.title("wuya") wuya.geometry ...

  7. 查看centos系统位数和强制关闭yum

    一个小命令查看centos 是什么多少位系统 getconf LONG_BIT 方法二: [root@linuxzgf ~]#uname -m 如果有x86_64就是64位的,没有就是32位的后面是X ...

  8. reader-write.go

    {         return n, err     }     r.bucket.Wait(int64(n))     return n, err } type writer struct {   ...

  9. logrus_hook.go

    package) //表示自身栈中跳过6个,:]     entry.Data["file"] = fileName     entry.Data["func" ...

  10. [ZLXOI2015]殉国 数论 扩展欧几里得

    题目大意:已知a,b,c,求满足ax+by=c (x>=0,y>=0)的(x+y)最大值与最小值与解的个数. 直接exgcd,求出x,y分别为最小正整数的解,然后一算就出来啦 #inclu ...