Java面向接口编程,低耦合高内聚的设计哲学
接口体现的是一种规范和实现分离的设计哲学,充分利用接口可以极大的降低程序中各个模块之间的耦合,提高系统的可维护性以及可扩展性。
因此,很多的软件架构设计理念都倡导“面向接口编程”而不是面向实现类编程,以期通过这种方式来降低程序的耦合。
但是在讨论这些之前,我们先要搞清楚一个问题:
接口还是抽象类?
为什么会有这个问题,因为在某些情况下,接口和抽象类很像:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其它类实现或者继承。
- 接口和抽象类都可以包含抽象方法,实现接口或者继承抽象类的普通子类都必须实现这些抽象方法。
接口作为系统和外界交互的窗口,体现的是一种规范。规定了实现者必须向外提供哪些服务,调用者可以调用哪些服务。当在一个程序中使用接口时,接口是多个模块之间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
而抽象类则不同,它作为多个子类的共同父类,体现的是一种模板式设计。它可以作为系统实现的中间产品,但是必须要进行完善才能成为最终产品。
除此之外,接口和抽象类在用法上也存在差别:
- 接口只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则可以包含普通方法。
- 接口中不能定义静态方法;抽象类可以。
- 接口不能定义普通成员变量;抽象类可以。
- 接口不包含构造器;抽象类可以包含构造器,但并不是用于创建对象,而是用于让其他子类调用这些构造器来完成属于抽象类的初始化操作。
- 一个类只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补单继承的不足。
下面介绍两种常见的应用场景来示范面向接口编程的优势。
简单的工厂模式
有这样一个场景:程序中的每一个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面向接口编程,低耦合高内聚的设计哲学的更多相关文章
- java面向接口编程
在oop中有一种设计原则是面向接口编程,面向接口编程有非常多优点,详细百度一大片.我来谈一下详细的使用中的一些不成熟的见解.! 首先面向接口编程能够消除类之间的依赖关系,使得业务仅仅依赖接口. 这样有 ...
- C# 低耦合 高内聚
低耦合 loosely Coupling 松散的耦合关系=炮友 couple=夫妻 夫妻=法律约束.家庭.生活.财产.繁衍 炮友:吃喝玩乐,不会产生感情方面的依赖 内聚性 内聚性又称块内联 ...
- 低耦合高内聚 - 不要把所有东西都放在 vuex中
我就举一个例子.比如,我想看电视,是否需要遥控器??请认真思考这个问题. 看似电视与“我”已经解耦了.然而,我需要通过遥控器去看电视,我的目的是看电视,但是我却需要依赖遥控器这个中间件.这就变相地将“ ...
- java接口,接口的特性,接口实现多态,面向接口编程
package cn.zy.cellphone; /**接口是一种引用数据类型.使用interface声明接口,形式 * 形式:public interface 接口名称{} * 接口不能拥有构造方法 ...
- JAVA面向接口的编程思想与具体实现
面向对象设计里有一点大家已基本形成共识,就是面向接口编程,我想大多数人对这个是没有什么觉得需要怀疑的. 问题是在实际的项目开发中我们是怎么体现的呢? 难道就是每一个实现都提供一个接口就了 ...
- Java中的面向接口编程
面向接口编程是很多软件架构设计理论都倡导的编程方式,学习Java自然少不了这一部分,下面是我在学习过程中整理出来的关于如何在Java中实现面向接口编程的知识.分享出来,有不对之处还请大家指正. 接口体 ...
- go 学习笔记之万万没想到宠物店竟然催生出面向接口编程?
到底是要猫还是要狗 在上篇文章中,我们编撰了一则简短的小故事用于讲解了什么是面向对象的继承特性以及 Go 语言是如何实现这种继承语义的,这一节我们将继续探讨新的场景,希望能顺便讲解面向对象的接口概念. ...
- javascript设计模式学习之十七——程序设计原则与面向接口编程
一.编程设计原则 1)单一职责原则(SRP): 这里的职责是指“引起变化的原因”:单一职责原则体现为:一个对象(方法)只做一件事. 事实上,未必要在任何时候都一成不变地遵守原则,实际开发中,因为种种原 ...
- Python 中的面向接口编程
前言 "面向接口编程"写 Java 的朋友耳朵已经可以听出干茧了吧,当然这个思想在 Java 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...
随机推荐
- jsoncpp linux平台编译和arm移植
下载 http://sourceforge.net/projects/jsoncpp/ 或者 http://download.csdn.net/detail/chinaeran/8631141 Lin ...
- eclipse下的tomcat配置https(最简单得配置https)
近期公司列出一大堆的东西,其中包括https,啥也不想说,你们是无法理解的苦逼的我的 本文不是双向认证, 双向认证需要让客户端信任自己生成的证书,有点类似登录银行网站的情,如果想知道双向认证的同志可以 ...
- 【转】数据库事务ACID以及事务隔离
本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ 原子性(Atomicity) 原子性是指 ...
- 微服务与SOA的区别
微服务架构强调的第一个重点就是业务系统需要彻底的组件化和服务化,原有的单个业务系统会拆分为多个可以独立开发,设计,运行和运维的小应用.这些小应用之间通过服务完成交互和集成.每个小应用从前端web ui ...
- git的个人配置
一..gitconfig存储在当前用户所在文件目录下,如图1.1. 图1.1 二.git拉取代码的服务器.用户名.密码,存储的所在位置,如图1.2. 图1.2 三.是否保存密码,由.gitconfig ...
- Linux kernel的中断子系统之(三):IRQ number和中断描述符
返回目录:<ARM-Linux中断系统>. 总结: 二描述了中断处理示意图,以及关中断.开中断,和IRQ number重要概念. 三介绍了三个重要的结构体,irq_desc.irq_dat ...
- golang自定义路由控制实现(二)-流式注册接口以及支持RESTFUL
先简单回顾一下在上一篇的文章中,上一篇我主要是结合了数组和Map完成路由映射,数组的大小为8,下标为0的代表Get方法,以此类推,而数组的值则是Map,键为URL,值则是我们编写对应的接口.但 ...
- 在 Docker 容器中运行应用程序
案例说明 运行 3 个容器,实现对网站的监控. 三个容器的说明: 容器 web: 创建自 nginx 映像,使用 80 端口,运行于后台,实现 web 服务. 容器 mailer: 该容器中运行一个 ...
- Javascript继承,再谈
说到Javascript的继承,相信只要是前端开发者都有所了解或应用,因为这是太基础的知识了.但不知各位有没有深入去理解其中的玄机与奥秘.今本人不才,但也想用自己的理解来说一说这其中的玄机和奥秘. 一 ...
- 7-25 :active :after :before :disabled
1:<list,<datalist>,required,<select>,<option>,title,draggable,hidden 2:data-*和命 ...