理解架构中的设计原则

xiaoxiao2021-02-28  110

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

 

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

转载请注明原文地址: https://www.6miu.com/read-17897.html

最新回复(0)