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 中非常重要,甚至几乎所有的编程语言都需要,毕竟程序具有良好的扩展性.维护性谁都不能拒绝 ...
随机推荐
- 洛谷 P3177 树上染色 解题报告
P3177 [HAOI2015]树上染色 题目描述 有一棵点数为\(N\)的树,树边有边权.给你一个在\(0\) ~ \(N\)之内的正整数\(K\),你要在这棵树中选择\(K\)个点,将其染成黑色, ...
- (WCF初体验)WCF的认证和消息保护
最近做WCF开发,有个需求是在服务端做认证,网上查资料了解到可以用UserName和Password 来做认证,只需要写好配置文件和在服务端写好验证类就行了,但是网上普遍的博文都是需要用证书,而我自己 ...
- Mabits简单应用 2017.8.3
http://www.cnblogs.com/wushiqi54719880/archive/2011/07/26/2117601.html
- SQL—对数据表内容的基本操作
数据表 students id name sex age address 101 张汉 男 14 杭州 102 欧阳钦 男 13 杭州 103 吴昊 男 14 北京 104 钱进进 男 1 ...
- CSS选择器详细总结
一.基本选择器 序号 选择器 含义 1. * 通用元素选择器,匹配任何元素 2. E 标签选择器,匹配所有使用E标签的元素 3. .info class选择器,匹配所有class属性中包含info的元 ...
- 使用 python 处理 nc 数据
前言 这两天帮一个朋友处理了些 nc 数据,本以为很简单的事情,没想到里面涉及到了很多的细节和坑,无论是"知难行易"还是"知易行难"都不能充分的说明问题,还是& ...
- 手机端实现fullPage——全屏滚动效果
封装了一个小插件模拟fullPage的全屏滚动效果,比较简单. 特点: 1. 纯js实现,小巧轻便. 2. 兼容性好.苹果.安卓都没问题,暂时没遇到问题机型. 缺点: 1. 仅封装了基础功能,H ...
- Python 字典(Dictionary) has_key()方法
描述 Python 字典(Dictionary) has_key() 函数用于判断键是否存在于字典中,如果键在字典dict里返回true,否则返回false. 语法 has_key()方法语法:dic ...
- Linux 6.8 TFS(Taobao File System)使用文档-安装篇
介绍 TFS(Taobao FileSystem)是一个高可扩展.高可用.高性能.面向互联网服务的分布式文件系统,其设计目标是支持海量的非结构化数据的存储:TFS使用C++语言开发,需要运行在64b ...
- django(权限、认证)系统——自定义UserProfile储存User额外信息
上篇文章我们引出了Django内置的权限控制系统,讲了安装,和最核心和基本的User模型的API和其Manager的API. 接下来我们继续深入下去,使用User对象做一些事情,首先当然就是创建一个U ...