理解架构中的设计原则

来源:互联网 发布:java 乐观锁 例子 编辑:程序博客网 时间:2024/05/19 22:55

在使用面向对象的思想进行系统设计时,应遵循面向对象的设计原则,前人总结的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)

里氏替换原则的核心思想是:在任何父类出现的地方都可以用他的子类来代替。即同一个继承体系中的对象应该拥有共同的行为特征。也就是说只要父类出现的地方子类就能出现而,且替换为子类不会出现任何错误和异常,但是反过来,子类出现的地方替换为父类很可能就出问题了。

这一原则为良好的继承指定了一个规范:

  1. 子类必须完全实现父类的方法
  2. 子类可以有自己的特性
  3. 覆盖或者实现父类的方法时输入参数可以被放大
  4. 覆盖或者实现父类的方法时输出结果可以被缩小

规范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不支持多继承)

摘自:修炼—清华大学出版社,于广编著

原创粉丝点击