单一职责(Single Responsibility Principle )原则的定义是:一个接口或者类有且仅有一个原因引起类的变化。
每一个类实现的功能和作用要单一,比如实体类实现的是单纯的属性和get,set方法,是为了能生成一个纯净的类。实现逻辑操作的要重新生成一个类,不要在实体类中给出复杂业务逻辑的操作。调用到业务逻辑的服务操作也要重新生成一个类,边界尽量清晰。
单一职责设计的好处:
1.类的复杂性将i,实现职责明确定义。
2.可读性提高
3.可维护性提高
4.变更引起的风险降低,变更是必不可少的,但如果一个接口修改只对应的实现类是有影响的。
一般用户设计类图会这样设计如图:
但是这样的设计是不对的不符合单一职责的原则,应该把用户属性和 用户的业务分离开来可以这样:
抽取两个接口,一个是收集和反馈用户属性信息,一个是负责用户的行为完成用户的信息和变更。
如何代码使用如下:
IUserInfo userInfo = new UserInfo() ;
IUserBo userBo = userInfo;
userBo.setPassword("abc");
userBiz userBiz = userInfo ;
userBiz.deleteUser() ;
但实际使用中更倾向于这种:
项目中经常用到的SRP类图
抽象出IUserBo 和 IUserBiz
IUserBiz 依赖 IUserBo
引申:虚线的箭头表示依赖关系:A依赖类B(局部变量、方法的参数或者对静态方法的调用) 这里的A是IUserBiz,B为IUserBO
可能会这样使用:
IUserBiz userBiz = new UserBiz() ;
IUserBo userBo = new UserBo();
userBo.setPassword("abc") ;
userBiz.updateUser(userBo) ;
设计电话通话的过程:拨号、通话、挂机。写一个接口表示类图:
其中这里不仅仅是一个职责,拨号和挂机属于协议管理,通话属于数据传送。分为协议管理和数据传送这两个职责。一般设计时会认为手机有链接和传输的功能,而并非设计成 手机时一个连接器和传输器,故,需要思维转化所以使用单一职责的原则,
这样设计虽然结构清晰,但这样设计有一点不好,就是ConnectionManager类和DataTransfer 类 属于组合关系,需要组合在Phone类中使用,组合是一种强耦合关系,你和我都有共同的生命周期,这样的强耦合关系不如直接使用接口实现,如下图
单一职责也适用于方法,比如有的方法参数很多,甚至可以不定数的传参。这样的设计不符合单一职责。
里氏替换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏替换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。
子类可以继承父类的私有方法以外的所有方法和非私有的属性,重写可以覆盖掉父类中同名同参数的方法。
1.子类必须完全实现父类的方法。
2.子类可以有自己独立的属性和方法。
覆盖或实现父类的方法时输入参数可能会被放大。(如果子类给的参数范围大于父类,不会被执行到,要求子类给参数类型必须等于父类,从而达到复写)。
覆盖或者实现父类的方法时输出可以被缩小范围。(父类的返回参数类型必须大于子类)
在项目中,采用里氏替换原则时,尽量避免子类的“个性”,把子类当作父类来使用,子类的个性被抹杀——委屈了一点;然而把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离——缺乏类替换的标准。
使用接口,就是面向接口编程。“面向接口编程”——OOD(Object-Oriented Design)
定义:
1. 高层模块不因该依赖地层模块,两者都应该依赖其抽象;
2. 抽象不应该依赖细节;
3. 细节应该依赖抽象;
底层模块和高层模块: 不可分割的原子逻辑为底层模块。由多个实现原子逻辑组成的逻辑属于高层模块。
什么是细节: 细节就是实现类,实现接口或者继承抽象类而产生的类就是细节,其特点就是可以直接被实例化,就是加一个关键字new产生一个对象。
依赖倒置原则在java中的体现就是:
1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或者抽象类产生的;
2. 接口或抽象类不依赖于实现类;
3. 实现类依赖接口或抽象类;
使用依赖倒置原则的好处:可以减少类间的耦合性,提高系统的稳定性,降低并行开发引起的风险,提高代码的可读性和可维护性。
pubic interface IDriver{
//是司机就应该会驾驶汽车
public void drive(ICar car) ;
}
司机类的实现:
public class Driver implements IDriver{
//司机的主要职责就是驾驶汽车
public void drive(ICar car) {
car.run() ;
}
}
汽车接口以及实现类:
public interface ICar {
// 是汽车就应该能跑
public void run() ;
}
public class Benz implements ICar{
//汽车肯定会跑
public void run(){
System.out.println("奔驰汽车开起来了") ;
}
}
public class BMWimplements ICar{
//宝马汽车能开起来
public void run(){
System.out.println("宝马汽车开起来了") ;
}
}
业务场景
public class Main{
public static void main(String Args[]) {
IDriver zhangsan= new Driver() ;
ICar car = new Benz() ;
zhangsan.drive(car) ;
//当然zhangsan也可以驾驶 BMW
ICar car = new BMW() ;
zhansan.drive(car) ;
}
}
main属于高层业务逻辑,它对底层模块的依赖建立于抽象上。 后期如果张三想开别的车只需要增加汽车的避免了复杂的修改。
Java在运行时会出现两种类型:编译时类型和运行时类型。
编译时类型由声明该变量时使用的类决定,运行时类型,由实际赋给该变量的对象决定。两者不一致时会出现所谓的多态。
代码测试类:
测试驱动开发,先写好单元测试类,然后再写实现类,A程序员负责IDriver的开发,B程序员负责ICar 的开发,两个开发人员按照制定好的接口独立开发,结果 A 程序开发的比较快,完成了IDriver 类的开发工作,B程序员还没有完成开发。此时,A程序员可以使用JMock工具对抽象虚拟对象进行测试。如下代码:
public class DriverTest extends TestCase{
Mockery context = new JUnit4Mockery() ;
@Test
public void testDriver() {
final ICar car = context.mock(ICar.class);
IDriver driver = new Driver() ;
//内部类
context.checking(new Expectations(){}{
oneOf(car).run() ;
}});
driver.driver(car) ;
}
依赖是可以传递的 A可以依赖B ,B可以依赖C …
但要知道一点:只要做到抽象的依赖,即使多层依赖传递也无所畏惧。
1.依赖的第一种写法: 构造函数传递依赖
public interface IDriver {
//是司机就应该会驾驶
public void driver();
}
public class Driver implements IDriver {
private ICar car ;
//构造函数注入
public Driver(ICar _car){
this.car = _car
}
//司机的职责就是开车
public void drive() {
this.car.run() ;
}
}
2. 使用Setter注入
顾名思义就是使用Setter方法声明依赖关系,注入。
public interface IDriver {
public void setter(ICar car) ;
//是司机就应该会驾驶
public void driver();
}
public class Driver implements IDriver {
private ICar car ;
//构造函数注入
public Setter(ICar _car){
this.car = _car
}
//司机的职责就是开车
public void drive() {
this.car.run() ;
}
}
3.使用声明依赖对象
如1.3.1所示即可
接口的定义:
public abstract class AbstractSearcher {
protected IGoodBodyGirl goodBodyGirl ;
protected IGreatTemperamentGirl greatTemperamentGirl ;
public AbstractSearcher(IGoodBodyGirl _goodBodyGirl) {
this.goodBodyGirl = _goodBodyGirl ;
}
public AbstractSearcher(IGreatTemperamentGirl _greateTemperamentGirl) {
this.greatTemperamentGirl = _greateTemperamentGirl ;
}
//展示美女
public abstract void show() ;
}
IGoodBodyGirl 与 IGreatTemperamentGirl :
//长的好看的女孩
public interface IGoodBodyGirl {
//长的好看
public void goodLooking() ;
//好的身材
public void niceFigure() ;
}
//气质好的女孩
public interface IGreatTemperamentGirl {
//要有气质
public void greatTemprament() ;
}
PrettyGril :
public class PrettyGril implements IGoodBodyGirl,IGreatTemperamentGirl {
private String name ;
public PrettyGril(String _name){
this.name = _name ;
}
@Override
public void goodLooking() {
System.out.println(this.name+"脸蛋漂亮");
}
@Override
public void niceFigure() {
System.out.println(this.name+"体型漂亮");
}
@Override
public void greatTemprament() {
System.out.println(this.name+"很有气质");
}
}
Seacher
public class Seacher extends AbstractSearcher {
public Seacher(IGoodBodyGirl _goodBodyGirl) {
super(_goodBodyGirl);
}
public Seacher(IGreatTemperamentGirl _greateTemperamentGirl) {
super(_greateTemperamentGirl);
}
@Override
public void show() {
if(super.goodBodyGirl!=null) {
super.goodBodyGirl.goodLooking();
super.goodBodyGirl.niceFigure();
}
if(super.greatTemperamentGirl !=null) {
super.greatTemperamentGirl.greatTemprament();
}
}
}
迪米特法则(Law of Demeter,LoD) 也成为最少知识原则。
一个对象应该 对其他对象有最少的了解。 一个类内部如何复杂没关系,外部调用只关心public的方法。其他一概不关心。
迪米特法则包含4中要求:
朋友类定义: 一个类中出现的 成员变量、方法的输入输出参数,而出现在方法体中内的类的实例和声明不属于朋友类。
迪米特法则告诉我们一个类之和朋友类交流。但凡方法中有非朋友类声明和其实例将被视为违反了迪米特法则。(在某个类的方法中写业务时候,不能随便new对象了, 得看这个类是否是朋友类,如果不是,则违反了迪米特法则)
这里的 Teacher类中的Commond 方法
public void commond(GroupLeader groupLeader) {
List listGirls = new ArrayList() ;
///初始化listGirls
for(int i - 0 ; i<20;i++){
listGirls.add(new Girl()) ;
}
groupLeader.countGirls(listGirls) ;
}
teather类中有一个朋友groupLeader 。Girl 类并不是teacher 的朋友所以不能够在teather 的 成员方法中使用 。迪米特法则要求一个类只能和朋友类交流。如果在commond 方法中 List listGirls 动态数组有了交流,就破坏了Teacher的健壮性。 方法是一个类的行为,如果一个类竟然不知道自己的行为与其他类有依赖关系,这是不允许的,严重违反了迪米特法则。
如何修改呢, 将 List listGirls声明为Teacher类的成员变量,通过构造方法传入赋值即可。
public class Teacher {
private List listGirls ;
public Teacher( List _listGrils){
this.listGrils = _listGrils
}
}
朋友之间公开的public属性和方法也不宜过多。否则修改的时候设计的面就越大,引起变更风险扩散就越大。
例子:
InstallSoftware 安装软件类 依赖 Wizard 安装向导类
public class Wizard{
public int first() {
return 1 ;
}
public int second() {
return 2 ;
}
public int third() {
return 3;
}
}
public class InstallSoftware{
public void installWizard(Wizard wizard){
int first = wizard.first() ;
if(first>50){
int second = wizard.second() ;
if(second>50){
int third = wizard.third() ;
if(third>50) {
System.out.prinln(“安装成功!”)
}
}
}
}
}
我们可以看到 Wizard 是 InstallSoftware 类的 朋友类,但是
Wizard的太多方法暴漏给朋友了,这样会导致一个问题,两者朋友关系太亲密耦合关系变得异常牢固。如果将Wizrd类中的 first方法 返回值由int 改为 boolean ,就需要修改 InstallSoftware类,从而把修改的风险扩散开。所以这样的耦合关系不合理,需要进行重构。
如图所示将 first() second() third() 方法由public 改为 private。
再增加一个public的installWizard()方法,在这个方法中负责前边这些方法的业务。 这样再后期修改的时候只修改当前类即可。
其代码如下:
public class Wizard{
private int first() {
return 1 ;
}
private int second() {
return 2 ;
}
private int third() {
return 3;
}
public void installWizard(){
int first = wizard.first() ;
if(first>50){
int second = wizard.second() ;
if(second>50){
int third = wizard.third() ;
if(third>50) {
System.out.prinln(“安装成功!”) :
}
}
}
}
}
public class InstallSoftware{
public void installWizard(Wizard wizard){
wizard.installWizard() ;
}
}
这样Wizard类只对外公布了一个public方法,若有修改first方法的需求 ,影响的也只是本类其他类不受影响,这显示了类的高内聚特性
迪米特法则要求“羞涩”一点,尽量不要对外公布太多的public方法和非静态的public变量,尽量内敛,多使用private、protected等访问权限。
在实际应用中会出现这种方法: 放在本类也可以,放在其他类也没有错,拿怎么去衡量呢? 可以坚持这个原则:如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,就放置到本类中。
小结:
迪米特法则 就是让 每个对象管好自己的一亩二分地,尽可能公共独立可供其他类调用,减少只作为某一个对象私有对象。
迪米特法则要求类间解耦,但解耦是有限度的,在实际项目中需要适度的考虑这个原则,不能为了适应原则而做项目。要求类与类之间的通信尽可能在类中暴漏尽量少的方法和属性,便于维护,也就是所说的高内聚低耦合。尽量不传递串联太多的类,要不然维护起来特别麻烦。最少知道原则希望我们不要知道彼此太多,不然干涉互相伤害纠缠不清。
一个软件实体如 类、模块和函数应该对扩展开放,对修改关闭。
也就是说一个软件实体应该通过扩展来实现变化,而不是通过修改软件来实现变化。
软件实体包括以下部分:
书店销售例子:IBook定义了数据三个属性,名称 价格 和作者。小说类NovelBook 是一个具体实现的类,BookStore 包含Main函数。如下图:
若想增加打折功能,根据开闭原则在不修改原先代码的基础上进行扩展,即创建一个OffNovelBook ,复写getPrice()方法,将打折的逻辑写在里边即可。
代码如下:
public interface IBook {
public String getName() ;
public String getPrice() ;
public String getAuthor() ;
}
public class NovelBook implements IBook {
private String name ;
private int price ;
private String author ;
public NovelBook(String _name,int _Price,String _author){
this.name = _name ;
this.price = _price ;
this.author = _author ;
}
///省略实现IBook 接口的 好多 get 方法
}
public class BookStore {
private final static ArrayList bookLIst = new ArrayList() ;
//static 静态模块初始化
static {
bookList.add(new NovelBook("天龙八部",3200,"金庸")) ;
bookList.add(new NovelBook("巴黎圣母院",3200,"雨果")) ;
bookList.add(new NovelBook("悲惨世界",3200,"雨果")) ;
bookList.add(new NovelBook("aaa",3200,"bb")) ;
}
public static void main(String Args[]) {
//for (IBook book: bookList){
book.getAuthor() ;
book.getName() ;
book.getPrice() ;
}
}
}
如果增加计算机类图书,并且计算机类图书新增了一个方法就是获得该书籍的范围
如图所示:
新增一个IComputerBook 接口及其 实现类
代码如下:
public interface IComputerBook extends IBook{
public String getScope() ;
}
public class ComputerBook implements IComputerBook{
private String name ;
private String scope ;
private String author;
private int price ;
public ComputerBook(String _name,String _scope,String _author,String _price){
this.name = _name ;
this.scope = _scope;
this.author = _author;
this.price = _price ;
}
// 省略一堆 实现的方法
}