在使用面向对象的思想进行系统设计时,应遵循面向对象的设计原则,前人总结的7条分别是:单一职责原则、开闭原则、里氏替换原则、依赖注入原则、接口分离原则、迪米特原则和优先使用组合而不是继承原则。
单一职责原则(SRP-Single Responsibility Principle)
单一职责也就是开发人员经常说的“高内聚低耦合”,也就是说系统中的每一个对象都应该有一个单独的职责,对外只能提供一种功能,引起对象变化的原因也只有一个,所有的设计模式都遵循这一原则。通常一个类的职责越多,导致其变化的因素也就越多。一般情况我们设计类时会把该类有关的操作都组合在一起,这样的结果就是有可能将多个职责聚合到了一起,当这个类的某个职责发生变化时,很难避免其他部分不受影响,最终导致程序的脆弱和僵硬。解决办法就是分耦,将不同职责分别进行封装。
例如用户的属性和用户的行为被放在一个接口中申明,造成业务对象和业务逻辑混在一起,使接口有两种职责。
/**
* JavaProject
* Created by xian.juanjuan on 2017-7-10 10:59.
*/
public interface Ijuanjuan {
//身高
double getShengao();
void setShengao(double height);
//体重
double getTizhong();
void setTizhong(double weight);
//吃饭
boolean chiFan(boolean hungry);
//上班
boolean shangBan(boolean flag);
}
分别定义属性和行为接口并分别实现
/**
* BO:Bussiness Object
*/
public interface IjuanjuanBO{
//身高
double getShengao();
void setShengao(double height);
//体重
double getTizhong();
void setTizhong(double weight);
}
public class JuanjuanBO implements IjuanjuanBO{
private double height;
private double weight;
@Override
public double getShengao() {
return height;
}
@Override
public double getTizhong() {
return weight;
}
@Override
public void setShengao(double height) {
this.height = height;
}
@Override
public void setTizhong(double weight) {
this.weight = weight;
}
}
/**
* BL:Business Logic
*/
public interface IjuanjuanBL{
//吃完上班
boolean chiFan(boolean hungry);
boolean shangBan(boolean flag);
}
public class JuanjuanBL implements IjuanjuanBL{
@Override
public boolean chiFan(boolean hungry) {
if(hungry){
System.out.println("吃大餐。。。");
return true;
}
return false;
}
@Override
public boolean shangBan(boolean flag) {
if (flag){
System.out.println("上班中。。。");
return true;
}
return false;
}
}
这样需要修改用户属性的时候只需要对IjuanjuanBO这个接口进行修改,影响的也只是JuanjuanBO这个类,其余的类不受影响。
SRP原则的好处是消除耦合,减小因需求变化而引起代码僵化的局面。
里氏替换原则(LSP-Liskov Substitution Principle)
里氏替换原则的核心思想是:在任何父类出现的地方都可以用他的子类来代替。即同一个继承体系中的对象应该拥有共同的行为特征。也就是说只要父类出现的地方子类就能出现而,且替换为子类不会出现任何错误和异常,但是反过来,子类出现的地方替换为父类很可能就出问题了。
这一原则为良好的继承指定了一个规范:
- 子类必须完全实现父类的方法
- 子类可以有自己的特性
- 覆盖或者实现父类的方法时输入参数可以被放大
- 覆盖或者实现父类的方法时输出结果可以被缩小
规范3示例
package com.xianjj.principle;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* JavaProject
* Created by xian.juanjuan on 2017-7-10 14:01.
*/
public class Father {
public Collection say(HashMap hashMap){
System.out.println("father 被执行");
return hashMap.values();
}
}
class Son extends Father{
public Collection say(Map map) {
System.out.println("son 被执行");
return map.values();
}
}
class Home{
public static void main(String[] args){
invoke();
}
public static void invoke(){
Son son = new Son();
HashMap hashMap = new HashMap();
son.say(hashMap);
Map map = new HashMap<>();
son.say(map);
Father father = new Father();
HashMap hashMap1 = new HashMap();
father.say(hashMap1);
}
}
执行结果
father 被执行
son 被执行
father 被执行
依赖注入原则(DIP-Dependence Inversion Principle)
依赖注入原则的核心思想:要依赖于抽象(抽象类或接口),不要依赖于具体的实现。在应用程序中,所有的类如果使用或依赖于其他类,都应该依赖于这些类的抽象类,而不是具体实现类。要求:开发人员应该针对接口编程
依赖注入原则三点说明:
- 高层模块不应该依赖底层模块,两者都应该依赖于抽象
- 抽象不应该依赖于细节
- 细节应该依赖抽象
依赖注入的本质是通过抽象(抽象类或接口是不能实例化)使各个类或者模块之间实现彼此独立,不相互影响,实现模块间的送耦合。
依赖注入的三种实现方式:
- 通过构造函数传递依赖对象(在构造函数中需要传递的参数是抽象类或接口)
- 通过setter方法传递依赖对象(我们设置setXX方法中的参数是抽象类或接口)
- 接口声明实现依赖对象
例如,涂涂只会煮面条
public class Tutu {
//涂涂只会煮面条
public void cook(Noodles noodles){
noodles.eat();
}
}
class Noodles{
public void eat(){
System.out.println("涂涂吃面条");
}
}
class Eat{
public static void main(String[] args){
Tutu tutu = new Tutu();
Noodles noodles = new Noodles();
tutu.cook(noodles);
}
}
涂涂天天吃面条吃腻了,想吃水煮鱼,大闸蟹怎么办呢,于是涂涂开始学习做其他吃的,需要实现Ifood接口。
public class Tutu {
public void cook(Ifood ifood){
ifood.eat();
}
}
interface Ifood{
public void eat();
}
class Noodles implements Ifood{
@Override
public void eat(){
System.out.println("涂涂吃面条");
}
}
class Rice implements Ifood{
@Override
public void eat() {
System.out.println("涂涂吃米饭");
}
}
class Eat{
public static void main(String[] args){
Tutu tutu = new Tutu();
//涂涂会煮面条
Ifood noodles = new Noodles();
tutu.cook(noodles);
//涂涂学会煮米饭
Ifood rice = new Rice();
tutu.cook(rice);
}
}
煮米饭和煮面条作为两个独立的模块互不影响,实现了松耦合。如果以后涂涂还想吃饺子,只需要实现Ifood接口即可。
接口分离原则(ISP-Interface Segregation Principle)
接口分离原则的核心思想:不应该强迫客户程序依赖他们不需要的方法。意思就是说:一个接口不需要不需要提供太多的行为,不应该把所有的操作都封装到一个接口中。
这里的接口不仅指interface定义的接口,包含以下两种:
- java中声明的一个类,通过new关键字产生的一个实例,他是对一个类型的事务的描述,也是一种接口(Phone phone = new Phone())
- 类接口,通过interface关键字定义好的接口
使用接口分离原则的规范:
- 接口尽量小(主要是保证一个接口只服务于一个子模块或者业务逻辑)
- 接口高内聚(对内高度依赖,对外尽可能隔离。即一个接口内部声明的方法相互之间都与某一个子模块相关,且是这个子模块必需的)
- 接口设计时有限度的(如果完全遵循接口分离原则会是接口的力度越来越小,这样造成接口数量剧增,增加系统复杂性,所以这个没有固定标准,需要根据经验判断)
//定义美女接口
public interface IprettyGirl {
void greatLooks();//长相好
void greatFigure();//身材好
void greatTemperament();//气质佳
}
class PrettyGirl implements IprettyGirl{
private String name;
public PrettyGirl(String name) {
this.name = name;
}
@Override
public void greatFigure() {
System.out.println(name+":身材非常好");
}
@Override
public void greatLooks() {
System.out.println(name+":长相非常好");
}
@Override
public void greatTemperament() {
System.out.println(name+":气质非常好");
}
}
//抽象一个帅哥
abstract class IMan{
protected IprettyGirl prettyGirl;
public IMan(IprettyGirl prettyGirl) {
this.prettyGirl = prettyGirl;
}
//帅哥开始找美女了
public abstract void findGirl();
}
class Man extends IMan{
public Man(IprettyGirl prettyGirl) {
super(prettyGirl);
}
@Override
public void findGirl() {
System.out.println("找到美女了。。。");
super.prettyGirl.greatFigure();
super.prettyGirl.greatLooks();
super.prettyGirl.greatTemperament();
}
}
class Beijing{//在北京找美女
public static void main(String[] args){
IprettyGirl jiajai = new PrettyGirl("佳佳");
IMan man = new Man(jiajai);
man.findGirl();
}
}
这里有个问题是,接口的划分不是很清晰,有的人认为长相好,身材好的就是美女,有的则认为气质佳的就是美女,所以需要把接口划分的再细致一点,长相好身材好的为一个接口,气质佳的为一个接口,以满足不同人的审美观。
迪米特原则(LOD-Law of Demeter)
迪米特原则的核心思想是:一个对象应该对其他对象尽可能少的了解。即实现对象之间解耦,弱耦合。例如除了探亲,监狱(类)里的犯人(类内部信息)不应该与外界有接触。他们与外界信息传递是通过狱警(迪米特法则的执行者)来执行。
例如:家人去监狱探亲,对于家人来说只与某个犯人是亲人,家人与其他犯人之间并不认识。家人与监狱之间就是弱耦合。
开闭原则(OCP-Open for Extension,Closed for Modification)
开闭原则的核心思想:一个对象对扩展开放,对修改关闭。意思是说对类的改动是通过增加代码进行的,而不是改动现有的代码。这就需要借助于抽象和多态,把可能变化的内容抽象出来,从而使抽象的部分相对稳定,具体的实现是可以改变和扩展的。
尽量使用对象组合,而不是对象继承
用一个示例来说明对象的继承与组合:
假如有对象A,实现了a1方法,对象C想扩展A的功能,并给它增加一个新的c11,两种实现方案
继承
class A{
public void a1(){
System.out.println("now in A.a1");
}
}
class C extends A{
public void c11(){
System.out.println("now in C.c11");
}
}
对象组合
class C2{
//创建A对象的实例
A a = new A();
public void a1(){
// 转调A对象的功能
a.a1();
}
public void c11(){
System.out.println("now in C2.c11");
}
}
对象组合优点:
- 可以由选择的复用功能,不是所有A的功能都会被复用;
- 在调转前后可以实现一些功能处理,并且A对象并不知道在调用a1方法的时候被添加了功能;
- 可以组合更多对象(Java不支持多继承)
摘自:修炼—清华大学出版社,于广编著