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 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...
随机推荐
- 单片机开发——02工欲善其事必先利其器(Proteus软件安装破解)
在单片机开发工程中,博主经常通过模拟软件Proteus进行模拟仿真,将编译生成的"HEX"文件下载在单片机芯片中,然后进行后期的debug工作,当模拟仿真完成之后,进行硬件测试部分 ...
- 第二次作业 单例模式的SessionFactory
一.基础Hibernate环境搭建(参见http://www.cnblogs.com/sangewuxie/p/9004968.html) 二.实体类User及User.hbm.xml配置 1.Use ...
- ubuntu 命令整合2
通配符 * 匹配任意多个字符 ?匹配一个任意字符 示例:ls *.txt rm -rf *.txt 文本编辑器 vi.vim 格式:vi 文件名 编辑 vi的三种工作模式 正常模式(启动进入的模式) ...
- Flask构建微电影(二)
第三章.项目分析.搭建目录及模型设计 3.1.前后台项目目录分析 微电影网站 前台模块 后台模块 前台(home) 数据模型:models.py 表单处理:home/forms.py 模板目录:tem ...
- Struts标签库详解【3】
struts2标签库详解 要在jsp中使用Struts2的标志,先要指明标志的引入.通过jsp的代码的顶部加入以下的代码: <%@taglib prefix="s" uri= ...
- SVG的动态之美-搜狗地铁图重构散记
搜狗地图发布了新版的移动端地铁图,改版初衷是为了用户交互体验的提升以及性能的改善.原版地铁图被用户吐槽最多的是pinch缩放不流畅.无过渡动画.拖拽边界不合理等等,大体上都是交互体验上的问题.实际上原 ...
- this.state.menuList.toArray()[0].get('id')
用toArray()处理传过来的list用get(")获取里面的值 而我用的是attributes得方法
- 你不知道的JavaScript--Item10 闭包(closure)
JavaScript 闭包究竟是什么? 用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑.陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了资料也看了一些,但还是 ...
- 译MassTransit 消息契约
消息契约 在MassTransit中,使用.NET .NET系统定义消息契约.消息可以使用类和接口来定义,但是,建议类型使用只读属性而不使用行为. 注意:强烈建议使用消息接口的接口,基于多年的经验,具 ...
- linux下安装python3.6.4
想在阿里云端装一个 python36,因为自带的python2有点老 ,(centos系统) 当然你如果选择的乌班图系统的话就自带了python3,就不用看了 于是查找资料,但是一步一步的来总是不行, ...